[RFC 2/3] SPI-GPIO variant optimized for the Ingenic Jz4740 SoC
Werner Almesberger
werner at almesberger.net
Mon Apr 29 20:15:46 EDT 2013
This is a drop-in replacement for spi-gpio.c optimized for Jz4740-based
systems. It is up to about six times faster than its generic counterpart.
Only supports SPI mode 0 and CS active-low. Furthermore, MOSI, MISO, and
SCK must be on the same port.
A detailed performance analysis can be found here:
http://projects.qi-hardware.com/index.php/p/ben-wpan/source/tree/master/atben/misc/atben-spi-performance.txt
---
Kconfig | 8 +
Makefile | 1
spi-jz4740-gpio.c | 424 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 433 insertions(+)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 2be0de9..897bdda 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -187,6 +187,14 @@ config SPI_IMX
This enables using the Freescale i.MX SPI controllers in master
mode.
+config SPI_JZ4740_GPIO
+ tristate "GPIO-based SPI Master for Ingenic Jz4740"
+ depends on GENERIC_GPIO && MACH_JZ4740
+ select SPI_BITBANG
+ help
+ This is a variant of SPI-GPIO optimized for the Ingenic Jz4740.
+ Only supports SPI mode 0 and CS active-low.
+
config SPI_LM70_LLP
tristate "Parallel port adapter for LM70 eval board (DEVELOPMENT)"
depends on PARPORT
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index e53c309..18e5aee 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_SPI_FSL_ESPI) += spi-fsl-espi.o
obj-$(CONFIG_SPI_FSL_SPI) += spi-fsl-spi.o
obj-$(CONFIG_SPI_GPIO) += spi-gpio.o
obj-$(CONFIG_SPI_IMX) += spi-imx.o
+obj-$(CONFIG_SPI_JZ4740_GPIO) += spi-jz4740-gpio.o
obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o
obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o
obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o
diff --git a/drivers/spi/spi-jz4740-gpio.c b/drivers/spi/spi-jz4740-gpio.c
new file mode 100644
index 0000000..d8cdbfd
--- /dev/null
+++ b/drivers/spi/spi-jz4740-gpio.c
@@ -0,0 +1,424 @@
+/*
+ * spi-jz4740-gpio.c - Bit-banging SPI host for the Jz4740
+ *
+ * Written 2011, 2013 by Werner Almesberger
+ * Based on spi-gpio.c, Copyright (C) 2006,2008 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ */
+
+/*
+ * This is a drop-in replacement for spi-gpio.c optimized for Jz4740-based
+ * systems. It is up to about six times faster than its generic counterpart.
+ *
+ * There are three restrictions and usage differences:
+ *
+ * 1) No other configurations than SPI mode 0 and CS active-low are
+ * supported.
+ *
+ * 2) MOSI, MISO, and SCK must be on the same port. Driver probing fails
+ * if they are not.
+ *
+ * 3) struct platform_device.name must be "spi_jz4740_gpio" instead of
+ * "spi_gpio".
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_gpio.h>
+#include <asm/mach-jz4740/base.h>
+
+
+#define DRIVER_NAME "spi_jz4740_gpio"
+
+struct spi_jz4740_gpio {
+ const struct spi_gpio_platform_data *pdata;
+ struct device *dev;
+ void __iomem *port_base;
+ uint32_t mosi, miso, sck;
+ unsigned long port_addr;
+};
+
+#define PxPIN (prv->port_base)
+#define PxDATS (prv->port_base+0x14)
+#define PxDATC (prv->port_base+0x18)
+
+#define PIN_TO_PORT(pin) ((pin) >> 5)
+#define PIN_TO_MASK(pin) (1 << ((pin) & 31))
+
+
+/* ----- SPI transfers ----------------------------------------------------- */
+
+
+static void rx_only(const struct spi_jz4740_gpio *prv, uint8_t *buf, int len)
+{
+ uint32_t miso = prv->miso;
+ uint32_t sck = prv->sck;
+ uint8_t v;
+
+ while (len--) {
+ writel(sck, PxDATS);
+ v = readl(PxPIN) & miso ? 0x80 : 0;
+ writel(sck, PxDATC);
+
+ #define DO_BIT(m) \
+ writel(sck, PxDATS); \
+ if (readl(PxPIN) & miso) \
+ v |= (m); \
+ writel(sck, PxDATC)
+
+ DO_BIT(0x40);
+ DO_BIT(0x20);
+ DO_BIT(0x10);
+ DO_BIT(0x08);
+ DO_BIT(0x04);
+ DO_BIT(0x02);
+ DO_BIT(0x01);
+
+ #undef DO_BIT
+
+ *buf++ = v;
+ }
+}
+
+
+static void tx_only(const struct spi_jz4740_gpio *prv,
+ const uint8_t *buf, int len)
+{
+ uint32_t mosi = prv->mosi;
+ uint32_t sck = prv->sck;
+ uint8_t tv;
+
+ while (len--) {
+ tv = *buf++;
+
+ if (tv & 0x80) {
+ writel(mosi, PxDATS);
+ goto b6_1;
+ } else {
+ writel(mosi, PxDATC);
+ goto b6_0;
+ }
+
+ #define DO_BIT(m, this, next) \
+ this##_1: \
+ writel(sck, PxDATS); \
+ if (tv & (m)) { \
+ writel(sck, PxDATC); \
+ goto next##_1; \
+ } else { \
+ writel(mosi | sck, PxDATC); \
+ goto next##_0; \
+ } \
+ this##_0: \
+ writel(sck, PxDATS); \
+ writel(sck, PxDATC); \
+ if (tv & (m)) { \
+ writel(mosi, PxDATS); \
+ goto next##_1; \
+ } else { \
+ goto next##_0; \
+ }
+
+ DO_BIT(0x40, b6, b5);
+ DO_BIT(0x20, b5, b4);
+ DO_BIT(0x10, b4, b3);
+ DO_BIT(0x08, b3, b2);
+ DO_BIT(0x04, b2, b1);
+ DO_BIT(0x02, b1, b0);
+ DO_BIT(0x01, b0, done);
+
+ #undef DO_BIT
+
+done_1:
+done_0:
+ writel(sck, PxDATS);
+ writel(sck, PxDATC);
+ }
+}
+
+
+static void bidir(const struct spi_jz4740_gpio *prv,
+ const uint8_t *tx, uint8_t *rx, int len)
+{
+ uint32_t mosi = prv->mosi;
+ uint32_t miso = prv->miso;
+ uint32_t sck = prv->sck;
+ uint8_t mask, tv, rv = 0;
+
+ while (len--) {
+ tv = *tx++;
+ for (mask = 0x80; mask; mask >>= 1) {
+ if (tv & mask)
+ writel(mosi, PxDATS);
+ else
+ writel(mosi, PxDATC);
+ writel(sck, PxDATS);
+ if (readl(PxPIN) & miso)
+ rv |= mask;
+ writel(sck, PxDATC);
+ }
+ *rx++ = rv;
+ }
+}
+
+
+static inline unsigned get_nsel(struct spi_device *spi)
+{
+ return (unsigned) spi->controller_data;
+}
+
+
+static int spi_jz4740_gpio_transfer_one(struct spi_master *master,
+ struct spi_message *msg)
+{
+ struct spi_jz4740_gpio *prv = spi_master_get_devdata(master);
+ struct spi_device *spi = msg->spi;
+ uint32_t nsel = get_nsel(spi);
+ struct spi_transfer *xfer;
+ const uint8_t *tx;
+ uint8_t *rx;
+
+ if (unlikely(list_empty(&msg->transfers))) {
+ dev_err(&spi->dev, "transfer is empty\n");
+ msg->status = -EINVAL;
+ goto out;
+ }
+
+ msg->actual_length = 0;
+ msg->status = 0;
+
+ gpio_set_value(nsel, 0);
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ tx = xfer->tx_buf;
+ rx = xfer->rx_buf;
+ msg->actual_length += xfer->len;
+
+ if (!tx)
+ rx_only(prv, rx, xfer->len);
+ else if (!rx)
+ tx_only(prv, tx, xfer->len);
+ else
+ bidir(prv, tx, rx, xfer->len);
+ }
+ gpio_set_value(nsel, 1);
+
+out:
+ spi_finalize_current_message(master);
+
+ return 0;
+}
+
+
+/* ----- Device-specific setup (nSEL) -------------------------------------- */
+
+
+static int get_gpio(struct spi_jz4740_gpio *prv,
+ unsigned pin, const char *label, int value)
+{
+ int err;
+ unsigned long port;
+
+ err = gpio_request(pin, label);
+ if (err)
+ return err;
+
+ if (value >= 0)
+ err = gpio_direction_output(pin, value);
+ else
+ err = gpio_direction_input(pin);
+ if (err)
+ goto fail;
+
+ err = jz_gpio_set_function(pin, JZ_GPIO_FUNC_NONE);
+ if (err)
+ goto fail;
+
+ if (!prv)
+ return 0;
+
+ port = JZ4740_GPIO_BASE_ADDR + 0x100 * PIN_TO_PORT(pin);
+ if (prv->port_addr && prv->port_addr != port) {
+ err = -EINVAL;
+ goto fail;
+ }
+ prv->port_addr = port;
+
+ return 0;
+
+fail:
+ gpio_free(pin);
+ return err;
+}
+
+static int spi_jz4740_gpio_setup(struct spi_device *spi)
+{
+ unsigned nsel = get_nsel(spi);
+
+ dev_dbg(&spi->dev, "%s\n", __func__);
+ return get_gpio(NULL, nsel, dev_name(&spi->dev), 0);
+}
+
+static void spi_jz4740_gpio_cleanup(struct spi_device *spi)
+{
+ unsigned nsel = get_nsel(spi);
+
+ dev_dbg(&spi->dev, "%s\n", __func__);
+ gpio_free(nsel);
+}
+
+
+/* ----- Non-nSEL GPIO allocation and configuration ------------------------ */
+
+
+static void free_gpios(struct spi_jz4740_gpio *prv)
+{
+ const struct spi_gpio_platform_data *pdata = prv->pdata;
+
+ if (prv->mosi)
+ gpio_free(pdata->mosi);
+ if (prv->miso)
+ gpio_free(pdata->miso);
+ if (prv->sck)
+ gpio_free(pdata->sck);
+}
+
+
+static int setup_gpios(struct spi_jz4740_gpio *prv, const char *label,
+ uint16_t *flags)
+{
+ const struct spi_gpio_platform_data *pdata = prv->pdata;
+ int err;
+
+ if (pdata->mosi == -1) {
+ *flags |= SPI_MASTER_NO_TX;
+ } else {
+ err = get_gpio(prv, pdata->mosi, label, 0);
+ if (err)
+ return err;
+ prv->mosi = PIN_TO_MASK(pdata->mosi);
+ }
+
+ if (pdata->miso == -1) {
+ *flags |= SPI_MASTER_NO_RX;
+ } else {
+ err = get_gpio(prv, pdata->miso, label, -1);
+ if (err)
+ goto fail;
+ prv->miso = PIN_TO_MASK(pdata->miso);
+ }
+
+ err = get_gpio(prv, pdata->sck, label, 0);
+ if (err)
+ goto fail;
+ prv->sck = PIN_TO_MASK(pdata->sck);
+
+ return 0;
+
+fail:
+ free_gpios(prv);
+ return err;
+}
+
+
+/* ----- SPI master creation/removal --------------------------------------- */
+
+
+static int spi_jz4740_gpio_probe(struct platform_device *pdev)
+{
+ const struct spi_gpio_platform_data *pdata = pdev->dev.platform_data;
+ struct spi_master *master;
+ struct spi_jz4740_gpio *prv;
+ int err;
+
+ dev_dbg(prv->dev, "%s\n", __func__);
+ if (!pdata)
+ return -ENODEV;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(*prv));
+ if (!master)
+ return -ENOMEM;
+
+ prv = spi_master_get_devdata(master);
+ prv->dev = &pdev->dev;
+ prv->pdata = pdata;
+ platform_set_drvdata(pdev, spi_master_get(master));
+
+ err = setup_gpios(prv, dev_name(prv->dev), &master->flags);
+ if (err)
+ goto out;
+
+ master->mode_bits = 0; /* SPI_MODE_0 only */
+ master->bus_num = pdev->id;
+ master->num_chipselect = pdata->num_chipselect;
+ master->setup = spi_jz4740_gpio_setup;
+ master->cleanup = spi_jz4740_gpio_cleanup;
+ master->transfer_one_message = spi_jz4740_gpio_transfer_one;
+
+ /*
+ * We don't [devm_]request_mem_region here since we don't need
+ * exclusive access to port registers. Pin access conflicts have
+ * already been resolved by gpiolib.
+ */
+
+ prv->port_base = devm_ioremap(&pdev->dev, prv->port_addr, 0x100);
+ if (!prv->port_base) {
+ dev_err(prv->dev, "can't ioremap\n");
+ goto out_busy;
+ }
+
+ err = spi_register_master(master);
+ if (err) {
+ dev_err(prv->dev, "can't register master\n");
+ goto out_master;
+ }
+
+ return 0;
+
+out_busy:
+ err = -EBUSY;
+out_master:
+ free_gpios(prv);
+out:
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+
+ return err;
+}
+
+static int spi_jz4740_gpio_remove(struct platform_device *pdev)
+{
+ struct spi_master *master = platform_get_drvdata(pdev);
+ struct spi_jz4740_gpio *prv = spi_master_get_devdata(master);
+
+ spi_unregister_master(master);
+
+ free_gpios(prv);
+
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+
+ return 0;
+}
+
+static struct platform_driver spi_jz4740_gpio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = spi_jz4740_gpio_probe,
+ .remove = spi_jz4740_gpio_remove,
+};
+module_platform_driver(spi_jz4740_gpio_driver);
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_DESCRIPTION("Jz4740 GPIO SPI Driver");
+MODULE_AUTHOR("Werner Almesberger <werner at almesberger.net>");
+MODULE_LICENSE("GPL");
More information about the discussion
mailing list