From: Thomas Chou <thomas@wytron.com.tw> To: David Brownell <dbrownell@users.sourceforge.net>, Grant Likely <grant.likely@secretlab.ca> Cc: linux-kernel@vger.kernel.org, nios2-dev@sopc.et.ntust.edu.tw, devicetree-discuss@lists.ozlabs.org, spi-devel-general@lists.sourceforge.net, Jonas Bonn <jonas@southpole.se>, Thomas Chou <thomas@wytron.com.tw> Subject: [PATCH v4] spi: add OpenCores tiny SPI driver Date: Thu, 3 Feb 2011 18:37:46 +0800 [thread overview] Message-ID: <1296729466-2936-1-git-send-email-thomas@wytron.com.tw> (raw) In-Reply-To: <1295925450-28550-1-git-send-email-thomas@wytron.com.tw> This patch adds support of OpenCores tiny SPI driver. http://opencores.org/project,tiny_spi Signed-off-by: Thomas Chou <thomas@wytron.com.tw> --- v2 minor cleanup, same as Grant suggest for spi_altera. v3 rename driver to spi_oc_tiny as Grant suggested. set version to tiny-spi-rtlsvn2 as Jonas suggested. v4 add dts binding doc. .../devicetree/bindings/spi/spi_oc_tiny.txt | 11 + drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi_oc_tiny.c | 396 ++++++++++++++++++++ include/linux/spi/spi_oc_tiny.h | 14 + 5 files changed, 428 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/spi_oc_tiny.txt create mode 100644 drivers/spi/spi_oc_tiny.c create mode 100644 include/linux/spi/spi_oc_tiny.h diff --git a/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt new file mode 100644 index 0000000..fba0f30 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt @@ -0,0 +1,11 @@ +OpenCores tiny SPI + +Required properties: +- compatible : should be "opencores,tiny-spi-rtlsvn2". +Optional properties: +- clock-frequency : input clock frequency to the core. +- baud-width: width, in bits, of the programmable divider used to scale + the input clock to SCLK. + +The clock-frequency and baud-width properties are needed only if the divider +is programmable. They are not needed if the divider is fixed. diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index bb233a9..fbb82ee 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -231,6 +231,12 @@ config SPI_FSL_ESPI From MPC8536, 85xx platform uses the controller, and all P10xx, P20xx, P30xx,P40xx, P50xx uses this controller. +config SPI_OC_TINY + tristate "OpenCores tiny SPI" + select SPI_BITBANG + help + This is the driver for OpenCores tiny SPI master controller. + config SPI_OMAP_UWIRE tristate "OMAP1 MicroWire" depends on ARCH_OMAP1 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 86d1b5f..c7e4c23 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SPI_IMX) += spi_imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o obj-$(CONFIG_SPI_PXA2XX_PCI) += pxa2xx_spi_pci.o +obj-$(CONFIG_SPI_OC_TINY) += spi_oc_tiny.o obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o obj-$(CONFIG_SPI_OMAP24XX) += omap2_mcspi.o obj-$(CONFIG_SPI_OMAP_100K) += omap_spi_100k.o diff --git a/drivers/spi/spi_oc_tiny.c b/drivers/spi/spi_oc_tiny.c new file mode 100644 index 0000000..1ea3223 --- /dev/null +++ b/drivers/spi/spi_oc_tiny.c @@ -0,0 +1,396 @@ +/* + * OpenCores tiny SPI master driver + * + * http://opencores.org/project,tiny_spi + * + * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw> + * + * Based on spi_s3c24xx.c, which is: + * Copyright (c) 2006 Ben Dooks + * Copyright (c) 2006 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * 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. + */ + +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> +#include <linux/spi/spi_oc_tiny.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/of.h> + +#define DRV_NAME "spi_oc_tiny" + +#define TINY_SPI_RXDATA 0 +#define TINY_SPI_TXDATA 4 +#define TINY_SPI_STATUS 8 +#define TINY_SPI_CONTROL 12 +#define TINY_SPI_BAUD 16 + +#define TINY_SPI_STATUS_TXE 0x1 +#define TINY_SPI_STATUS_TXR 0x2 + +struct tiny_spi { + /* bitbang has to be first */ + struct spi_bitbang bitbang; + struct completion done; + + void __iomem *base; + int irq; + unsigned int freq; + unsigned int baudwidth; + unsigned int baud; + unsigned int speed_hz; + unsigned int mode; + unsigned int len; + unsigned int txc, rxc; + const u8 *txp; + u8 *rxp; +}; + +static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev) +{ + return spi_master_get_devdata(sdev->master); +} + +static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1; +} + +static void tiny_spi_chipselect(struct spi_device *spi, int is_active) +{ + gpio_set_value(spi->chip_select, + (spi->mode & SPI_CS_HIGH) ? is_active : !is_active); +} + +static int tiny_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + unsigned int baud = hw->baud; + + if (t) { + if (t->speed_hz && t->speed_hz != hw->speed_hz) + baud = tiny_spi_baud(spi, t->speed_hz); + } + writel(baud, hw->base + TINY_SPI_BAUD); + writel(hw->mode, hw->base + TINY_SPI_CONTROL); + return 0; +} + +static int tiny_spi_setup(struct spi_device *spi) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + if (spi->max_speed_hz != hw->speed_hz) { + hw->speed_hz = spi->max_speed_hz; + hw->baud = tiny_spi_baud(spi, hw->speed_hz); + } + hw->mode = spi->mode & (SPI_CPOL | SPI_CPHA); + return 0; +} + +static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + const u8 *txp = t->tx_buf; + u8 *rxp = t->rx_buf; + unsigned int i; + + if (hw->irq >= 0) { + /* use intrrupt driven data transfer */ + hw->len = t->len; + hw->txp = t->tx_buf; + hw->rxp = t->rx_buf; + hw->txc = 0; + hw->rxc = 0; + + /* send the first byte */ + if (t->len > 1) { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS); + } else { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS); + } + + wait_for_completion(&hw->done); + } else if (txp && rxp) { + /* we need to tighten the transfer loop */ + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 rx, tx = *txp++; + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + rx = readb(hw->base + TINY_SPI_TXDATA); + writeb(tx, hw->base + TINY_SPI_TXDATA); + *rxp++ = rx; + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_TXDATA); + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_RXDATA); + } else if (rxp) { + writeb(0, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(0, + hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 rx; + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + rx = readb(hw->base + TINY_SPI_TXDATA); + writeb(0, + hw->base + TINY_SPI_TXDATA); + *rxp++ = rx; + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_TXDATA); + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_RXDATA); + } else if (txp) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 tx = *txp++; + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + writeb(tx, hw->base + TINY_SPI_TXDATA); + } + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + } else { + writeb(0, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(0, + hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + writeb(0, + hw->base + TINY_SPI_TXDATA); + } + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + } + return t->len; +} + +static irqreturn_t tiny_spi_irq(int irq, void *dev) +{ + struct tiny_spi *hw = dev; + + writeb(0, hw->base + TINY_SPI_STATUS); + if (hw->rxc + 1 == hw->len) { + if (hw->rxp) + *hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA); + hw->rxc++; + complete(&hw->done); + } else { + if (hw->rxp) + *hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA); + hw->rxc++; + if (hw->txc < hw->len) { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXR, + hw->base + TINY_SPI_STATUS); + } else { + writeb(TINY_SPI_STATUS_TXE, + hw->base + TINY_SPI_STATUS); + } + } + return IRQ_HANDLED; +} + +static int __devinit tiny_spi_probe(struct platform_device *pdev) +{ + struct tiny_spi_platform_data *platp = pdev->dev.platform_data; + struct tiny_spi *hw; + struct spi_master *master; + struct resource *res; + int err = 0; + + master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi)); + if (master == NULL) { + dev_err(&pdev->dev, "No memory for spi_master\n"); + err = -ENOMEM; + goto err_no_mem; + } + + /* setup the master state. */ + master->bus_num = pdev->id; + master->num_chipselect = 255; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->setup = tiny_spi_setup; + + hw = spi_master_get_devdata(master); + platform_set_drvdata(pdev, hw); + + /* setup the state for the bitbang driver */ + hw->bitbang.master = spi_master_get(master); + if (hw->bitbang.master == NULL) { + dev_err(&pdev->dev, "Cannot get device\n"); + err = -ENODEV; + goto err_no_dev; + } + hw->bitbang.setup_transfer = tiny_spi_setup_transfer; + hw->bitbang.chipselect = tiny_spi_chipselect; + hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs; + + /* find and map our resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); + err = -ENOENT; + goto err_no_iores; + } + hw->base = ioremap(res->start, (res->end - res->start) + 1); + if (hw->base == 0) { + dev_err(&pdev->dev, "Cannot map IO\n"); + err = -ENXIO; + goto err_no_iomap; + } + /* irq is optional */ + hw->irq = platform_get_irq(pdev, 0); + if (hw->irq >= 0) { + init_completion(&hw->done); + err = request_irq(hw->irq, tiny_spi_irq, 0, pdev->name, hw); + if (err) { + dev_err(&pdev->dev, "Cannot claim IRQ\n"); + goto err_no_irq; + } + } + /* find platform data */ + if (platp) { + hw->freq = platp->freq; + hw->baudwidth = platp->baudwidth; + } else { +#ifdef CONFIG_OF + const __be32 *val; + + hw->bitbang.master->dev.of_node = pdev->dev.of_node; + val = of_get_property(pdev->dev.of_node, + "clock-frequency", NULL); + if (val) + hw->freq = be32_to_cpup(val); + val = of_get_property(pdev->dev.of_node, "baud-width", NULL); + if (val) + hw->baudwidth = be32_to_cpup(val); +#endif + } + + /* register our spi controller */ + err = spi_bitbang_start(&hw->bitbang); + if (err) { + dev_err(&pdev->dev, "Failed to register SPI master\n"); + goto err_register; + } + dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq); + + return 0; + +err_register: + if (hw->irq >= 0) + free_irq(hw->irq, hw); +err_no_irq: + iounmap(hw->base); +err_no_iomap: +err_no_iores: + spi_master_put(master); +err_no_mem: +err_no_dev: + return err; +} + +static int __devexit tiny_spi_remove(struct platform_device *dev) +{ + struct tiny_spi *hw = platform_get_drvdata(dev); + struct spi_master *master = hw->bitbang.master; + + spi_bitbang_stop(&hw->bitbang); + + if (hw->irq >= 0) + free_irq(hw->irq, hw); + iounmap(hw->base); + + platform_set_drvdata(dev, NULL); + spi_master_put(master); + return 0; +} + +static const struct of_device_id tiny_spi_match[] = { + { .compatible = "opencores,tiny-spi-rtlsvn2", }, + {}, +} +MODULE_DEVICE_TABLE(of, tiny_spi_match); + +static struct platform_driver tiny_spidrv = { + .remove = __devexit_p(tiny_spi_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = tiny_spi_match, + }, +}; + +static int __init tiny_spi_init(void) +{ + return platform_driver_probe(&tiny_spidrv, tiny_spi_probe); +} +module_init(tiny_spi_init); + +static void __exit tiny_spi_exit(void) +{ + platform_driver_unregister(&tiny_spidrv); +} +module_exit(tiny_spi_exit); + +MODULE_DESCRIPTION("OpenCores tiny SPI driver"); +MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/include/linux/spi/spi_oc_tiny.h b/include/linux/spi/spi_oc_tiny.h new file mode 100644 index 0000000..5080e1c --- /dev/null +++ b/include/linux/spi/spi_oc_tiny.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_SPI_SPI_OC_TINY_H +#define _LINUX_SPI_SPI_OC_TINY_H + +/* + * struct tiny_spi_platform_data - platform data of the OpenCores tiny SPI + * @freq: input clock freq to the core. + * @baudwidth: baud rate divider width of the core. + */ +struct tiny_spi_platform_data { + uint freq; + uint baudwidth; +}; + +#endif /* _LINUX_SPI_SPI_OC_TINY_H */ -- 1.7.3.5
WARNING: multiple messages have this Message-ID (diff)
From: Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> To: David Brownell <dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org>, Grant Likely <grant.likely-s3s/WqlpOiPyB63q8FvJNQ@public.gmane.org> Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH@public.gmane.org, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org, linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org Subject: [PATCH v4] spi: add OpenCores tiny SPI driver Date: Thu, 3 Feb 2011 18:37:46 +0800 [thread overview] Message-ID: <1296729466-2936-1-git-send-email-thomas@wytron.com.tw> (raw) In-Reply-To: <1295925450-28550-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> This patch adds support of OpenCores tiny SPI driver. http://opencores.org/project,tiny_spi Signed-off-by: Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> --- v2 minor cleanup, same as Grant suggest for spi_altera. v3 rename driver to spi_oc_tiny as Grant suggested. set version to tiny-spi-rtlsvn2 as Jonas suggested. v4 add dts binding doc. .../devicetree/bindings/spi/spi_oc_tiny.txt | 11 + drivers/spi/Kconfig | 6 + drivers/spi/Makefile | 1 + drivers/spi/spi_oc_tiny.c | 396 ++++++++++++++++++++ include/linux/spi/spi_oc_tiny.h | 14 + 5 files changed, 428 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/spi/spi_oc_tiny.txt create mode 100644 drivers/spi/spi_oc_tiny.c create mode 100644 include/linux/spi/spi_oc_tiny.h diff --git a/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt new file mode 100644 index 0000000..fba0f30 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt @@ -0,0 +1,11 @@ +OpenCores tiny SPI + +Required properties: +- compatible : should be "opencores,tiny-spi-rtlsvn2". +Optional properties: +- clock-frequency : input clock frequency to the core. +- baud-width: width, in bits, of the programmable divider used to scale + the input clock to SCLK. + +The clock-frequency and baud-width properties are needed only if the divider +is programmable. They are not needed if the divider is fixed. diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index bb233a9..fbb82ee 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -231,6 +231,12 @@ config SPI_FSL_ESPI From MPC8536, 85xx platform uses the controller, and all P10xx, P20xx, P30xx,P40xx, P50xx uses this controller. +config SPI_OC_TINY + tristate "OpenCores tiny SPI" + select SPI_BITBANG + help + This is the driver for OpenCores tiny SPI master controller. + config SPI_OMAP_UWIRE tristate "OMAP1 MicroWire" depends on ARCH_OMAP1 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 86d1b5f..c7e4c23 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_SPI_IMX) += spi_imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o obj-$(CONFIG_SPI_PXA2XX_PCI) += pxa2xx_spi_pci.o +obj-$(CONFIG_SPI_OC_TINY) += spi_oc_tiny.o obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o obj-$(CONFIG_SPI_OMAP24XX) += omap2_mcspi.o obj-$(CONFIG_SPI_OMAP_100K) += omap_spi_100k.o diff --git a/drivers/spi/spi_oc_tiny.c b/drivers/spi/spi_oc_tiny.c new file mode 100644 index 0000000..1ea3223 --- /dev/null +++ b/drivers/spi/spi_oc_tiny.c @@ -0,0 +1,396 @@ +/* + * OpenCores tiny SPI master driver + * + * http://opencores.org/project,tiny_spi + * + * Copyright (C) 2011 Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> + * + * Based on spi_s3c24xx.c, which is: + * Copyright (c) 2006 Ben Dooks + * Copyright (c) 2006 Simtec Electronics + * Ben Dooks <ben-Y5A6D6n0/KfQXOPxS62xeg@public.gmane.org> + * + * 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. + */ + +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> +#include <linux/spi/spi_oc_tiny.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/of.h> + +#define DRV_NAME "spi_oc_tiny" + +#define TINY_SPI_RXDATA 0 +#define TINY_SPI_TXDATA 4 +#define TINY_SPI_STATUS 8 +#define TINY_SPI_CONTROL 12 +#define TINY_SPI_BAUD 16 + +#define TINY_SPI_STATUS_TXE 0x1 +#define TINY_SPI_STATUS_TXR 0x2 + +struct tiny_spi { + /* bitbang has to be first */ + struct spi_bitbang bitbang; + struct completion done; + + void __iomem *base; + int irq; + unsigned int freq; + unsigned int baudwidth; + unsigned int baud; + unsigned int speed_hz; + unsigned int mode; + unsigned int len; + unsigned int txc, rxc; + const u8 *txp; + u8 *rxp; +}; + +static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev) +{ + return spi_master_get_devdata(sdev->master); +} + +static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1; +} + +static void tiny_spi_chipselect(struct spi_device *spi, int is_active) +{ + gpio_set_value(spi->chip_select, + (spi->mode & SPI_CS_HIGH) ? is_active : !is_active); +} + +static int tiny_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + unsigned int baud = hw->baud; + + if (t) { + if (t->speed_hz && t->speed_hz != hw->speed_hz) + baud = tiny_spi_baud(spi, t->speed_hz); + } + writel(baud, hw->base + TINY_SPI_BAUD); + writel(hw->mode, hw->base + TINY_SPI_CONTROL); + return 0; +} + +static int tiny_spi_setup(struct spi_device *spi) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + if (spi->max_speed_hz != hw->speed_hz) { + hw->speed_hz = spi->max_speed_hz; + hw->baud = tiny_spi_baud(spi, hw->speed_hz); + } + hw->mode = spi->mode & (SPI_CPOL | SPI_CPHA); + return 0; +} + +static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + const u8 *txp = t->tx_buf; + u8 *rxp = t->rx_buf; + unsigned int i; + + if (hw->irq >= 0) { + /* use intrrupt driven data transfer */ + hw->len = t->len; + hw->txp = t->tx_buf; + hw->rxp = t->rx_buf; + hw->txc = 0; + hw->rxc = 0; + + /* send the first byte */ + if (t->len > 1) { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS); + } else { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS); + } + + wait_for_completion(&hw->done); + } else if (txp && rxp) { + /* we need to tighten the transfer loop */ + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 rx, tx = *txp++; + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + rx = readb(hw->base + TINY_SPI_TXDATA); + writeb(tx, hw->base + TINY_SPI_TXDATA); + *rxp++ = rx; + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_TXDATA); + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_RXDATA); + } else if (rxp) { + writeb(0, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(0, + hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 rx; + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + rx = readb(hw->base + TINY_SPI_TXDATA); + writeb(0, + hw->base + TINY_SPI_TXDATA); + *rxp++ = rx; + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_TXDATA); + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + *rxp++ = readb(hw->base + TINY_SPI_RXDATA); + } else if (txp) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 tx = *txp++; + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + writeb(tx, hw->base + TINY_SPI_TXDATA); + } + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + } else { + writeb(0, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(0, + hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); + writeb(0, + hw->base + TINY_SPI_TXDATA); + } + } + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); + } + return t->len; +} + +static irqreturn_t tiny_spi_irq(int irq, void *dev) +{ + struct tiny_spi *hw = dev; + + writeb(0, hw->base + TINY_SPI_STATUS); + if (hw->rxc + 1 == hw->len) { + if (hw->rxp) + *hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA); + hw->rxc++; + complete(&hw->done); + } else { + if (hw->rxp) + *hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA); + hw->rxc++; + if (hw->txc < hw->len) { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXR, + hw->base + TINY_SPI_STATUS); + } else { + writeb(TINY_SPI_STATUS_TXE, + hw->base + TINY_SPI_STATUS); + } + } + return IRQ_HANDLED; +} + +static int __devinit tiny_spi_probe(struct platform_device *pdev) +{ + struct tiny_spi_platform_data *platp = pdev->dev.platform_data; + struct tiny_spi *hw; + struct spi_master *master; + struct resource *res; + int err = 0; + + master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi)); + if (master == NULL) { + dev_err(&pdev->dev, "No memory for spi_master\n"); + err = -ENOMEM; + goto err_no_mem; + } + + /* setup the master state. */ + master->bus_num = pdev->id; + master->num_chipselect = 255; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->setup = tiny_spi_setup; + + hw = spi_master_get_devdata(master); + platform_set_drvdata(pdev, hw); + + /* setup the state for the bitbang driver */ + hw->bitbang.master = spi_master_get(master); + if (hw->bitbang.master == NULL) { + dev_err(&pdev->dev, "Cannot get device\n"); + err = -ENODEV; + goto err_no_dev; + } + hw->bitbang.setup_transfer = tiny_spi_setup_transfer; + hw->bitbang.chipselect = tiny_spi_chipselect; + hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs; + + /* find and map our resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); + err = -ENOENT; + goto err_no_iores; + } + hw->base = ioremap(res->start, (res->end - res->start) + 1); + if (hw->base == 0) { + dev_err(&pdev->dev, "Cannot map IO\n"); + err = -ENXIO; + goto err_no_iomap; + } + /* irq is optional */ + hw->irq = platform_get_irq(pdev, 0); + if (hw->irq >= 0) { + init_completion(&hw->done); + err = request_irq(hw->irq, tiny_spi_irq, 0, pdev->name, hw); + if (err) { + dev_err(&pdev->dev, "Cannot claim IRQ\n"); + goto err_no_irq; + } + } + /* find platform data */ + if (platp) { + hw->freq = platp->freq; + hw->baudwidth = platp->baudwidth; + } else { +#ifdef CONFIG_OF + const __be32 *val; + + hw->bitbang.master->dev.of_node = pdev->dev.of_node; + val = of_get_property(pdev->dev.of_node, + "clock-frequency", NULL); + if (val) + hw->freq = be32_to_cpup(val); + val = of_get_property(pdev->dev.of_node, "baud-width", NULL); + if (val) + hw->baudwidth = be32_to_cpup(val); +#endif + } + + /* register our spi controller */ + err = spi_bitbang_start(&hw->bitbang); + if (err) { + dev_err(&pdev->dev, "Failed to register SPI master\n"); + goto err_register; + } + dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq); + + return 0; + +err_register: + if (hw->irq >= 0) + free_irq(hw->irq, hw); +err_no_irq: + iounmap(hw->base); +err_no_iomap: +err_no_iores: + spi_master_put(master); +err_no_mem: +err_no_dev: + return err; +} + +static int __devexit tiny_spi_remove(struct platform_device *dev) +{ + struct tiny_spi *hw = platform_get_drvdata(dev); + struct spi_master *master = hw->bitbang.master; + + spi_bitbang_stop(&hw->bitbang); + + if (hw->irq >= 0) + free_irq(hw->irq, hw); + iounmap(hw->base); + + platform_set_drvdata(dev, NULL); + spi_master_put(master); + return 0; +} + +static const struct of_device_id tiny_spi_match[] = { + { .compatible = "opencores,tiny-spi-rtlsvn2", }, + {}, +} +MODULE_DEVICE_TABLE(of, tiny_spi_match); + +static struct platform_driver tiny_spidrv = { + .remove = __devexit_p(tiny_spi_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = tiny_spi_match, + }, +}; + +static int __init tiny_spi_init(void) +{ + return platform_driver_probe(&tiny_spidrv, tiny_spi_probe); +} +module_init(tiny_spi_init); + +static void __exit tiny_spi_exit(void) +{ + platform_driver_unregister(&tiny_spidrv); +} +module_exit(tiny_spi_exit); + +MODULE_DESCRIPTION("OpenCores tiny SPI driver"); +MODULE_AUTHOR("Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/include/linux/spi/spi_oc_tiny.h b/include/linux/spi/spi_oc_tiny.h new file mode 100644 index 0000000..5080e1c --- /dev/null +++ b/include/linux/spi/spi_oc_tiny.h @@ -0,0 +1,14 @@ +#ifndef _LINUX_SPI_SPI_OC_TINY_H +#define _LINUX_SPI_SPI_OC_TINY_H + +/* + * struct tiny_spi_platform_data - platform data of the OpenCores tiny SPI + * @freq: input clock freq to the core. + * @baudwidth: baud rate divider width of the core. + */ +struct tiny_spi_platform_data { + uint freq; + uint baudwidth; +}; + +#endif /* _LINUX_SPI_SPI_OC_TINY_H */ -- 1.7.3.5
next prev parent reply other threads:[~2011-02-03 10:37 UTC|newest] Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top [not found] <1294645208-6322-1-git-send-email-thomas@wytron.com.tw> 2011-01-12 2:41 ` [PATCH v2] spi: add OpenCores tiny SPI driver Thomas Chou 2011-01-12 2:41 ` Thomas Chou 2011-01-17 7:32 ` Thomas Chou 2011-01-17 7:32 ` Thomas Chou 2011-01-20 16:54 ` Grant Likely 2011-01-20 16:54 ` Grant Likely 2011-01-20 21:36 ` Thomas Chou 2011-01-21 12:27 ` Jonas Bonn 2011-01-21 12:27 ` Jonas Bonn 2011-01-24 1:19 ` Thomas Chou 2011-01-24 1:19 ` Thomas Chou 2011-01-24 10:59 ` Jonas Bonn 2011-01-24 10:59 ` Jonas Bonn 2011-01-24 14:50 ` Thomas Chou 2011-01-24 14:50 ` Thomas Chou 2011-01-24 15:50 ` Grant Likely 2011-01-24 15:50 ` Grant Likely 2011-01-25 3:17 ` [PATCH v3] " Thomas Chou 2011-01-25 3:17 ` Thomas Chou 2011-02-03 10:37 ` Thomas Chou [this message] 2011-02-03 10:37 ` [PATCH v4] " Thomas Chou 2011-02-03 11:03 ` Wolfram Sang 2011-02-03 15:20 ` Dirk Brandewie
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=1296729466-2936-1-git-send-email-thomas@wytron.com.tw \ --to=thomas@wytron.com.tw \ --cc=dbrownell@users.sourceforge.net \ --cc=devicetree-discuss@lists.ozlabs.org \ --cc=grant.likely@secretlab.ca \ --cc=jonas@southpole.se \ --cc=linux-kernel@vger.kernel.org \ --cc=nios2-dev@sopc.et.ntust.edu.tw \ --cc=spi-devel-general@lists.sourceforge.net \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.