[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


interactive