2011-02-03 10:01:36

by Thomas Chou

[permalink] [raw]
Subject: [PATCH v6] spi: New driver for Altera SPI

This patch adds a new SPI driver to support the Altera SOPC Builder
SPI component. It uses the bitbanging library.

Signed-off-by: Thomas Chou <[email protected]>
---
v2 add devicetree support
v3 remove platform header, as Grant suggested.
no irq resource means polling.
v4 minor cleanup, as Grant suggested.
v5 add compat version.
v6 change compatible vendor to uppercase, ALTR.
add dts binding doc.

.../devicetree/bindings/spi/spi_altera.txt | 4 +
drivers/spi/Kconfig | 6 +
drivers/spi/Makefile | 1 +
drivers/spi/spi_altera.c | 360 ++++++++++++++++++++
4 files changed, 371 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/spi_altera.txt
create mode 100644 drivers/spi/spi_altera.c

diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt
new file mode 100644
index 0000000..dda3759
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_altera.txt
@@ -0,0 +1,4 @@
+Altera SPI
+
+Required properties:
+- compatible : should be "ALTR,spi-1.0".
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bb233a9..e791579 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -53,6 +53,12 @@ if SPI_MASTER

comment "SPI Master Controller Drivers"

+config SPI_ALTERA
+ tristate "Altera SPI Controller"
+ select SPI_BITBANG
+ help
+ This is the driver for the Altera SPI Controller.
+
config SPI_ATH79
tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
depends on ATH79 && GENERIC_GPIO
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 86d1b5f..4d2e35a 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
obj-$(CONFIG_SPI_MASTER) += spi.o

