/* * Copyright (c) 2014 Google, Inc * * (C) Copyright 2002 * Gerald Van Baren, Custom IDEAS, vanbaren@cideas.com. * * Influenced by code from: * Wolfgang Denk, DENX Software Engineering, wd@denx.de. * * SPDX-License-Identifier: GPL-2.0+ */ #include #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; struct soft_spi_platdata { struct gpio_desc cs; struct gpio_desc sclk; struct gpio_desc mosi; struct gpio_desc miso; int spi_delay_us; int flags; }; #define SPI_MASTER_NO_RX BIT(0) #define SPI_MASTER_NO_TX BIT(1) struct soft_spi_priv { unsigned int mode; }; static int soft_spi_scl(struct udevice *dev, int bit) { struct udevice *bus = dev_get_parent(dev); struct soft_spi_platdata *plat = dev_get_platdata(bus); dm_gpio_set_value(&plat->sclk, bit); return 0; } static int soft_spi_sda(struct udevice *dev, int bit) { struct udevice *bus = dev_get_parent(dev); struct soft_spi_platdata *plat = dev_get_platdata(bus); dm_gpio_set_value(&plat->mosi, bit); return 0; } static int soft_spi_cs_activate(struct udevice *dev) { struct udevice *bus = dev_get_parent(dev); struct soft_spi_platdata *plat = dev_get_platdata(bus); dm_gpio_set_value(&plat->cs, 0); dm_gpio_set_value(&plat->sclk, 0); dm_gpio_set_value(&plat->cs, 1); return 0; } static int soft_spi_cs_deactivate(struct udevice *dev) { struct udevice *bus = dev_get_parent(dev); struct soft_spi_platdata *plat = dev_get_platdata(bus); dm_gpio_set_value(&plat->cs, 0); return 0; } static int soft_spi_claim_bus(struct udevice *dev) { /* * Make sure the SPI clock is in idle state as defined for * this slave. */ return soft_spi_scl(dev, 0); } static int soft_spi_release_bus(struct udevice *dev) { /* Nothing to do */ return 0; } /*----------------------------------------------------------------------- * SPI transfer * * This writes "bitlen" bits out the SPI MOSI port and simultaneously clocks * "bitlen" bits in the SPI MISO port. That's just the way SPI works. * * The source of the outgoing bits is the "dout" parameter and the * destination of the input bits is the "din" parameter. Note that "dout" * and "din" can point to the same memory location, in which case the * input data overwrites the output data (since both are buffered by * temporary variables, this is OK). */ static int soft_spi_xfer(struct udevice *dev, unsigned int bitlen, const void *dout, void *din, unsigned long flags) { struct udevice *bus = dev_get_parent(dev); struct soft_spi_priv *priv = dev_get_priv(bus); struct soft_spi_platdata *plat = dev_get_platdata(bus); uchar tmpdin = 0; uchar tmpdout = 0; const u8 *txd = dout; u8 *rxd = din; int cpha = priv->mode & SPI_CPHA; unsigned int j; debug("spi_xfer: slave %s:%s dout %08X din %08X bitlen %u\n", dev->parent->name, dev->name, *(uint *)txd, *(uint *)rxd, bitlen); if (flags & SPI_XFER_BEGIN) soft_spi_cs_activate(dev); for (j = 0; j < bitlen; j++) { /* * Check if it is time to work on a new byte. */ if ((j % 8) == 0) { if (txd) tmpdout = *txd++; else tmpdout = 0; if (j != 0) { if (rxd) *rxd++ = tmpdin; } tmpdin = 0; } if (!cpha) soft_spi_scl(dev, 0); if ((plat->flags & SPI_MASTER_NO_TX) == 0) soft_spi_sda(dev, !!(tmpdout & 0x80)); udelay(plat->spi_delay_us); if (cpha) soft_spi_scl(dev, 0); else soft_spi_scl(dev, 1); tmpdin <<= 1; if ((plat->flags & SPI_MASTER_NO_RX) == 0) tmpdin |= dm_gpio_get_value(&plat->miso); tmpdout <<= 1; udelay(plat->spi_delay_us); if (cpha) soft_spi_scl(dev, 1); } /* * If the number of bits isn't a multiple of 8, shift the last * bits over to left-justify them. Then store the last byte * read in. */ if (rxd) { if ((bitlen % 8) != 0) tmpdin <<= 8 - (bitlen % 8); *rxd++ = tmpdin; } if (flags & SPI_XFER_END) soft_spi_cs_deactivate(dev); return 0; } static int soft_spi_set_speed(struct udevice *dev, unsigned int speed) { /* Accept any speed */ return 0; } static int soft_spi_set_mode(struct udevice *dev, unsigned int mode) { struct soft_spi_priv *priv = dev_get_priv(dev); priv->mode = mode; return 0; } static const struct dm_spi_ops soft_spi_ops = { .claim_bus = soft_spi_claim_bus, .release_bus = soft_spi_release_bus, .xfer = soft_spi_xfer, .set_speed = soft_spi_set_speed, .set_mode = soft_spi_set_mode, }; static int soft_spi_ofdata_to_platdata(struct udevice *dev) { struct soft_spi_platdata *plat = dev->platdata; const void *blob = gd->fdt_blob; int node = dev->of_offset; plat->spi_delay_us = fdtdec_get_int(blob, node, "spi-delay-us", 0); return 0; } static int soft_spi_probe(struct udevice *dev) { struct spi_slave *slave = dev_get_parent_priv(dev); struct soft_spi_platdata *plat = dev->platdata; int cs_flags, clk_flags; int ret; cs_flags = (slave->mode & SPI_CS_HIGH) ? 0 : GPIOD_ACTIVE_LOW; clk_flags = (slave->mode & SPI_CPOL) ? GPIOD_ACTIVE_LOW : 0; if (gpio_request_by_name(dev, "cs-gpios", 0, &plat->cs, GPIOD_IS_OUT | cs_flags) || gpio_request_by_name(dev, "gpio-sck", 0, &plat->sclk, GPIOD_IS_OUT | clk_flags)) return -EINVAL; ret = gpio_request_by_name(dev, "gpio-mosi", 0, &plat->mosi, GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE); if (ret) plat->flags |= SPI_MASTER_NO_TX; ret = gpio_request_by_name(dev, "gpio-miso", 0, &plat->miso, GPIOD_IS_IN); if (ret) plat->flags |= SPI_MASTER_NO_RX; if ((plat->flags & (SPI_MASTER_NO_RX | SPI_MASTER_NO_TX)) == (SPI_MASTER_NO_RX | SPI_MASTER_NO_TX)) return -EINVAL; return 0; } static const struct udevice_id soft_spi_ids[] = { { .compatible = "spi-gpio" }, { } }; U_BOOT_DRIVER(soft_spi) = { .name = "soft_spi", .id = UCLASS_SPI, .of_match = soft_spi_ids, .ops = &soft_spi_ops, .ofdata_to_platdata = soft_spi_ofdata_to_platdata, .platdata_auto_alloc_size = sizeof(struct soft_spi_platdata), .priv_auto_alloc_size = sizeof(struct soft_spi_priv), .probe = soft_spi_probe, };