dm: spi: Add soft_spi implementation
authorSimon Glass <sjg@chromium.org>
Tue, 14 Oct 2014 05:42:00 +0000 (23:42 -0600)
committerSimon Glass <sjg@chromium.org>
Wed, 22 Oct 2014 16:36:48 +0000 (10:36 -0600)
Add a new implementation of soft_spi that uses device tree to specify the
GPIOs. This will replace soft_spi_legacy for boards which use driver model.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Jagannadha Sutradharudu Teki <jagannadh.teki@gmail.com>
doc/device-tree-bindings/spi/soft-spi.txt [new file with mode: 0644]
drivers/spi/Makefile
drivers/spi/soft_spi.c [new file with mode: 0644]

diff --git a/doc/device-tree-bindings/spi/soft-spi.txt b/doc/device-tree-bindings/spi/soft-spi.txt
new file mode 100644 (file)
index 0000000..d09c1a5
--- /dev/null
@@ -0,0 +1,34 @@
+Soft SPI
+
+The soft SPI bus implementation allows the use of GPIO pins to simulate a
+SPI bus. No SPI host is required for this to work. The down-side is that the
+performance will typically be much lower than a real SPI bus.
+
+The soft SPI node requires the following properties:
+
+compatible: "u-boot,soft-spi"
+soft_spi_cs: GPIO number to use for SPI chip select (output)
+soft_spi_sclk: GPIO number to use for SPI clock (output)
+soft_spi_mosi: GPIO number to use for SPI MOSI line (output)
+soft_spi_miso GPIO number to use for SPI MISO line (input)
+spi-delay-us: Number of microseconds of delay between each CS transition
+
+The GPIOs should be specified as required by the GPIO controller referenced.
+The first cell holds the phandle of the controller and the second cell
+typically holds the GPIO number.
+
+
+Example:
+
+       soft-spi {
+               compatible = "u-boot,soft-spi";
+               cs-gpio = <&gpio 235 0>;        /* Y43 */
+               sclk-gpio = <&gpio 225 0>;      /* Y31 */
+               mosi-gpio = <&gpio 227 0>;      /* Y33 */
+               miso-gpio = <&gpio 224 0>;      /* Y30 */
+               spi-delay-us = <1>;
+               #address-cells = <1>;
+               #size-cells = <0>;
+               cs@0 {
+               };
+       };
index 5cc8655d136cf10f2ceaa3770799f5ab331012e3..e71852898a870c7d424bdddadcac440ec9b087f3 100644 (file)
@@ -9,6 +9,7 @@
 ifdef CONFIG_DM_SPI
 obj-y += spi-uclass.o
 obj-$(CONFIG_SANDBOX) += spi-emul-uclass.o
+obj-$(CONFIG_SOFT_SPI) += soft_spi.o
 else
 obj-y += spi.o
 obj-$(CONFIG_SOFT_SPI) += soft_spi_legacy.o
diff --git a/drivers/spi/soft_spi.c b/drivers/spi/soft_spi.c
new file mode 100644 (file)
index 0000000..5588036
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * 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 <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <fdtdec.h>
+#include <malloc.h>
+#include <spi.h>
+#include <asm/gpio.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct soft_spi_platdata {
+       struct fdt_gpio_state cs;
+       struct fdt_gpio_state sclk;
+       struct fdt_gpio_state mosi;
+       struct fdt_gpio_state miso;
+       int spi_delay_us;
+};
+
+struct soft_spi_priv {
+       unsigned int mode;
+};
+
+static int soft_spi_scl(struct udevice *dev, int bit)
+{
+       struct soft_spi_platdata *plat = dev->platdata;
+       struct soft_spi_priv *priv = dev_get_priv(dev);
+
+       gpio_set_value(plat->sclk.gpio, priv->mode & SPI_CPOL ? bit : !bit);
+
+       return 0;
+}
+
+static int soft_spi_sda(struct udevice *dev, int bit)
+{
+       struct soft_spi_platdata *plat = dev->platdata;
+
+       gpio_set_value(plat->mosi.gpio, bit);
+
+       return 0;
+}
+
+static int soft_spi_cs_activate(struct udevice *dev)
+{
+       struct soft_spi_platdata *plat = dev->platdata;
+       struct soft_spi_priv *priv = dev_get_priv(dev);
+
+       gpio_set_value(plat->cs.gpio, !(priv->mode & SPI_CS_HIGH));
+       gpio_set_value(plat->sclk.gpio, priv->mode & SPI_CPOL);
+       gpio_set_value(plat->cs.gpio, priv->mode & SPI_CS_HIGH);
+
+       return 0;
+}
+
+static int soft_spi_cs_deactivate(struct udevice *dev)
+{
+       struct soft_spi_platdata *plat = dev->platdata;
+       struct soft_spi_priv *priv = dev_get_priv(dev);
+
+       gpio_set_value(plat->cs.gpio, !(priv->mode & SPI_CS_HIGH));
+
+       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 soft_spi_priv *priv = dev_get_priv(dev);
+       struct soft_spi_platdata *plat = dev->platdata;
+       uchar           tmpdin  = 0;
+       uchar           tmpdout = 0;
+       const u8        *txd = dout;
+       u8              *rxd = din;
+       int             cpol = priv->mode & SPI_CPOL;
+       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, !cpol);
+               soft_spi_sda(dev, tmpdout & 0x80);
+               udelay(plat->spi_delay_us);
+               if (cpha)
+                       soft_spi_scl(dev, !cpol);
+               else
+                       soft_spi_scl(dev, cpol);
+               tmpdin  <<= 1;
+               tmpdin  |= gpio_get_value(plat->miso.gpio);
+               tmpdout <<= 1;
+               udelay(plat->spi_delay_us);
+               if (cpha)
+                       soft_spi_scl(dev, cpol);
+       }
+       /*
+        * 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 int soft_spi_child_pre_probe(struct udevice *dev)
+{
+       struct spi_slave *slave = dev_get_parentdata(dev);
+
+       slave->dev = dev;
+       return spi_ofdata_to_platdata(gd->fdt_blob, dev->of_offset, slave);
+}
+
+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;
+
+       if (fdtdec_decode_gpio(blob, node, "cs-gpio", &plat->cs) ||
+           fdtdec_decode_gpio(blob, node, "sclk-gpio", &plat->sclk) ||
+           fdtdec_decode_gpio(blob, node, "mosi-gpio", &plat->mosi) ||
+           fdtdec_decode_gpio(blob, node, "miso-gpio", &plat->miso))
+               return -EINVAL;
+       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_parentdata(dev);
+       struct soft_spi_platdata *plat = dev->platdata;
+
+       gpio_request(plat->cs.gpio, "soft_spi_cs");
+       gpio_request(plat->sclk.gpio, "soft_spi_sclk");
+       gpio_request(plat->mosi.gpio, "soft_spi_mosi");
+       gpio_request(plat->miso.gpio, "soft_spi_miso");
+
+       gpio_direction_output(plat->sclk.gpio, slave->mode & SPI_CPOL);
+       gpio_direction_output(plat->mosi.gpio, 1);
+       gpio_direction_input(plat->miso.gpio);
+       gpio_direction_output(plat->cs.gpio, !(slave->mode & SPI_CS_HIGH));
+
+       return 0;
+}
+
+static const struct udevice_id soft_spi_ids[] = {
+       { .compatible = "u-boot,soft-spi" },
+       { }
+};
+
+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),
+       .per_child_auto_alloc_size      = sizeof(struct spi_slave),
+       .probe  = soft_spi_probe,
+       .child_pre_probe        = soft_spi_child_pre_probe,
+};