# SPI master controller drivers (bus)
+obj-$(CONFIG_SPI_ALTERA) += spi_altera.o
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
obj-$(CONFIG_SPI_ATH79) += ath79_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..1056ded
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,360 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 Thomas Chou <[email protected]>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ * Ben Dooks <[email protected]>
+ *
+ * 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/workqueue.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/io.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_altera"
+
+#define ALTERA_SPI_RXDATA 0
+#define ALTERA_SPI_TXDATA 4
+#define ALTERA_SPI_STATUS 8
+#define ALTERA_SPI_CONTROL 12
+#define ALTERA_SPI_SLAVE_SEL 20
+
+#define ALTERA_SPI_STATUS_ROE_MSK 0x8
+#define ALTERA_SPI_STATUS_TOE_MSK 0x10
+#define ALTERA_SPI_STATUS_TMT_MSK 0x20
+#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
+#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
+#define ALTERA_SPI_STATUS_E_MSK 0x100
+
+#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
+#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
+#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
+#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
+#define ALTERA_SPI_CONTROL_IE_MSK 0x100
+#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
+
+struct altera_spi {
+ /* bitbang has to be first */
+ struct spi_bitbang bitbang;
+ struct completion done;
+
+ void __iomem *base;
+ int irq;
+ int len;
+ int count;
+ int bytes_per_word;
+ unsigned long imr;
+
+ /* data buffers */
+ const unsigned char *tx;
+ unsigned char *rx;
+};
+
+static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
+{
+ return spi_master_get_devdata(sdev->master);
+}
+
+static void altera_spi_chipsel(struct spi_device *spi, int value)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ if (spi->mode & SPI_CS_HIGH) {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
+ break;
+ }
+ } else {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+ }
+ }
+}
+
+static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ return 0;
+}
+
+static int altera_spi_setup(struct spi_device *spi)
+{
+ return 0;
+}
+
+static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
+{
+ if (hw->tx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ return hw->tx[count];
+ case 2:
+ return (hw->tx[count * 2]
+ | (hw->tx[count * 2 + 1] << 8));
+ }
+ }
+ return 0;
+}
+
+static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ hw->tx = t->tx_buf;
+ hw->rx = t->rx_buf;
+ hw->count = 0;
+ hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
+ hw->len = t->len / hw->bytes_per_word;
+
+ if (hw->irq >= 0) {
+ /* enable receive interrupt */
+ hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ wait_for_completion(&hw->done);
+ /* disable receive interrupt */
+ hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ } else {
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ while (1) {
+ unsigned int rxd;
+
+ while (!(readl(hw->base + ALTERA_SPI_STATUS) &
+ ALTERA_SPI_STATUS_RRDY_MSK))
+ cpu_relax();
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count),
+ hw->base + ALTERA_SPI_TXDATA);
+ else
+ break;
+ }
+
+ }
+
+ return hw->count * hw->bytes_per_word;
+}
+
+static irqreturn_t altera_spi_irq(int irq, void *dev)
+{
+ struct altera_spi *hw = dev;
+ unsigned int rxd;
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
+ else
+ complete(&hw->done);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit altera_spi_probe(struct platform_device *pdev)
+{
+ struct altera_spi_platform_data *platp = pdev->dev.platform_data;
+ struct altera_spi *hw;
+ struct spi_master *master;
+ struct resource *res;
+ int err = 0;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct altera_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 = 16;
+ master->mode_bits = SPI_CS_HIGH;
+ master->setup = altera_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 = altera_spi_setupxfer;
+ hw->bitbang.chipselect = altera_spi_chipsel;
+ hw->bitbang.txrx_bufs = altera_spi_txrx;
+
+ /* 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;
+ }
+ /* program defaults into the registers */
+ hw->imr = 0; /* disable spi interrupts */
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
+ if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
+ readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
+ /* irq is optional */
+ hw->irq = platform_get_irq(pdev, 0);
+ if (hw->irq >= 0) {
+ init_completion(&hw->done);
+ err = request_irq(hw->irq, altera_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->bitbang.master->dev.of_node = pdev->dev.of_node;
+
+ /* 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:
+err_no_dev:
+ spi_master_put(master);
+err_no_mem:
+ return err;
+}
+
+static int __devexit altera_spi_remove(struct platform_device *dev)
+{
+ struct altera_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 altera_spi_match[] = {
+ { .compatible = "ALTR,spi-1.0", },
+ {},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+
+static struct platform_driver altera_spidrv = {
+ .remove = __devexit_p(altera_spi_remove),
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = NULL,
+ .of_match_table = altera_spi_match,
+ },
+};
+
+static int __init altera_spi_init(void)
+{
+ return platform_driver_probe(&altera_spidrv, altera_spi_probe);
+}
+module_init(altera_spi_init);
+
+static void __exit altera_spi_exit(void)
+{
+ platform_driver_unregister(&altera_spidrv);
+}
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
--
1.7.3.5


2011-02-03 17:34:37

by Grant Likely

[permalink] [raw]
Subject: Re: [PATCH v6] spi: New driver for Altera SPI

On Thu, Feb 03, 2011 at 06:02:06PM +0800, Thomas Chou wrote:
> This patch adds a new SPI driver to support the Altera SOPC Builder
> SPI component. It uses the bitbanging library.
>
> Signed-off-by: Thomas Chou <[email protected]>
> ---
> v2 add devicetree support
> v3 remove platform header, as Grant suggested.
> no irq resource means polling.
> v4 minor cleanup, as Grant suggested.
> v5 add compat version.
> v6 change compatible vendor to uppercase, ALTR.
> add dts binding doc.

Hi Thomas, comments below, but looking pretty close.

>
> .../devicetree/bindings/spi/spi_altera.txt | 4 +
> drivers/spi/Kconfig | 6 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi_altera.c | 360 ++++++++++++++++++++
> 4 files changed, 371 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/spi/spi_altera.txt
> create mode 100644 drivers/spi/spi_altera.c
>
> diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt
> new file mode 100644
> index 0000000..dda3759
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/spi/spi_altera.txt
> @@ -0,0 +1,4 @@
> +Altera SPI
> +
> +Required properties:
> +- compatible : should be "ALTR,spi-1.0".
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index bb233a9..e791579 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -53,6 +53,12 @@ if SPI_MASTER
>
> comment "SPI Master Controller Drivers"
>
> +config SPI_ALTERA
> + tristate "Altera SPI Controller"
> + select SPI_BITBANG

Will this compile on all architectures? Will it break allyesconfig
on anything other than nios?

> + help
> + This is the driver for the Altera SPI Controller.
> +
> config SPI_ATH79
> tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
> depends on ATH79 && GENERIC_GPIO
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 86d1b5f..4d2e35a 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
> obj-$(CONFIG_SPI_MASTER) += spi.o
>
> # SPI master controller drivers (bus)
> +obj-$(CONFIG_SPI_ALTERA) += spi_altera.o
> obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
> obj-$(CONFIG_SPI_ATH79) += ath79_spi.o
> obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
> diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
> new file mode 100644
> index 0000000..1056ded
> --- /dev/null
> +++ b/drivers/spi/spi_altera.c
> @@ -0,0 +1,360 @@
> +/*
> + * Altera SPI driver
> + *
> + * Copyright (C) 2008 Thomas Chou <[email protected]>
> + *
> + * Based on spi_s3c24xx.c, which is:
> + * Copyright (c) 2006 Ben Dooks
> + * Copyright (c) 2006 Simtec Electronics
> + * Ben Dooks <[email protected]>
> + *
> + * 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/workqueue.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/io.h>
> +#include <linux/of.h>
> +
> +#define DRV_NAME "spi_altera"
> +
> +#define ALTERA_SPI_RXDATA 0
> +#define ALTERA_SPI_TXDATA 4
> +#define ALTERA_SPI_STATUS 8
> +#define ALTERA_SPI_CONTROL 12
> +#define ALTERA_SPI_SLAVE_SEL 20
> +
> +#define ALTERA_SPI_STATUS_ROE_MSK 0x8
> +#define ALTERA_SPI_STATUS_TOE_MSK 0x10
> +#define ALTERA_SPI_STATUS_TMT_MSK 0x20
> +#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
> +#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
> +#define ALTERA_SPI_STATUS_E_MSK 0x100
> +
> +#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
> +#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
> +#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
> +#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
> +#define ALTERA_SPI_CONTROL_IE_MSK 0x100
> +#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
> +
> +struct altera_spi {
> + /* bitbang has to be first */
> + struct spi_bitbang bitbang;
> + struct completion done;
> +
> + void __iomem *base;
> + int irq;
> + int len;
> + int count;
> + int bytes_per_word;
> + unsigned long imr;
> +
> + /* data buffers */
> + const unsigned char *tx;
> + unsigned char *rx;
> +};
> +
> +static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
> +{
> + return spi_master_get_devdata(sdev->master);
> +}
> +
> +static void altera_spi_chipsel(struct spi_device *spi, int value)
> +{
> + struct altera_spi *hw = altera_spi_to_hw(spi);
> +
> + if (spi->mode & SPI_CS_HIGH) {
> + switch (value) {
> + case BITBANG_CS_INACTIVE:
> + writel(1 << spi->chip_select,
> + hw->base + ALTERA_SPI_SLAVE_SEL);
> + hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + break;
> +
> + case BITBANG_CS_ACTIVE:
> + hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
> + break;
> + }
> + } else {
> + switch (value) {
> + case BITBANG_CS_INACTIVE:
> + hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + break;
> +
> + case BITBANG_CS_ACTIVE:
> + writel(1 << spi->chip_select,
> + hw->base + ALTERA_SPI_SLAVE_SEL);
> + hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + break;
> + }
> + }
> +}
> +
> +static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
> +{
> + return 0;
> +}
> +
> +static int altera_spi_setup(struct spi_device *spi)
> +{
> + return 0;
> +}
> +
> +static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
> +{
> + if (hw->tx) {
> + switch (hw->bytes_per_word) {
> + case 1:
> + return hw->tx[count];
> + case 2:
> + return (hw->tx[count * 2]
> + | (hw->tx[count * 2 + 1] << 8));
> + }
> + }
> + return 0;
> +}
> +
> +static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
> +{
> + struct altera_spi *hw = altera_spi_to_hw(spi);
> +
> + hw->tx = t->tx_buf;
> + hw->rx = t->rx_buf;
> + hw->count = 0;
> + hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
> + hw->len = t->len / hw->bytes_per_word;
> +
> + if (hw->irq >= 0) {
> + /* enable receive interrupt */
> + hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> +
> + /* send the first byte */
> + writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
> +
> + wait_for_completion(&hw->done);
> + /* disable receive interrupt */
> + hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + } else {
> + /* send the first byte */
> + writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
> +
> + while (1) {
> + unsigned int rxd;
> +
> + while (!(readl(hw->base + ALTERA_SPI_STATUS) &
> + ALTERA_SPI_STATUS_RRDY_MSK))
> + cpu_relax();
> +
> + rxd = readl(hw->base + ALTERA_SPI_RXDATA);
> + if (hw->rx) {
> + switch (hw->bytes_per_word) {
> + case 1:
> + hw->rx[hw->count] = rxd;
> + break;
> + case 2:
> + hw->rx[hw->count * 2] = rxd;
> + hw->rx[hw->count * 2 + 1] = rxd >> 8;
> + break;
> + }
> + }
> +
> + hw->count++;
> +
> + if (hw->count < hw->len)
> + writel(hw_txbyte(hw, hw->count),
> + hw->base + ALTERA_SPI_TXDATA);
> + else
> + break;
> + }
> +
> + }
> +
> + return hw->count * hw->bytes_per_word;
> +}
> +
> +static irqreturn_t altera_spi_irq(int irq, void *dev)
> +{
> + struct altera_spi *hw = dev;
> + unsigned int rxd;
> +
> + rxd = readl(hw->base + ALTERA_SPI_RXDATA);
> + if (hw->rx) {
> + switch (hw->bytes_per_word) {
> + case 1:
> + hw->rx[hw->count] = rxd;
> + break;
> + case 2:
> + hw->rx[hw->count * 2] = rxd;
> + hw->rx[hw->count * 2 + 1] = rxd >> 8;
> + break;
> + }
> + }
> +
> + hw->count++;
> +
> + if (hw->count < hw->len)
> + writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
> + else
> + complete(&hw->done);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int __devinit altera_spi_probe(struct platform_device *pdev)
> +{
> + struct altera_spi_platform_data *platp = pdev->dev.platform_data;
> + struct altera_spi *hw;
> + struct spi_master *master;
> + struct resource *res;
> + int err = 0;
> +
> + master = spi_alloc_master(&pdev->dev, sizeof(struct altera_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 = 16;
> + master->mode_bits = SPI_CS_HIGH;
> + master->setup = altera_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 = altera_spi_setupxfer;
> + hw->bitbang.chipselect = altera_spi_chipsel;
> + hw->bitbang.txrx_bufs = altera_spi_txrx;
> +
> + /* 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;
> + }
> + /* program defaults into the registers */
> + hw->imr = 0; /* disable spi interrupts */
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
> + if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
> + readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
> + /* irq is optional */
> + hw->irq = platform_get_irq(pdev, 0);
> + if (hw->irq >= 0) {
> + init_completion(&hw->done);
> + err = request_irq(hw->irq, altera_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->bitbang.master->dev.of_node = pdev->dev.of_node;
> +
> + /* 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:
> +err_no_dev:
> + spi_master_put(master);
> +err_no_mem:
> + return err;
> +}
> +
> +static int __devexit altera_spi_remove(struct platform_device *dev)
> +{
> + struct altera_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 altera_spi_match[] = {
> + { .compatible = "ALTR,spi-1.0", },
> + {},
> +}
> +MODULE_DEVICE_TABLE(of, altera_spi_match);
> +
> +static struct platform_driver altera_spidrv = {
> + .remove = __devexit_p(altera_spi_remove),
> + .driver = {
> + .name = DRV_NAME,
> + .owner = THIS_MODULE,
> + .pm = NULL,
> + .of_match_table = altera_spi_match,
> + },
> +};
> +
> +static int __init altera_spi_init(void)
> +{
> + return platform_driver_probe(&altera_spidrv, altera_spi_probe);

platform_driver_register() please, and put the altera_spi_probe()
routine into the driver structure.

> +}
> +module_init(altera_spi_init);
> +
> +static void __exit altera_spi_exit(void)
> +{
> + platform_driver_unregister(&altera_spidrv);
> +}
> +module_exit(altera_spi_exit);
> +
> +MODULE_DESCRIPTION("Altera SPI driver");
> +MODULE_AUTHOR("Thomas Chou <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRV_NAME);
> --
> 1.7.3.5
>

2011-02-05 13:07:49

by Thomas Chou

[permalink] [raw]
Subject: Re: [PATCH v6] spi: New driver for Altera SPI

On 02/04/2011 01:34 AM, Grant Likely wrote:
> Hi Thomas, comments below, but looking pretty close.

Thanks a lot.

>> +config SPI_ALTERA
>> + tristate "Altera SPI Controller"
>> + select SPI_BITBANG
>
> Will this compile on all architectures? Will it break allyesconfig
> on anything other than nios?

Yes, it compile on other architectures. I will add 'default n' though.

There are soft-core arm, mips and coldfire running on altera fpga.
Others might use fpga as peripheral extender, too. So we shouldn't limit
it to nios2.

>> +static int __init altera_spi_init(void)
>> +{
>> + return platform_driver_probe(&altera_spidrv, altera_spi_probe);
>
> platform_driver_register() please, and put the altera_spi_probe()
> routine into the driver structure.

I will change it to register as you suggested.

- Thomas

2011-02-05 14:01:28

by Thomas Chou

[permalink] [raw]
Subject: [PATCH v7] spi: New driver for Altera SPI

This patch adds a new SPI driver to support the Altera SOPC Builder
SPI component. It uses the bitbanging library.

Signed-off-by: Thomas Chou <[email protected]>
---
v2 add devicetree support
v3 remove platform header, as Grant suggested.
no irq resource means polling.
v4 minor cleanup, as Grant suggested.
v5 add compat version.
v6 change compatible vendor to uppercase, ALTR.
add dts binding doc.
v7 use platform driver register.
use devm_ to help cleanup.

.../devicetree/bindings/spi/spi_altera.txt | 4 +
drivers/spi/Kconfig | 7 +
drivers/spi/Makefile | 1 +
drivers/spi/spi_altera.c | 335 ++++++++++++++++++++
4 files changed, 347 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/spi_altera.txt
create mode 100644 drivers/spi/spi_altera.c

diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt
new file mode 100644
index 0000000..dda3759
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_altera.txt
@@ -0,0 +1,4 @@
+Altera SPI
+
+Required properties:
+- compatible : should be "ALTR,spi-1.0".
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bb233a9..4d5d33c 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -53,6 +53,13 @@ if SPI_MASTER

comment "SPI Master Controller Drivers"

+config SPI_ALTERA
+ tristate "Altera SPI Controller"
+ default n
+ select SPI_BITBANG
+ help
+ This is the driver for the Altera SPI Controller.
+
config SPI_ATH79
tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
depends on ATH79 && GENERIC_GPIO
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 86d1b5f..4d2e35a 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
obj-$(CONFIG_SPI_MASTER) += spi.o

# SPI master controller drivers (bus)
+obj-$(CONFIG_SPI_ALTERA) += spi_altera.o
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
obj-$(CONFIG_SPI_ATH79) += ath79_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..e7f4835
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,335 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 Thomas Chou <[email protected]>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ * Ben Dooks <[email protected]>
+ *
+ * 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/interrupt.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_altera"
+
+#define ALTERA_SPI_RXDATA 0
+#define ALTERA_SPI_TXDATA 4
+#define ALTERA_SPI_STATUS 8
+#define ALTERA_SPI_CONTROL 12
+#define ALTERA_SPI_SLAVE_SEL 20
+
+#define ALTERA_SPI_STATUS_ROE_MSK 0x8
+#define ALTERA_SPI_STATUS_TOE_MSK 0x10
+#define ALTERA_SPI_STATUS_TMT_MSK 0x20
+#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
+#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
+#define ALTERA_SPI_STATUS_E_MSK 0x100
+
+#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
+#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
+#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
+#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
+#define ALTERA_SPI_CONTROL_IE_MSK 0x100
+#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
+
+struct altera_spi {
+ /* bitbang has to be first */
+ struct spi_bitbang bitbang;
+ struct completion done;
+
+ void __iomem *base;
+ int irq;
+ int len;
+ int count;
+ int bytes_per_word;
+ unsigned long imr;
+
+ /* data buffers */
+ const unsigned char *tx;
+ unsigned char *rx;
+};
+
+static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
+{
+ return spi_master_get_devdata(sdev->master);
+}
+
+static void altera_spi_chipsel(struct spi_device *spi, int value)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ if (spi->mode & SPI_CS_HIGH) {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
+ break;
+ }
+ } else {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+ }
+ }
+}
+
+static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ return 0;
+}
+
+static int altera_spi_setup(struct spi_device *spi)
+{
+ return 0;
+}
+
+static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
+{
+ if (hw->tx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ return hw->tx[count];
+ case 2:
+ return (hw->tx[count * 2]
+ | (hw->tx[count * 2 + 1] << 8));
+ }
+ }
+ return 0;
+}
+
+static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ hw->tx = t->tx_buf;
+ hw->rx = t->rx_buf;
+ hw->count = 0;
+ hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
+ hw->len = t->len / hw->bytes_per_word;
+
+ if (hw->irq >= 0) {
+ /* enable receive interrupt */
+ hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ wait_for_completion(&hw->done);
+ /* disable receive interrupt */
+ hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ } else {
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ while (1) {
+ unsigned int rxd;
+
+ while (!(readl(hw->base + ALTERA_SPI_STATUS) &
+ ALTERA_SPI_STATUS_RRDY_MSK))
+ cpu_relax();
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count),
+ hw->base + ALTERA_SPI_TXDATA);
+ else
+ break;
+ }
+
+ }
+
+ return hw->count * hw->bytes_per_word;
+}
+
+static irqreturn_t altera_spi_irq(int irq, void *dev)
+{
+ struct altera_spi *hw = dev;
+ unsigned int rxd;
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
+ else
+ complete(&hw->done);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit altera_spi_probe(struct platform_device *pdev)
+{
+ struct altera_spi_platform_data *platp = pdev->dev.platform_data;
+ struct altera_spi *hw;
+ struct spi_master *master;
+ struct resource *res;
+ int err = -ENODEV;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
+ if (!master)
+ return err;
+
+ /* setup the master state. */
+ master->bus_num = pdev->id;
+ master->num_chipselect = 16;
+ master->mode_bits = SPI_CS_HIGH;
+ master->setup = altera_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)
+ return err;
+ hw->bitbang.setup_transfer = altera_spi_setupxfer;
+ hw->bitbang.chipselect = altera_spi_chipsel;
+ hw->bitbang.txrx_bufs = altera_spi_txrx;
+
+ /* find and map our resources */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ goto exit_busy;
+ if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
+ pdev->name))
+ goto exit_busy;
+ hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (!hw->base)
+ goto exit_busy;
+ /* program defaults into the registers */
+ hw->imr = 0; /* disable spi interrupts */
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
+ if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
+ readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
+ /* irq is optional */
+ hw->irq = platform_get_irq(pdev, 0);
+ if (hw->irq >= 0) {
+ init_completion(&hw->done);
+ err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
+ pdev->name, hw);
+ if (err)
+ goto exit;
+ }
+ /* find platform data */
+ if (!platp)
+ hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+
+ /* register our spi controller */
+ err = spi_bitbang_start(&hw->bitbang);
+ if (err)
+ goto exit;
+ dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
+
+ return 0;
+
+exit_busy:
+ err = -EBUSY;
+exit:
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+ return err;
+}
+
+static int __devexit altera_spi_remove(struct platform_device *dev)
+{
+ struct altera_spi *hw = platform_get_drvdata(dev);
+ struct spi_master *master = hw->bitbang.master;
+
+ spi_bitbang_stop(&hw->bitbang);
+ platform_set_drvdata(dev, NULL);
+ spi_master_put(master);
+ return 0;
+}
+
+static const struct of_device_id altera_spi_match[] = {
+ { .compatible = "ALTR,spi-1.0", },
+ {},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+
+static struct platform_driver altera_spi_driver = {
+ .probe = altera_spi_probe,
+ .remove = __devexit_p(altera_spi_remove),
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = NULL,
+ .of_match_table = altera_spi_match,
+ },
+};
+
+static int __init altera_spi_init(void)
+{
+ return platform_driver_register(&altera_spi_driver);
+}
+module_init(altera_spi_init);
+
+static void __exit altera_spi_exit(void)
+{
+ platform_driver_unregister(&altera_spi_driver);
+}
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
--
1.7.3.5

