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 <[email protected]>"); +MODULE_LICENSE("GPL"); _______________________________________________ Qi Hardware Discussion List Mail to list (members only): [email protected] Subscribe or Unsubscribe: http://lists.en.qi-hardware.com/mailman/listinfo/discussion