2011-02-06 20:23:29

by Peter Korsgaard

[permalink] [raw]
Subject: Re: [PATCH v7] spi: New driver for Altera SPI

>>>>> "Thomas" == Thomas Chou <[email protected]> writes:

Hi,

Thomas> +++ b/drivers/spi/Kconfig
Thomas> @@ -53,6 +53,13 @@ if SPI_MASTER

Thomas> comment "SPI Master Controller Drivers"

Thomas> +config SPI_ALTERA
Thomas> + tristate "Altera SPI Controller"
Thomas> + default n

'n' is the default anyway, so no need for this line.

--
Bye, Peter Korsgaard

2011-02-08 02:40:46

by Thomas Chou

[permalink] [raw]
Subject: Re: [PATCH v7] spi: New driver for Altera SPI

On 02/07/2011 04:23 AM, Peter Korsgaard wrote:
>>>>>> "Thomas" == Thomas Chou<[email protected]> writes:
>
> Hi,
>
> Thomas> +++ b/drivers/spi/Kconfig
> Thomas> @@ -53,6 +53,13 @@ if SPI_MASTER
>
> Thomas> comment "SPI Master Controller Drivers"
>
> Thomas> +config SPI_ALTERA
> Thomas> + tristate "Altera SPI Controller"
> Thomas> + default n
>
> 'n' is the default anyway, so no need for this line.
>
Hi Peter,

Thanks. I will remove them.

- Thomas

2011-02-08 05:19:09

by Thomas Chou

[permalink] [raw]
Subject: [PATCH v8] spi: New driver for Altera SPI

This patch adds a new SPI driver to support the Altera SOPC Builder
SPI component. It uses the bitbanging library.

Signed-off-by: Thomas Chou <[email protected]>
---
v2 add devicetree support
v3 remove platform header, as Grant suggested.
no irq resource means polling.
v4 minor cleanup, as Grant suggested.
v5 add compat version.
v6 change compatible vendor to uppercase, ALTR.
add dts binding doc.
v7 use platform driver register.
use devm_ to help cleanup.
v8 remove default n in Kconfig.

.../devicetree/bindings/spi/spi_altera.txt | 4 +
drivers/spi/Kconfig | 6 +
drivers/spi/Makefile | 1 +
drivers/spi/spi_altera.c | 335 ++++++++++++++++++++
4 files changed, 346 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/spi_altera.txt
create mode 100644 drivers/spi/spi_altera.c

diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt
new file mode 100644
index 0000000..dda3759
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_altera.txt
@@ -0,0 +1,4 @@
+Altera SPI
+
+Required properties:
+- compatible : should be "ALTR,spi-1.0".
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bb233a9..e791579 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -53,6 +53,12 @@ if SPI_MASTER

comment "SPI Master Controller Drivers"

+config SPI_ALTERA
+ tristate "Altera SPI Controller"
+ select SPI_BITBANG
+ help
+ This is the driver for the Altera SPI Controller.
+
config SPI_ATH79
tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
depends on ATH79 && GENERIC_GPIO
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 86d1b5f..4d2e35a 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
obj-$(CONFIG_SPI_MASTER) += spi.o

# SPI master controller drivers (bus)
+obj-$(CONFIG_SPI_ALTERA) += spi_altera.o
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
obj-$(CONFIG_SPI_ATH79) += ath79_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..e7f4835
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,335 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 Thomas Chou <[email protected]>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ * Ben Dooks <[email protected]>
+ *
+ * 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/interrupt.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_altera"
+
+#define ALTERA_SPI_RXDATA 0
+#define ALTERA_SPI_TXDATA 4
+#define ALTERA_SPI_STATUS 8
+#define ALTERA_SPI_CONTROL 12
+#define ALTERA_SPI_SLAVE_SEL 20
+
+#define ALTERA_SPI_STATUS_ROE_MSK 0x8
+#define ALTERA_SPI_STATUS_TOE_MSK 0x10
+#define ALTERA_SPI_STATUS_TMT_MSK 0x20
+#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
+#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
+#define ALTERA_SPI_STATUS_E_MSK 0x100
+
+#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
+#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
+#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
+#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
+#define ALTERA_SPI_CONTROL_IE_MSK 0x100
+#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
+
+struct altera_spi {
+ /* bitbang has to be first */
+ struct spi_bitbang bitbang;
+ struct completion done;
+
+ void __iomem *base;
+ int irq;
+ int len;
+ int count;
+ int bytes_per_word;
+ unsigned long imr;
+
+ /* data buffers */
+ const unsigned char *tx;
+ unsigned char *rx;
+};
+
+static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
+{
+ return spi_master_get_devdata(sdev->master);
+}
+
+static void altera_spi_chipsel(struct spi_device *spi, int value)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ if (spi->mode & SPI_CS_HIGH) {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
+ break;
+ }
+ } else {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+ }
+ }
+}
+
+static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ return 0;
+}
+
+static int altera_spi_setup(struct spi_device *spi)
+{
+ return 0;
+}
+
+static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
+{
+ if (hw->tx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ return hw->tx[count];
+ case 2:
+ return (hw->tx[count * 2]
+ | (hw->tx[count * 2 + 1] << 8));
+ }
+ }
+ return 0;
+}
+
+static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ hw->tx = t->tx_buf;
+ hw->rx = t->rx_buf;
+ hw->count = 0;
+ hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
+ hw->len = t->len / hw->bytes_per_word;
+
+ if (hw->irq >= 0) {
+ /* enable receive interrupt */
+ hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ wait_for_completion(&hw->done);
+ /* disable receive interrupt */
+ hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ } else {
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ while (1) {
+ unsigned int rxd;
+
+ while (!(readl(hw->base + ALTERA_SPI_STATUS) &
+ ALTERA_SPI_STATUS_RRDY_MSK))
+ cpu_relax();
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count),
+ hw->base + ALTERA_SPI_TXDATA);
+ else
+ break;
+ }
+
+ }
+
+ return hw->count * hw->bytes_per_word;
+}
+
+static irqreturn_t altera_spi_irq(int irq, void *dev)
+{
+ struct altera_spi *hw = dev;
+ unsigned int rxd;
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
+ else
+ complete(&hw->done);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit altera_spi_probe(struct platform_device *pdev)
+{
+ struct altera_spi_platform_data *platp = pdev->dev.platform_data;
+ struct altera_spi *hw;
+ struct spi_master *master;
+ struct resource *res;
+ int err = -ENODEV;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
+ if (!master)
+ return err;
+
+ /* setup the master state. */
+ master->bus_num = pdev->id;
+ master->num_chipselect = 16;
+ master->mode_bits = SPI_CS_HIGH;
+ master->setup = altera_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)
+ return err;
+ hw->bitbang.setup_transfer = altera_spi_setupxfer;
+ hw->bitbang.chipselect = altera_spi_chipsel;
+ hw->bitbang.txrx_bufs = altera_spi_txrx;
+
+ /* find and map our resources */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ goto exit_busy;
+ if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
+ pdev->name))
+ goto exit_busy;
+ hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (!hw->base)
+ goto exit_busy;
+ /* program defaults into the registers */
+ hw->imr = 0; /* disable spi interrupts */
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
+ if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
+ readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
+ /* irq is optional */
+ hw->irq = platform_get_irq(pdev, 0);
+ if (hw->irq >= 0) {
+ init_completion(&hw->done);
+ err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
+ pdev->name, hw);
+ if (err)
+ goto exit;
+ }
+ /* find platform data */
+ if (!platp)
+ hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+
+ /* register our spi controller */
+ err = spi_bitbang_start(&hw->bitbang);
+ if (err)
+ goto exit;
+ dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
+
+ return 0;
+
+exit_busy:
+ err = -EBUSY;
+exit:
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+ return err;
+}
+
+static int __devexit altera_spi_remove(struct platform_device *dev)
+{
+ struct altera_spi *hw = platform_get_drvdata(dev);
+ struct spi_master *master = hw->bitbang.master;
+
+ spi_bitbang_stop(&hw->bitbang);
+ platform_set_drvdata(dev, NULL);
+ spi_master_put(master);
+ return 0;
+}
+
+static const struct of_device_id altera_spi_match[] = {
+ { .compatible = "ALTR,spi-1.0", },
+ {},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+
+static struct platform_driver altera_spi_driver = {
+ .probe = altera_spi_probe,
+ .remove = __devexit_p(altera_spi_remove),
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = NULL,
+ .of_match_table = altera_spi_match,
+ },
+};
+
+static int __init altera_spi_init(void)
+{
+ return platform_driver_register(&altera_spi_driver);
+}
+module_init(altera_spi_init);
+
+static void __exit altera_spi_exit(void)
+{
+ platform_driver_unregister(&altera_spi_driver);
+}
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
--
1.7.4

2011-02-14 02:08:01

by Thomas Chou

[permalink] [raw]
Subject: [PATCH v9] spi: New driver for Altera SPI

This patch adds a new SPI driver to support the Altera SOPC Builder
SPI component. It uses the bitbanging library.

Signed-off-by: Thomas Chou <[email protected]>
---
v2 add devicetree support
v3 remove platform header, as Grant suggested.
no irq resource means polling.
v4 minor cleanup, as Grant suggested.
v5 add compat version.
v6 change compatible vendor to uppercase, ALTR.
add dts binding doc.
v7 use platform driver register.
use devm_ to help cleanup.
v8 remove default n in Kconfig.
v9 condition module device table export for of.

.../devicetree/bindings/spi/spi_altera.txt | 4 +
drivers/spi/Kconfig | 6 +
drivers/spi/Makefile | 1 +
drivers/spi/spi_altera.c | 339 ++++++++++++++++++++
4 files changed, 350 insertions(+), 0 deletions(-)
create mode 100644 Documentation/devicetree/bindings/spi/spi_altera.txt
create mode 100644 drivers/spi/spi_altera.c

diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt
new file mode 100644
index 0000000..dda3759
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_altera.txt
@@ -0,0 +1,4 @@
+Altera SPI
+
+Required properties:
+- compatible : should be "ALTR,spi-1.0".
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bb233a9..e791579 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -53,6 +53,12 @@ if SPI_MASTER

comment "SPI Master Controller Drivers"

+config SPI_ALTERA
+ tristate "Altera SPI Controller"
+ select SPI_BITBANG
+ help
+ This is the driver for the Altera SPI Controller.
+
config SPI_ATH79
tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
depends on ATH79 && GENERIC_GPIO
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 86d1b5f..4d2e35a 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
obj-$(CONFIG_SPI_MASTER) += spi.o

# SPI master controller drivers (bus)
+obj-$(CONFIG_SPI_ALTERA) += spi_altera.o
obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
obj-$(CONFIG_SPI_ATH79) += ath79_spi.o
obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..5e001f3
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,339 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 Thomas Chou <[email protected]>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ * Ben Dooks <[email protected]>
+ *
+ * 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/interrupt.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_altera"
+
+#define ALTERA_SPI_RXDATA 0
+#define ALTERA_SPI_TXDATA 4
+#define ALTERA_SPI_STATUS 8
+#define ALTERA_SPI_CONTROL 12
+#define ALTERA_SPI_SLAVE_SEL 20
+
+#define ALTERA_SPI_STATUS_ROE_MSK 0x8
+#define ALTERA_SPI_STATUS_TOE_MSK 0x10
+#define ALTERA_SPI_STATUS_TMT_MSK 0x20
+#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
+#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
+#define ALTERA_SPI_STATUS_E_MSK 0x100
+
+#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
+#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
+#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
+#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
+#define ALTERA_SPI_CONTROL_IE_MSK 0x100
+#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
+
+struct altera_spi {
+ /* bitbang has to be first */
+ struct spi_bitbang bitbang;
+ struct completion done;
+
+ void __iomem *base;
+ int irq;
+ int len;
+ int count;
+ int bytes_per_word;
+ unsigned long imr;
+
+ /* data buffers */
+ const unsigned char *tx;
+ unsigned char *rx;
+};
+
+static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
+{
+ return spi_master_get_devdata(sdev->master);
+}
+
+static void altera_spi_chipsel(struct spi_device *spi, int value)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ if (spi->mode & SPI_CS_HIGH) {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
+ break;
+ }
+ } else {
+ switch (value) {
+ case BITBANG_CS_INACTIVE:
+ hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+
+ case BITBANG_CS_ACTIVE:
+ writel(1 << spi->chip_select,
+ hw->base + ALTERA_SPI_SLAVE_SEL);
+ hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ break;
+ }
+ }
+}
+
+static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
+{
+ return 0;
+}
+
+static int altera_spi_setup(struct spi_device *spi)
+{
+ return 0;
+}
+
+static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
+{
+ if (hw->tx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ return hw->tx[count];
+ case 2:
+ return (hw->tx[count * 2]
+ | (hw->tx[count * 2 + 1] << 8));
+ }
+ }
+ return 0;
+}
+
+static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+ struct altera_spi *hw = altera_spi_to_hw(spi);
+
+ hw->tx = t->tx_buf;
+ hw->rx = t->rx_buf;
+ hw->count = 0;
+ hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
+ hw->len = t->len / hw->bytes_per_word;
+
+ if (hw->irq >= 0) {
+ /* enable receive interrupt */
+ hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ wait_for_completion(&hw->done);
+ /* disable receive interrupt */
+ hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ } else {
+ /* send the first byte */
+ writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+ while (1) {
+ unsigned int rxd;
+
+ while (!(readl(hw->base + ALTERA_SPI_STATUS) &
+ ALTERA_SPI_STATUS_RRDY_MSK))
+ cpu_relax();
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count),
+ hw->base + ALTERA_SPI_TXDATA);
+ else
+ break;
+ }
+
+ }
+
+ return hw->count * hw->bytes_per_word;
+}
+
+static irqreturn_t altera_spi_irq(int irq, void *dev)
+{
+ struct altera_spi *hw = dev;
+ unsigned int rxd;
+
+ rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+ if (hw->rx) {
+ switch (hw->bytes_per_word) {
+ case 1:
+ hw->rx[hw->count] = rxd;
+ break;
+ case 2:
+ hw->rx[hw->count * 2] = rxd;
+ hw->rx[hw->count * 2 + 1] = rxd >> 8;
+ break;
+ }
+ }
+
+ hw->count++;
+
+ if (hw->count < hw->len)
+ writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
+ else
+ complete(&hw->done);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit altera_spi_probe(struct platform_device *pdev)
+{
+ struct altera_spi_platform_data *platp = pdev->dev.platform_data;
+ struct altera_spi *hw;
+ struct spi_master *master;
+ struct resource *res;
+ int err = -ENODEV;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
+ if (!master)
+ return err;
+
+ /* setup the master state. */
+ master->bus_num = pdev->id;
+ master->num_chipselect = 16;
+ master->mode_bits = SPI_CS_HIGH;
+ master->setup = altera_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)
+ return err;
+ hw->bitbang.setup_transfer = altera_spi_setupxfer;
+ hw->bitbang.chipselect = altera_spi_chipsel;
+ hw->bitbang.txrx_bufs = altera_spi_txrx;
+
+ /* find and map our resources */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ goto exit_busy;
+ if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
+ pdev->name))
+ goto exit_busy;
+ hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (!hw->base)
+ goto exit_busy;
+ /* program defaults into the registers */
+ hw->imr = 0; /* disable spi interrupts */
+ writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+ writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
+ if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
+ readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
+ /* irq is optional */
+ hw->irq = platform_get_irq(pdev, 0);
+ if (hw->irq >= 0) {
+ init_completion(&hw->done);
+ err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
+ pdev->name, hw);
+ if (err)
+ goto exit;
+ }
+ /* find platform data */
+ if (!platp)
+ hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+
+ /* register our spi controller */
+ err = spi_bitbang_start(&hw->bitbang);
+ if (err)
+ goto exit;
+ dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
+
+ return 0;
+
+exit_busy:
+ err = -EBUSY;
+exit:
+ platform_set_drvdata(pdev, NULL);
+ spi_master_put(master);
+ return err;
+}
+
+static int __devexit altera_spi_remove(struct platform_device *dev)
+{
+ struct altera_spi *hw = platform_get_drvdata(dev);
+ struct spi_master *master = hw->bitbang.master;
+
+ spi_bitbang_stop(&hw->bitbang);
+ platform_set_drvdata(dev, NULL);
+ spi_master_put(master);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id altera_spi_match[] = {
+ { .compatible = "ALTR,spi-1.0", },
+ {},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+#else /* CONFIG_OF */
+#define altera_spi_match NULL
+#endif /* CONFIG_OF */
+
+static struct platform_driver altera_spi_driver = {
+ .probe = altera_spi_probe,
+ .remove = __devexit_p(altera_spi_remove),
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = NULL,
+ .of_match_table = altera_spi_match,
+ },
+};
+
+static int __init altera_spi_init(void)
+{
+ return platform_driver_register(&altera_spi_driver);
+}
+module_init(altera_spi_init);
+
+static void __exit altera_spi_exit(void)
+{
+ platform_driver_unregister(&altera_spi_driver);
+}
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
--
1.7.4

2011-02-14 02:20:21

by Ryan Mallon

[permalink] [raw]
Subject: Re: [PATCH v9] spi: New driver for Altera SPI

On 02/14/2011 03:10 PM, Thomas Chou wrote:
> This patch adds a new SPI driver to support the Altera SOPC Builder
> SPI component. It uses the bitbanging library.
>
> Signed-off-by: Thomas Chou <[email protected]>
> ---

> +struct altera_spi {
> + /* bitbang has to be first */
> + struct spi_bitbang bitbang;

Is this still true? I had a quick look and can't see anything which
relies on spi_bitbang being the first entry. Things like this should be
using container_of so that position in the struct is irrelevant.

~Ryan

--
Bluewater Systems Ltd - ARM Technology Solution Centre

Ryan Mallon 5 Amuri Park, 404 Barbadoes St
[email protected] PO Box 13 889, Christchurch 8013
http://www.bluewatersys.com New Zealand
Phone: +64 3 3779127 Freecall: Australia 1800 148 751
Fax: +64 3 3779135 USA 1800 261 2934

2011-02-15 07:01:54

by Thomas Chou

[permalink] [raw]
Subject: Re: [PATCH v9] spi: New driver for Altera SPI

Dear Ryan,

On 02/14/2011 10:20 AM, Ryan Mallon wrote:
> On 02/14/2011 03:10 PM, Thomas Chou wrote:
>> This patch adds a new SPI driver to support the Altera SOPC Builder
>> SPI component. It uses the bitbanging library.
>>
>> Signed-off-by: Thomas Chou<[email protected]>
>> ---
>
>> +struct altera_spi {
>> + /* bitbang has to be first */
>> + struct spi_bitbang bitbang;
>
> Is this still true? I had a quick look and can't see anything which
> relies on spi_bitbang being the first entry. Things like this should be
> using container_of so that position in the struct is irrelevant.
>
> ~Ryan
>

Yes, sadly true. This is due to the implementation of the bitbanging
library, spi_bitbang.c, which assumes the struct spi_bitbang is the
first of drvdata. Though it could be changed in the future (beyond this
little driver), every bitbanging library user has to follow this for now.

Best regards,
Thomas

2011-02-15 09:13:15

by Ryan Mallon

[permalink] [raw]
Subject: Re: [PATCH v9] spi: New driver for Altera SPI

On 15/02/11 20:04, Thomas Chou wrote:
> Dear Ryan,
>
> On 02/14/2011 10:20 AM, Ryan Mallon wrote:
>> On 02/14/2011 03:10 PM, Thomas Chou wrote:
>>> This patch adds a new SPI driver to support the Altera SOPC Builder
>>> SPI component. It uses the bitbanging library.
>>>
>>> Signed-off-by: Thomas Chou<[email protected]>
>>> ---
>>
>>> +struct altera_spi {
>>> + /* bitbang has to be first */
>>> + struct spi_bitbang bitbang;
>>
>> Is this still true? I had a quick look and can't see anything which
>> relies on spi_bitbang being the first entry. Things like this should be
>> using container_of so that position in the struct is irrelevant.
>>
>> ~Ryan
>>
>
> Yes, sadly true. This is due to the implementation of the bitbanging
> library, spi_bitbang.c, which assumes the struct spi_bitbang is the
> first of drvdata. Though it could be changed in the future (beyond this
> little driver), every bitbanging library user has to follow this for now.

Hmm, I see how it works now. Shouldn't the conversion be done like this:

struct spi_bitbang *bitbang = spi_master_get_devdata(master);
struct altera_spi *altera = container_of(bitbang,
struct altera_spi, bitbang);

Which doesn't require the bitbang field to be the first in the struct
and makes it more clear what is actually going on. The above could be
wrapped into a to_altera_spi macro.

~Ryan

2011-02-15 19:43:04

by Grant Likely

[permalink] [raw]
Subject: Re: [PATCH v9] spi: New driver for Altera SPI

On Tue, Feb 15, 2011 at 03:04:49PM +0800, Thomas Chou wrote:
> Dear Ryan,
>
> On 02/14/2011 10:20 AM, Ryan Mallon wrote:
> >On 02/14/2011 03:10 PM, Thomas Chou wrote:
> >>This patch adds a new SPI driver to support the Altera SOPC Builder
> >>SPI component. It uses the bitbanging library.
> >>
> >>Signed-off-by: Thomas Chou<[email protected]>
> >>---
> >
> >>+struct altera_spi {
> >>+ /* bitbang has to be first */
> >>+ struct spi_bitbang bitbang;
> >
> >Is this still true? I had a quick look and can't see anything which
> >relies on spi_bitbang being the first entry. Things like this should be
> >using container_of so that position in the struct is irrelevant.
> >
> >~Ryan
> >
>
> Yes, sadly true. This is due to the implementation of the bitbanging
> library, spi_bitbang.c, which assumes the struct spi_bitbang is the
> first of drvdata. Though it could be changed in the future (beyond
> this little driver), every bitbanging library user has to follow
> this for now.

Should be easy to fix if it is indeed still true (I haven't dug deep
enough to find the design error yet). Anybody want to volunteer?

g.

2011-02-15 21:59:03

by Ryan Mallon

[permalink] [raw]
Subject: Re: [PATCH v9] spi: New driver for Altera SPI

On 16/02/11 08:42, Grant Likely wrote:
> On Tue, Feb 15, 2011 at 03:04:49PM +0800, Thomas Chou wrote:
>> Dear Ryan,
>>
>> On 02/14/2011 10:20 AM, Ryan Mallon wrote:
>>> On 02/14/2011 03:10 PM, Thomas Chou wrote:
>>>> This patch adds a new SPI driver to support the Altera SOPC Builder
>>>> SPI component. It uses the bitbanging library.
>>>>
>>>> Signed-off-by: Thomas Chou<[email protected]>
>>>> ---
>>>> +struct altera_spi {
>>>> + /* bitbang has to be first */
>>>> + struct spi_bitbang bitbang;
>>> Is this still true? I had a quick look and can't see anything which
>>> relies on spi_bitbang being the first entry. Things like this should be
>>> using container_of so that position in the struct is irrelevant.
>>>
>>> ~Ryan
>>>
>> Yes, sadly true. This is due to the implementation of the bitbanging
>> library, spi_bitbang.c, which assumes the struct spi_bitbang is the
>> first of drvdata. Though it could be changed in the future (beyond
>> this little driver), every bitbanging library user has to follow
>> this for now.
> Should be easy to fix if it is indeed still true (I haven't dug deep
> enough to find the design error yet). Anybody want to volunteer?
>
> g.
>
The problem is that spi_master_get_devdata is used to get both
struct spi_bitbang and the controller dependent structure, which means
that struct spi_bitbang must be the first entry in the container
structure.

The following incomplete, untested patch shows a possible way to fix
this by introducing spi_alloc_master_bitbang. spi_master_get_devdata
now returns a pointer to struct spi_bitbang only. Drivers which need
an additional container struct should allocate it themselves and
pass the bitbang field to spi_alloc_master_bitbang. The driver
specific container can be fetched with:

static inline struct my_spi *to_my_spi(struct spi_master *master)
{
struct spi_bitbang *bitbang = spi_master_get_devdata(master);
return container_of(bitbang, struct my_spi, bitbang);
}

This patch only adds the alloc function and shows the necessary
changes to the spi_gpio.c driver, which is actually pretty minimal.
If there is any interest I can code up a proper patch.

~Ryan

---
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 34bb17f..52ec691 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -27,6 +27,7 @@
#include <linux/slab.h>
#include <linux/mod_devicetable.h>
#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
#include <linux/of_spi.h>
#include <linux/pm_runtime.h>

@@ -560,6 +561,24 @@ struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
}
EXPORT_SYMBOL_GPL(spi_alloc_master);

+struct spi_master *spi_alloc_master_bitbang(struct device *dev,
+ struct spi_bitbang *bitbang)
+{
+ struct spi_master *master;
+
+ master = kzalloc(sizeof(struct spi_master), GFP_KERNEL);
+ if (!master)
+ return NULL;
+
+ device_initialize(&master->dev);
+ master->dev.class = &spi_master_class;
+ master->dev.parent = get_device(dev);
+ spi_master_set_devdata(master, bitbang);
+
+ return master;
+}
+EXPORT_SYMBOL_GPL(spi_alloc_master_bitbang);
+
/**
* spi_register_master - register SPI master controller
* @master: initialized master, originally from spi_alloc_master()
diff --git a/drivers/spi/spi_gpio.c b/drivers/spi/spi_gpio.c
index 63e51b0..169dbf0 100644
--- a/drivers/spi/spi_gpio.c
+++ b/drivers/spi/spi_gpio.c
@@ -329,12 +329,17 @@ static int __init spi_gpio_probe(struct platform_device *pdev)
if (status < 0)
return status;

- master = spi_alloc_master(&pdev->dev, sizeof *spi_gpio);
- if (!master) {
+ spi_gpio = kzalloc(sizeof(struct spi_gpio), GFP_KERNEL);
+ if (!spi_gpio) {
status = -ENOMEM;
goto gpio_free;
}
- spi_gpio = spi_master_get_devdata(master);
+
+ master = spi_alloc_master_bitbang(&pdev->dev, &spi_gpio->bitbang);
+ if (!master) {
+ status = -ENOMEM;
+ goto spi_gpio_free;
+ }
platform_set_drvdata(pdev, spi_gpio);

spi_gpio->pdev = pdev;
@@ -367,6 +372,8 @@ static int __init spi_gpio_probe(struct platform_device *pdev)
status = spi_bitbang_start(&spi_gpio->bitbang);
if (status < 0) {
spi_master_put(spi_gpio->bitbang.master);
+spi_gpio_free:
+ kfree(spi_gpio);
gpio_free:
if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO)
gpio_free(SPI_MISO_GPIO);
@@ -391,6 +398,7 @@ static int __exit spi_gpio_remove(struct platform_device *pdev)
/* stop() unregisters child devices too */
status = spi_bitbang_stop(&spi_gpio->bitbang);
spi_master_put(spi_gpio->bitbang.master);
+ kfree(spi_gpio);

platform_set_drvdata(pdev, NULL);

diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index b4d7710..3f72a34 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -336,7 +336,8 @@ static inline void spi_master_put(struct spi_master *master)
/* the spi driver core manages memory for the spi_master classdev */
extern struct spi_master *
spi_alloc_master(struct device *host, unsigned size);
-
+extern struct spi_master *
+spi_alloc_master_bitbang(struct device *host, struct spi_bitbang *bitbang);
extern int spi_register_master(struct spi_master *master);
extern void spi_unregister_master(struct spi_master *master);


2011-02-16 02:47:51

by Grant Likely

[permalink] [raw]
Subject: Re: [PATCH v9] spi: New driver for Altera SPI

On Mon, Feb 14, 2011 at 10:10:43AM +0800, Thomas Chou wrote:
> This patch adds a new SPI driver to support the Altera SOPC Builder
> SPI component. It uses the bitbanging library.
>
> Signed-off-by: Thomas Chou <[email protected]>

Applied, thanks.

g.

> ---
> v2 add devicetree support
> v3 remove platform header, as Grant suggested.
> no irq resource means polling.
> v4 minor cleanup, as Grant suggested.
> v5 add compat version.
> v6 change compatible vendor to uppercase, ALTR.
> add dts binding doc.
> v7 use platform driver register.
> use devm_ to help cleanup.
> v8 remove default n in Kconfig.
> v9 condition module device table export for of.
>
> .../devicetree/bindings/spi/spi_altera.txt | 4 +
> drivers/spi/Kconfig | 6 +
> drivers/spi/Makefile | 1 +
> drivers/spi/spi_altera.c | 339 ++++++++++++++++++++
> 4 files changed, 350 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/spi/spi_altera.txt
> create mode 100644 drivers/spi/spi_altera.c
>
> diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt
> new file mode 100644
> index 0000000..dda3759
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/spi/spi_altera.txt
> @@ -0,0 +1,4 @@
> +Altera SPI
> +
> +Required properties:
> +- compatible : should be "ALTR,spi-1.0".
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index bb233a9..e791579 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -53,6 +53,12 @@ if SPI_MASTER
>
> comment "SPI Master Controller Drivers"
>
> +config SPI_ALTERA
> + tristate "Altera SPI Controller"
> + select SPI_BITBANG
> + help
> + This is the driver for the Altera SPI Controller.
> +
> config SPI_ATH79
> tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver"
> depends on ATH79 && GENERIC_GPIO
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 86d1b5f..4d2e35a 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
> obj-$(CONFIG_SPI_MASTER) += spi.o
>
> # SPI master controller drivers (bus)
> +obj-$(CONFIG_SPI_ALTERA) += spi_altera.o
> obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o
> obj-$(CONFIG_SPI_ATH79) += ath79_spi.o
> obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o
> diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
> new file mode 100644
> index 0000000..5e001f3
> --- /dev/null
> +++ b/drivers/spi/spi_altera.c
> @@ -0,0 +1,339 @@
> +/*
> + * Altera SPI driver
> + *
> + * Copyright (C) 2008 Thomas Chou <[email protected]>
> + *
> + * Based on spi_s3c24xx.c, which is:
> + * Copyright (c) 2006 Ben Dooks
> + * Copyright (c) 2006 Simtec Electronics
> + * Ben Dooks <[email protected]>
> + *
> + * 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/interrupt.h>
> +#include <linux/errno.h>
> +#include <linux/platform_device.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi_bitbang.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +
> +#define DRV_NAME "spi_altera"
> +
> +#define ALTERA_SPI_RXDATA 0
> +#define ALTERA_SPI_TXDATA 4
> +#define ALTERA_SPI_STATUS 8
> +#define ALTERA_SPI_CONTROL 12
> +#define ALTERA_SPI_SLAVE_SEL 20
> +
> +#define ALTERA_SPI_STATUS_ROE_MSK 0x8
> +#define ALTERA_SPI_STATUS_TOE_MSK 0x10
> +#define ALTERA_SPI_STATUS_TMT_MSK 0x20
> +#define ALTERA_SPI_STATUS_TRDY_MSK 0x40
> +#define ALTERA_SPI_STATUS_RRDY_MSK 0x80
> +#define ALTERA_SPI_STATUS_E_MSK 0x100
> +
> +#define ALTERA_SPI_CONTROL_IROE_MSK 0x8
> +#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10
> +#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40
> +#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80
> +#define ALTERA_SPI_CONTROL_IE_MSK 0x100
> +#define ALTERA_SPI_CONTROL_SSO_MSK 0x400
> +
> +struct altera_spi {
> + /* bitbang has to be first */
> + struct spi_bitbang bitbang;
> + struct completion done;
> +
> + void __iomem *base;
> + int irq;
> + int len;
> + int count;
> + int bytes_per_word;
> + unsigned long imr;
> +
> + /* data buffers */
> + const unsigned char *tx;
> + unsigned char *rx;
> +};
> +
> +static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev)
> +{
> + return spi_master_get_devdata(sdev->master);
> +}
> +
> +static void altera_spi_chipsel(struct spi_device *spi, int value)
> +{
> + struct altera_spi *hw = altera_spi_to_hw(spi);
> +
> + if (spi->mode & SPI_CS_HIGH) {
> + switch (value) {
> + case BITBANG_CS_INACTIVE:
> + writel(1 << spi->chip_select,
> + hw->base + ALTERA_SPI_SLAVE_SEL);
> + hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + break;
> +
> + case BITBANG_CS_ACTIVE:
> + hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + writel(0, hw->base + ALTERA_SPI_SLAVE_SEL);
> + break;
> + }
> + } else {
> + switch (value) {
> + case BITBANG_CS_INACTIVE:
> + hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + break;
> +
> + case BITBANG_CS_ACTIVE:
> + writel(1 << spi->chip_select,
> + hw->base + ALTERA_SPI_SLAVE_SEL);
> + hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + break;
> + }
> + }
> +}
> +
> +static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t)
> +{
> + return 0;
> +}
> +
> +static int altera_spi_setup(struct spi_device *spi)
> +{
> + return 0;
> +}
> +
> +static inline unsigned int hw_txbyte(struct altera_spi *hw, int count)
> +{
> + if (hw->tx) {
> + switch (hw->bytes_per_word) {
> + case 1:
> + return hw->tx[count];
> + case 2:
> + return (hw->tx[count * 2]
> + | (hw->tx[count * 2 + 1] << 8));
> + }
> + }
> + return 0;
> +}
> +
> +static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
> +{
> + struct altera_spi *hw = altera_spi_to_hw(spi);
> +
> + hw->tx = t->tx_buf;
> + hw->rx = t->rx_buf;
> + hw->count = 0;
> + hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8;
> + hw->len = t->len / hw->bytes_per_word;
> +
> + if (hw->irq >= 0) {
> + /* enable receive interrupt */
> + hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> +
> + /* send the first byte */
> + writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
> +
> + wait_for_completion(&hw->done);
> + /* disable receive interrupt */
> + hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK;
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + } else {
> + /* send the first byte */
> + writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
> +
> + while (1) {
> + unsigned int rxd;
> +
> + while (!(readl(hw->base + ALTERA_SPI_STATUS) &
> + ALTERA_SPI_STATUS_RRDY_MSK))
> + cpu_relax();
> +
> + rxd = readl(hw->base + ALTERA_SPI_RXDATA);
> + if (hw->rx) {
> + switch (hw->bytes_per_word) {
> + case 1:
> + hw->rx[hw->count] = rxd;
> + break;
> + case 2:
> + hw->rx[hw->count * 2] = rxd;
> + hw->rx[hw->count * 2 + 1] = rxd >> 8;
> + break;
> + }
> + }
> +
> + hw->count++;
> +
> + if (hw->count < hw->len)
> + writel(hw_txbyte(hw, hw->count),
> + hw->base + ALTERA_SPI_TXDATA);
> + else
> + break;
> + }
> +
> + }
> +
> + return hw->count * hw->bytes_per_word;
> +}
> +
> +static irqreturn_t altera_spi_irq(int irq, void *dev)
> +{
> + struct altera_spi *hw = dev;
> + unsigned int rxd;
> +
> + rxd = readl(hw->base + ALTERA_SPI_RXDATA);
> + if (hw->rx) {
> + switch (hw->bytes_per_word) {
> + case 1:
> + hw->rx[hw->count] = rxd;
> + break;
> + case 2:
> + hw->rx[hw->count * 2] = rxd;
> + hw->rx[hw->count * 2 + 1] = rxd >> 8;
> + break;
> + }
> + }
> +
> + hw->count++;
> +
> + if (hw->count < hw->len)
> + writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA);
> + else
> + complete(&hw->done);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int __devinit altera_spi_probe(struct platform_device *pdev)
> +{
> + struct altera_spi_platform_data *platp = pdev->dev.platform_data;
> + struct altera_spi *hw;
> + struct spi_master *master;
> + struct resource *res;
> + int err = -ENODEV;
> +
> + master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi));
> + if (!master)
> + return err;
> +
> + /* setup the master state. */
> + master->bus_num = pdev->id;
> + master->num_chipselect = 16;
> + master->mode_bits = SPI_CS_HIGH;
> + master->setup = altera_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)
> + return err;
> + hw->bitbang.setup_transfer = altera_spi_setupxfer;
> + hw->bitbang.chipselect = altera_spi_chipsel;
> + hw->bitbang.txrx_bufs = altera_spi_txrx;
> +
> + /* find and map our resources */
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + goto exit_busy;
> + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
> + pdev->name))
> + goto exit_busy;
> + hw->base = devm_ioremap_nocache(&pdev->dev, res->start,
> + resource_size(res));
> + if (!hw->base)
> + goto exit_busy;
> + /* program defaults into the registers */
> + hw->imr = 0; /* disable spi interrupts */
> + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
> + writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */
> + if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK)
> + readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */
> + /* irq is optional */
> + hw->irq = platform_get_irq(pdev, 0);
> + if (hw->irq >= 0) {
> + init_completion(&hw->done);
> + err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0,
> + pdev->name, hw);
> + if (err)
> + goto exit;
> + }
> + /* find platform data */
> + if (!platp)
> + hw->bitbang.master->dev.of_node = pdev->dev.of_node;
> +
> + /* register our spi controller */
> + err = spi_bitbang_start(&hw->bitbang);
> + if (err)
> + goto exit;
> + dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
> +
> + return 0;
> +
> +exit_busy:
> + err = -EBUSY;
> +exit:
> + platform_set_drvdata(pdev, NULL);
> + spi_master_put(master);
> + return err;
> +}
> +
> +static int __devexit altera_spi_remove(struct platform_device *dev)
> +{
> + struct altera_spi *hw = platform_get_drvdata(dev);
> + struct spi_master *master = hw->bitbang.master;
> +
> + spi_bitbang_stop(&hw->bitbang);
> + platform_set_drvdata(dev, NULL);
> + spi_master_put(master);
> + return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id altera_spi_match[] = {
> + { .compatible = "ALTR,spi-1.0", },
> + {},
> +}
> +MODULE_DEVICE_TABLE(of, altera_spi_match);
> +#else /* CONFIG_OF */
> +#define altera_spi_match NULL
> +#endif /* CONFIG_OF */
> +
> +static struct platform_driver altera_spi_driver = {
> + .probe = altera_spi_probe,
> + .remove = __devexit_p(altera_spi_remove),
> + .driver = {
> + .name = DRV_NAME,
> + .owner = THIS_MODULE,
> + .pm = NULL,
> + .of_match_table = altera_spi_match,
> + },
> +};
> +
> +static int __init altera_spi_init(void)
> +{
> + return platform_driver_register(&altera_spi_driver);
> +}
> +module_init(altera_spi_init);
> +
> +static void __exit altera_spi_exit(void)
> +{
> + platform_driver_unregister(&altera_spi_driver);
> +}
> +module_exit(altera_spi_exit);
> +
> +MODULE_DESCRIPTION("Altera SPI driver");
> +MODULE_AUTHOR("Thomas Chou <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRV_NAME);
> --
> 1.7.4
>