linux-spi.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] spi: New driver for Altera SPI
@ 2009-10-08  6:03 Thomas Chou
       [not found] ` <1254981838-20584-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2009-10-08  6:03 UTC (permalink / raw)
  To: spi-devel-general; +Cc: Nios2 development list, linux-kernel, Thomas Chou

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 <thomas@wytron.com.tw>
---
 drivers/spi/Kconfig      |    7 +
 drivers/spi/Makefile     |    1 +
 drivers/spi/spi_altera.c |  397 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 405 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/spi_altera.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 4b6f7cb..64f1148 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"
+	depends on EMBEDDED
+	select SPI_BITBANG
+	help
+	  This is the driver for the Altera SPI Controller.
+
 config SPI_ATMEL
 	tristate "Atmel SPI Controller"
 	depends on (ARCH_AT91 || AVR32)
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 21a1182..f5f43f1 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -11,6 +11,7 @@ endif
 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_BFIN)			+= spi_bfin5xx.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..337dc95
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,397 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 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/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>
+
+#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;
+
+	unsigned long base;
+	int irq;
+	int len;
+	int count;
+	int bytes_per_word;
+	unsigned long imr;
+
+	/* data buffers */
+	const unsigned char *tx;
+	unsigned char *rx;
+
+	struct resource *ioarea;
+	struct spi_master *master;
+	struct spi_device *curdev;
+	struct device *dev;
+};
+
+static inline struct 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 = 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)
+{
+	struct altera_spi *hw = to_hw(spi);
+
+	spin_lock(&hw->bitbang.lock);
+	if (!hw->bitbang.busy) {
+		hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
+		/* need to ndelay for 0.5 clocktick ? */
+	}
+	spin_unlock(&hw->bitbang.lock);
+
+	return 0;
+}
+
+/* the spi->mode bits understood by this driver: */
+#define MODEBITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH)
+
+static int altera_spi_setup(struct spi_device *spi)
+{
+	int ret;
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+
+	if (spi->mode & ~MODEBITS) {
+		dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",
+			spi->mode & ~MODEBITS);
+		return -EINVAL;
+	}
+
+	ret = altera_spi_setupxfer(spi, NULL);
+	if (ret < 0) {
+		dev_err(&spi->dev, "setupxfer returned %d\n", ret);
+		return ret;
+	}
+
+	dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
+		__func__, spi->mode, spi->bits_per_word, spi->max_speed_hz);
+
+	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 = to_hw(spi);
+
+	dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
+		t->tx_buf, t->rx_buf, t->len);
+
+	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;
+
+	init_completion(&hw->done);
+
+	/* send the first byte */
+	writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA);
+
+	wait_for_completion(&hw->done);
+
+	return hw->count * hw->bytes_per_word;
+}
+
+static irqreturn_t altera_spi_irq(int irq, void *dev)
+{
+	struct altera_spi *hw = dev;
+	unsigned int spsta = readl(hw->base + ALTERA_SPI_STATUS);
+	unsigned int count = hw->count;
+	unsigned int rxd;
+
+	if (spsta & ALTERA_SPI_STATUS_ROE_MSK) {
+		dev_dbg(hw->dev, "data-collision\n");
+		complete(&hw->done);
+		goto irq_done;
+	}
+
+	if (!(spsta & ALTERA_SPI_STATUS_TRDY_MSK)) {
+		dev_dbg(hw->dev, "spi not ready for tx?\n");
+		complete(&hw->done);
+		goto irq_done;
+	}
+
+	hw->count++;
+
+	rxd = readl(hw->base + ALTERA_SPI_RXDATA);
+	if (hw->rx) {
+		switch (hw->bytes_per_word) {
+		case 1:
+			hw->rx[count] = rxd;
+			break;
+		case 2:
+			hw->rx[count * 2] = rxd;
+			hw->rx[count * 2 + 1] = rxd >> 8;
+			break;
+		}
+	}
+
+	count++;
+
+	if (count < hw->len)
+		writel(hw_txbyte(hw, count), hw->base + ALTERA_SPI_TXDATA);
+	else
+		complete(&hw->done);
+
+irq_done:
+	return IRQ_HANDLED;
+}
+
+static int __init altera_spi_probe(struct platform_device *pdev)
+{
+	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_nomem;
+	}
+
+	hw = spi_master_get_devdata(master);
+	memset(hw, 0, sizeof(struct altera_spi));
+
+	hw->master = spi_master_get(master);
+	hw->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, hw);
+	init_completion(&hw->done);
+
+	/* setup the master state. */
+
+	master->bus_num = pdev->id;
+	master->num_chipselect = 16;
+
+	/* setup the state for the bitbang driver */
+
+	hw->bitbang.master = hw->master;
+	hw->bitbang.setup_transfer = altera_spi_setupxfer;
+	hw->bitbang.chipselect = altera_spi_chipsel;
+	hw->bitbang.txrx_bufs = altera_spi_txrx;
+	hw->bitbang.master->setup = altera_spi_setup;
+
+	dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);
+
+	/* 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->ioarea = request_mem_region(res->start, (res->end - res->start) + 1,
+					pdev->name);
+
+	if (hw->ioarea == NULL) {
+		dev_err(&pdev->dev, "Cannot reserve region\n");
+		err = -ENXIO;
+		goto err_no_iores;
+	}
+
+	hw->base =
+	    (unsigned long)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;
+	}
+
+	hw->irq = platform_get_irq(pdev, 0);
+	if (hw->irq < 0) {
+		dev_err(&pdev->dev, "No IRQ specified\n");
+		err = -ENOENT;
+		goto err_no_irq;
+	}
+
+	/* 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 */
+
+	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;
+	}
+
+	/* enable receive interrupt */
+	hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK;
+	writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+
+	/* 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;
+	}
+
+	return 0;
+
+err_register:
+	free_irq(hw->irq, hw);
+err_no_irq:
+	iounmap((void *)hw->base);
+err_no_iomap:
+	release_resource(hw->ioarea);
+	kfree(hw->ioarea);
+err_no_iores:
+	spi_master_put(hw->master);;
+err_nomem:
+	return err;
+}
+
+static int __exit altera_spi_remove(struct platform_device *dev)
+{
+	struct altera_spi *hw = platform_get_drvdata(dev);
+
+	/* disable spi interrupts */
+	hw->imr = 0;
+	writel(hw->imr, hw->base + ALTERA_SPI_CONTROL);
+
+	platform_set_drvdata(dev, NULL);
+
+	spi_unregister_master(hw->master);
+
+	free_irq(hw->irq, hw);
+	iounmap((void *)hw->base);
+
+	release_resource(hw->ioarea);
+	kfree(hw->ioarea);
+
+	spi_master_put(hw->master);
+	return 0;
+}
+
+static struct platform_driver altera_spidrv = {
+	.remove = __exit_p(altera_spi_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.pm = NULL,
+	},
+};
+
+static int __init altera_spi_init(void)
+{
+	return platform_driver_probe(&altera_spidrv, altera_spi_probe);
+}
+
+static void __exit altera_spi_exit(void)
+{
+	platform_driver_unregister(&altera_spidrv);
+}
+
+module_init(altera_spi_init);
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.6.2.5

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v2] spi: New driver for Altera SPI
       [not found] ` <1254981838-20584-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-01-17  5:46   ` Thomas Chou
       [not found]     ` <1295243200-28230-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-01-17  5:46 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, Mike Frysinger,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
---
v2 add devicetree support.

 drivers/spi/Kconfig            |    6 +
 drivers/spi/Makefile           |    1 +
 drivers/spi/spi_altera.c       |  398 ++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi_altera.h |   12 ++
 4 files changed, 417 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/spi_altera.c
 create mode 100644 include/linux/spi/spi_altera.h

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 13bfa9d..2b5b8d6 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_ATMEL
 	tristate "Atmel SPI Controller"
 	depends on (ARCH_AT91 || AVR32)
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 3a42463..c2675a4 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_BFIN)			+= spi_bfin5xx.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..28c78c3
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,398 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 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/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/spi/spi_altera.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 interrupt; /* use interrupt driven data transfer, slow */
+	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 *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 = 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 = 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 && hw->interrupt) {
+		init_completion(&hw->done);
+		/* 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;
+}
+
+#ifdef CONFIG_OF
+static int __devinit altera_spi_of_probe(struct platform_device *pdev,
+				       struct altera_spi *hw)
+{
+	const __be32 *val;
+
+	hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+	val = of_get_property(pdev->dev.of_node, "interrupt-driven", NULL);
+	if (val)
+		hw->interrupt = be32_to_cpup(val);
+	return 0;
+}
+#else
+static int __devinit altera_spi_of_probe(struct platform_device *pdev,
+				       struct altera_spi *hw)
+{
+	return 0;
+}
+#endif
+
+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->interrupt = platp->interrupt;
+	} else {
+		err = altera_spi_of_probe(pdev, hw);
+		if (err)
+			goto err_no_of;
+	}
+
+	/* 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:
+err_no_of:
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+err_no_irq:
+	iounmap((void *)hw->base);
+err_no_iomap:
+err_no_iores:
+	spi_master_put(master);;
+err_no_mem:
+err_no_dev:
+	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((void *)hw->base);
+
+	platform_set_drvdata(dev, NULL);
+	spi_master_put(master);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id altera_spi_match[] = {
+	{
+		.compatible = "altera,spi_altera",
+	},
+	{},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+#endif
+
+static struct platform_driver altera_spidrv = {
+	.remove = __devexit_p(altera_spi_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.pm = NULL,
+#ifdef CONFIG_OF
+		.of_match_table = altera_spi_match,
+#endif
+	},
+};
+
+static int __init altera_spi_init(void)
+{
+	return platform_driver_probe(&altera_spidrv, altera_spi_probe);
+}
+
+static void __exit altera_spi_exit(void)
+{
+	platform_driver_unregister(&altera_spidrv);
+}
+
+module_init(altera_spi_init);
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera 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_altera.h b/include/linux/spi/spi_altera.h
new file mode 100644
index 0000000..b3d7f69
--- /dev/null
+++ b/include/linux/spi/spi_altera.h
@@ -0,0 +1,12 @@
+#ifndef _LINUX_SPI_SPI_ALTERA_H
+#define _LINUX_SPI_SPI_ALTERA_H
+
+/*
+ * struct altera_spi_platform_data - platform data of the Altera SPI
+ * @interrupt:	use intrrupt driven data transfer.
+ */
+struct altera_spi_platform_data {
+	int interrupt;
+};
+
+#endif /* _LINUX_SPI_SPI_ALTERA_H */
-- 
1.7.3.4

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v2] spi: New driver for Altera SPI
       [not found]     ` <1295243200-28230-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-01-17  5:59       ` Stephen Rothwell
       [not found]         ` <20110117165958.f89e8220.sfr-3FnU+UHB4dNDw9hX6IcOSA@public.gmane.org>
  2011-01-17  6:56       ` [PATCH v2] " Grant Likely
  1 sibling, 1 reply; 27+ messages in thread
From: Stephen Rothwell @ 2011-01-17  5:59 UTC (permalink / raw)
  To: Thomas Chou
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f


[-- Attachment #1.1: Type: text/plain, Size: 1035 bytes --]

Hi Thomas,

On Mon, 17 Jan 2011 13:46:40 +0800 Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> wrote:
>
> This patch adds a new SPI driver to support the Altera SOPC Builder
> SPI component. It uses the bitbanging library.

Just a general query.

> diff --git a/include/linux/spi/spi_altera.h b/include/linux/spi/spi_altera.h
> new file mode 100644
> index 0000000..b3d7f69
> --- /dev/null
> +++ b/include/linux/spi/spi_altera.h
> @@ -0,0 +1,12 @@
> +#ifndef _LINUX_SPI_SPI_ALTERA_H
> +#define _LINUX_SPI_SPI_ALTERA_H
> +
> +/*
> + * struct altera_spi_platform_data - platform data of the Altera SPI
> + * @interrupt:	use intrrupt driven data transfer.
> + */
> +struct altera_spi_platform_data {
> +	int interrupt;
> +};

Is there some reason that this struct definition cannot just be placed
in spi_altera.c and so save a whole extra file (and its inclusion)?

-- 
Cheers,
Stephen Rothwell                    sfr-3FnU+UHB4dNDw9hX6IcOSA@public.gmane.org
http://www.canb.auug.org.au/~sfr/

[-- Attachment #1.2: Type: application/pgp-signature, Size: 490 bytes --]

[-- Attachment #2: Type: text/plain, Size: 192 bytes --]

_______________________________________________
devicetree-discuss mailing list
devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org
https://lists.ozlabs.org/listinfo/devicetree-discuss

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2] spi: New driver for Altera SPI
       [not found]         ` <20110117165958.f89e8220.sfr-3FnU+UHB4dNDw9hX6IcOSA@public.gmane.org>
@ 2011-01-17  6:22           ` Thomas Chou
       [not found]             ` <4D33E00E.5050802-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-01-17  6:22 UTC (permalink / raw)
  To: Stephen Rothwell
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On 01/17/2011 01:59 PM, Stephen Rothwell wrote:
>> +/*
>> + * struct altera_spi_platform_data - platform data of the Altera SPI
>> + * @interrupt:	use intrrupt driven data transfer.
>> + */
>> +struct altera_spi_platform_data {
>> +	int interrupt;
>> +};
>
> Is there some reason that this struct definition cannot just be placed
> in spi_altera.c and so save a whole extra file (and its inclusion)?
>

Hi Stephen,

It is needed for board configuration file to attach this platform data, 
which is used to choose between interrupt driven data transfer or 
polling method. The data transfer rate is usually faster for polling, as 
interrupt overhead is higher.

Best regards,
Thomas

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2] spi: New driver for Altera SPI
       [not found]             ` <4D33E00E.5050802-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-01-17  6:45               ` Grant Likely
       [not found]                 ` <AANLkTi=D4Rcxbei2oo+EHc6X1O-2Oo+VWHAasK_JLj5W-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Grant Likely @ 2011-01-17  6:45 UTC (permalink / raw)
  To: Thomas Chou
  Cc: Stephen Rothwell, nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH,
	David Brownell, Mike Frysinger,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On Sun, Jan 16, 2011 at 11:22 PM, Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> wrote:
> On 01/17/2011 01:59 PM, Stephen Rothwell wrote:
>>>
>>> +/*
>>> + * struct altera_spi_platform_data - platform data of the Altera SPI
>>> + * @interrupt: use intrrupt driven data transfer.
>>> + */
>>> +struct altera_spi_platform_data {
>>> +       int interrupt;
>>> +};
>>
>> Is there some reason that this struct definition cannot just be placed
>> in spi_altera.c and so save a whole extra file (and its inclusion)?
>>
>
> Hi Stephen,
>
> It is needed for board configuration file to attach this platform data,
> which is used to choose between interrupt driven data transfer or polling
> method. The data transfer rate is usually faster for polling, as interrupt
> overhead is higher.

In this case, that decision could simply be made based on whether or
not the device has an irq resource.  No irq == polled mode.

g.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v2] spi: New driver for Altera SPI
       [not found]     ` <1295243200-28230-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  2011-01-17  5:59       ` Stephen Rothwell
@ 2011-01-17  6:56       ` Grant Likely
       [not found]         ` <AANLkTimCvYai7mvrrHP9=EXB4L+yw3W2ad-7DU_iEzLy-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  1 sibling, 1 reply; 27+ messages in thread
From: Grant Likely @ 2011-01-17  6:56 UTC (permalink / raw)
  To: Thomas Chou
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On Sun, Jan 16, 2011 at 10:46 PM, Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> 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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>

Hi Thomas, some minor comments below.  I've not dug any deeper, so I
have not verified locking or the way the driver uses the bitbang
library, or anything like that.

g.

> ---
> v2 add devicetree support.
>
>  drivers/spi/Kconfig            |    6 +
>  drivers/spi/Makefile           |    1 +
>  drivers/spi/spi_altera.c       |  398 ++++++++++++++++++++++++++++++++++++++++
>  include/linux/spi/spi_altera.h |   12 ++
>  4 files changed, 417 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/spi/spi_altera.c
>  create mode 100644 include/linux/spi/spi_altera.h
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 13bfa9d..2b5b8d6 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_ATMEL
>        tristate "Atmel SPI Controller"
>        depends on (ARCH_AT91 || AVR32)
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 3a42463..c2675a4 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_BFIN)                 += spi_bfin5xx.o
>  obj-$(CONFIG_SPI_BITBANG)              += spi_bitbang.o
> diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
> new file mode 100644
> index 0000000..28c78c3
> --- /dev/null
> +++ b/drivers/spi/spi_altera.c
> @@ -0,0 +1,398 @@
> +/*
> + * Altera SPI driver
> + *
> + * Copyright (C) 2008 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/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/spi/spi_altera.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 interrupt; /* use interrupt driven data transfer, slow */

Is this a flag?  If so, use type 'bool'.

> +       int len;
> +       int count;
> +       int bytes_per_word;
> +       unsigned long imr;
> +
> +       /* data buffers */
> +       const unsigned char *tx;
> +       unsigned char *rx;
> +
> +};

Nit: drop the blank line at the end of the structure.

> +
> +static inline struct altera_spi *to_hw(struct spi_device *sdev)

All file scope symbols should have a driver prefix.  In this case, it
should be "altera_spi_to_hw()".

> +{
> +       return spi_master_get_devdata(sdev->master);
> +}
> +
> +static void altera_spi_chipsel(struct spi_device *spi, int value)
> +{
> +       struct altera_spi *hw = 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 = 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 && hw->interrupt) {
> +               init_completion(&hw->done);
> +               /* 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;
> +}
> +
> +#ifdef CONFIG_OF
> +static int __devinit altera_spi_of_probe(struct platform_device *pdev,
> +                                      struct altera_spi *hw)
> +{
> +       const __be32 *val;
> +
> +       hw->bitbang.master->dev.of_node = pdev->dev.of_node;
> +       val = of_get_property(pdev->dev.of_node, "interrupt-driven", NULL);
> +       if (val)
> +               hw->interrupt = be32_to_cpup(val);
> +       return 0;
> +}
> +#else
> +static int __devinit altera_spi_of_probe(struct platform_device *pdev,
> +                                      struct altera_spi *hw)
> +{
> +       return 0;
> +}
> +#endif
> +
> +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->interrupt = platp->interrupt;
> +       } else {
> +               err = altera_spi_of_probe(pdev, hw);
> +               if (err)
> +                       goto err_no_of;
> +       }
> +
> +       /* 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:
> +err_no_of:
> +       if (hw->irq >= 0)
> +               free_irq(hw->irq, hw);
> +err_no_irq:
> +       iounmap((void *)hw->base);

The cast should not be necessary.

> +err_no_iomap:
> +err_no_iores:
> +       spi_master_put(master);;
> +err_no_mem:
> +err_no_dev:
> +       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((void *)hw->base);

Ditto here.

> +
> +       platform_set_drvdata(dev, NULL);
> +       spi_master_put(master);
> +       return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static struct of_device_id altera_spi_match[] = {
> +       {
> +               .compatible = "altera,spi_altera",
> +       },
> +       {},
> +}
> +MODULE_DEVICE_TABLE(of, altera_spi_match);
> +#endif
> +
> +static struct platform_driver altera_spidrv = {
> +       .remove = __devexit_p(altera_spi_remove),
> +       .driver = {
> +               .name = DRV_NAME,
> +               .owner = THIS_MODULE,
> +               .pm = NULL,
> +#ifdef CONFIG_OF
> +               .of_match_table = altera_spi_match,
> +#endif
> +       },
> +};
> +
> +static int __init altera_spi_init(void)
> +{
> +       return platform_driver_probe(&altera_spidrv, altera_spi_probe);
> +}
> +
> +static void __exit altera_spi_exit(void)
> +{
> +       platform_driver_unregister(&altera_spidrv);
> +}
> +
> +module_init(altera_spi_init);

Move module_init line to be immediately after altera_spi_init() definition.

> +module_exit(altera_spi_exit);
> +
> +MODULE_DESCRIPTION("Altera 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_altera.h b/include/linux/spi/spi_altera.h
> new file mode 100644
> index 0000000..b3d7f69
> --- /dev/null
> +++ b/include/linux/spi/spi_altera.h
> @@ -0,0 +1,12 @@
> +#ifndef _LINUX_SPI_SPI_ALTERA_H
> +#define _LINUX_SPI_SPI_ALTERA_H
> +
> +/*
> + * struct altera_spi_platform_data - platform data of the Altera SPI
> + * @interrupt: use intrrupt driven data transfer.
> + */
> +struct altera_spi_platform_data {
> +       int interrupt;
> +};
> +
> +#endif /* _LINUX_SPI_SPI_ALTERA_H */
> --
> 1.7.3.4
>
>



-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v3] spi: New driver for Altera SPI
       [not found]                 ` <AANLkTi=D4Rcxbei2oo+EHc6X1O-2Oo+VWHAasK_JLj5W-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2011-01-17  7:04                   ` Thomas Chou
       [not found]                     ` <1295247869-4968-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-01-17  7:04 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, Mike Frysinger,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
---
v2 add devicetree support
v3 remove platform header, as Grant suggested.
   no irq resource means polling.

 drivers/spi/Kconfig      |    6 +
 drivers/spi/Makefile     |    1 +
 drivers/spi/spi_altera.c |  391 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 398 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/spi_altera.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 13bfa9d..2b5b8d6 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_ATMEL
 	tristate "Atmel SPI Controller"
 	depends on (ARCH_AT91 || AVR32)
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 3a42463..c2675a4 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_BFIN)			+= spi_bfin5xx.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..f90ef0f
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,391 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 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/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 *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 = 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 = 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) {
+		init_completion(&hw->done);
+		/* 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;
+}
+
+#ifdef CONFIG_OF
+static int __devinit altera_spi_of_probe(struct platform_device *pdev,
+				       struct altera_spi *hw)
+{
+	const __be32 *val;
+
+	hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+	return 0;
+}
+#else
+static int __devinit altera_spi_of_probe(struct platform_device *pdev,
+				       struct altera_spi *hw)
+{
+	return 0;
+}
+#endif
+
+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) {
+		err = altera_spi_of_probe(pdev, hw);
+		if (err)
+			goto err_no_of;
+	}
+
+	/* 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:
+err_no_of:
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+err_no_irq:
+	iounmap((void *)hw->base);
+err_no_iomap:
+err_no_iores:
+	spi_master_put(master);;
+err_no_mem:
+err_no_dev:
+	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((void *)hw->base);
+
+	platform_set_drvdata(dev, NULL);
+	spi_master_put(master);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id altera_spi_match[] = {
+	{
+		.compatible = "altera,spi_altera",
+	},
+	{},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+#endif
+
+static struct platform_driver altera_spidrv = {
+	.remove = __devexit_p(altera_spi_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.pm = NULL,
+#ifdef CONFIG_OF
+		.of_match_table = altera_spi_match,
+#endif
+	},
+};
+
+static int __init altera_spi_init(void)
+{
+	return platform_driver_probe(&altera_spidrv, altera_spi_probe);
+}
+
+static void __exit altera_spi_exit(void)
+{
+	platform_driver_unregister(&altera_spidrv);
+}
+
+module_init(altera_spi_init);
+module_exit(altera_spi_exit);
+
+MODULE_DESCRIPTION("Altera SPI driver");
+MODULE_AUTHOR("Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.3.4

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v4] spi: New driver for Altera SPI
       [not found]         ` <AANLkTimCvYai7mvrrHP9=EXB4L+yw3W2ad-7DU_iEzLy-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2011-01-17  7:13           ` Thomas Chou
       [not found]             ` <1295248436-5049-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-01-17  7:13 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, Mike Frysinger,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
---
v2 add devicetree support
v3 remove platform header, as Grant suggested.
   no irq resource means polling.
v4 minor cleanup, as Grant suggested.

 drivers/spi/Kconfig      |    6 +
 drivers/spi/Makefile     |    1 +
 drivers/spi/spi_altera.c |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 396 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/spi_altera.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 13bfa9d..2b5b8d6 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_ATMEL
 	tristate "Atmel SPI Controller"
 	depends on (ARCH_AT91 || AVR32)
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 3a42463..c2675a4 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_BFIN)			+= spi_bfin5xx.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
new file mode 100644
index 0000000..e4cbd5f
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,389 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 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/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) {
+		init_completion(&hw->done);
+		/* 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;
+}
+
+#ifdef CONFIG_OF
+static int __devinit altera_spi_of_probe(struct platform_device *pdev,
+				       struct altera_spi *hw)
+{
+	const __be32 *val;
+
+	hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+	return 0;
+}
+#else
+static int __devinit altera_spi_of_probe(struct platform_device *pdev,
+				       struct altera_spi *hw)
+{
+	return 0;
+}
+#endif
+
+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) {
+		err = altera_spi_of_probe(pdev, hw);
+		if (err)
+			goto err_no_of;
+	}
+
+	/* 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:
+err_no_of:
+	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 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;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id altera_spi_match[] = {
+	{
+		.compatible = "altera,spi_altera",
+	},
+	{},
+}
+MODULE_DEVICE_TABLE(of, altera_spi_match);
+#endif
+
+static struct platform_driver altera_spidrv = {
+	.remove = __devexit_p(altera_spi_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.pm = NULL,
+#ifdef CONFIG_OF
+		.of_match_table = altera_spi_match,
+#endif
+	},
+};
+
+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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.3.4

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v3] spi: New driver for Altera SPI
       [not found]                     ` <1295247869-4968-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-01-17  7:27                       ` Sinan Akman
       [not found]                         ` <4D33EF7C.7030005-w0xpxkV3uqlBDgjK7y7TUQ@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Sinan Akman @ 2011-01-17  7:27 UTC (permalink / raw)
  To: Thomas Chou
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

Thomas Chou wrote:

[...]
> +
> +static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
> +{
> +   struct altera_spi *hw = 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) {
> +       init_completion(&hw->done);

       I think you init this twice, once in altera_spi_probe
below as well.

> +       /* 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;
> +}
> +
> +#ifdef CONFIG_OF
> +static int __devinit altera_spi_of_probe(struct platform_device *pdev,
> +                      struct altera_spi *hw)
> +{
> +   const __be32 *val;
> +
> +   hw->bitbang.master->dev.of_node = pdev->dev.of_node;
> +   return 0;
> +}
> +#else
> +static int __devinit altera_spi_of_probe(struct platform_device *pdev,
> +                      struct altera_spi *hw)
> +{
> +   return 0;
> +}
> +#endif
> +
> +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) {
> +       err = altera_spi_of_probe(pdev, hw);
> +       if (err)
> +           goto err_no_of;
> +   }
> +
> +   /* 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:
> +err_no_of:
> +   if (hw->irq >= 0)
> +       free_irq(hw->irq, hw);
> +err_no_irq:
> +   iounmap((void *)hw->base);
> +err_no_iomap:
> +err_no_iores:
> +   spi_master_put(master);;
> +err_no_mem:
> +err_no_dev:
> +   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((void *)hw->base);
> +
> +   platform_set_drvdata(dev, NULL);
> +   spi_master_put(master);
> +   return 0;
> +}
> +
> +#ifdef CONFIG_OF
> +static struct of_device_id altera_spi_match[] = {
> +   {
> +       .compatible = "altera,spi_altera",
> +   },
> +   {},
> +}
> +MODULE_DEVICE_TABLE(of, altera_spi_match);
> +#endif
> +
> +static struct platform_driver altera_spidrv = {
> +   .remove = __devexit_p(altera_spi_remove),
> +   .driver = {
> +       .name = DRV_NAME,
> +       .owner = THIS_MODULE,
> +       .pm = NULL,
> +#ifdef CONFIG_OF
> +       .of_match_table = altera_spi_match,
> +#endif
> +   },
> +};
> +
> +static int __init altera_spi_init(void)
> +{
> +   return platform_driver_probe(&altera_spidrv, altera_spi_probe);
> +}
> +
> +static void __exit altera_spi_exit(void)
> +{
> +   platform_driver_unregister(&altera_spidrv);
> +}
> +
> +module_init(altera_spi_init);
> +module_exit(altera_spi_exit);
> +
> +MODULE_DESCRIPTION("Altera SPI driver");
> +MODULE_AUTHOR("Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRV_NAME);


------------------------------------------------------------------------------
Protect Your Site and Customers from Malware Attacks
Learn about various malware tactics and how to avoid them. Understand 
malware threats, the impact they can have on your business, and how you 
can protect your company and customers by using code signing.
http://p.sf.net/sfu/oracle-sfdevnl

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v4] spi: New driver for Altera SPI
       [not found]             ` <1295248436-5049-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-01-17  7:42               ` Grant Likely
       [not found]                 ` <AANLkTinXduw54sKNa8r9sLm_Q8YjvpqAseYPhRshbNpE-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Grant Likely @ 2011-01-17  7:42 UTC (permalink / raw)
  To: Thomas Chou
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On Mon, Jan 17, 2011 at 12:13 AM, Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org> 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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>

Hi Thomas,

Some more comments below.

g.

> ---
> v2 add devicetree support
> v3 remove platform header, as Grant suggested.
>   no irq resource means polling.
> v4 minor cleanup, as Grant suggested.
>
>  drivers/spi/Kconfig      |    6 +
>  drivers/spi/Makefile     |    1 +
>  drivers/spi/spi_altera.c |  389 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 396 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/spi/spi_altera.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 13bfa9d..2b5b8d6 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_ATMEL
>        tristate "Atmel SPI Controller"
>        depends on (ARCH_AT91 || AVR32)
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 3a42463..c2675a4 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_BFIN)                 += spi_bfin5xx.o
>  obj-$(CONFIG_SPI_BITBANG)              += spi_bitbang.o
> diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c
> new file mode 100644
> index 0000000..e4cbd5f
> --- /dev/null
> +++ b/drivers/spi/spi_altera.c
> @@ -0,0 +1,389 @@
> +/*
> + * Altera SPI driver
> + *
> + * Copyright (C) 2008 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/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) {
> +               init_completion(&hw->done);
> +               /* 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;
> +}
> +
> +#ifdef CONFIG_OF
> +static int __devinit altera_spi_of_probe(struct platform_device *pdev,
> +                                      struct altera_spi *hw)
> +{
> +       const __be32 *val;
> +
> +       hw->bitbang.master->dev.of_node = pdev->dev.of_node;
> +       return 0;
> +}
> +#else
> +static int __devinit altera_spi_of_probe(struct platform_device *pdev,
> +                                      struct altera_spi *hw)
> +{
> +       return 0;
> +}
> +#endif

This ifdef/else/endif block is a bit of overkill for just setting the
of_node pointer.  I think in this case single line #ifdef'ed in the
probe routine is acceptable.  I'm also looking at a way to eliminate
the need for drivers to do #ifdef for of_node entirely.

> +
> +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) {
> +               err = altera_spi_of_probe(pdev, hw);
> +               if (err)
> +                       goto err_no_of;
> +       }
> +
> +       /* 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:
> +err_no_of:
> +       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 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;
> +}
> +
> +#ifdef CONFIG_OF
> +static struct of_device_id altera_spi_match[] = {
> +       {
> +               .compatible = "altera,spi_altera",

Ditto to my question about the ps2 driver patch, is there a version
number for this soft core?  Also, underscores '_' are discouraged in
the device tree.  Use '-' instead.

> +       },
> +       {},
> +}
> +MODULE_DEVICE_TABLE(of, altera_spi_match);
> +#endif
> +
> +static struct platform_driver altera_spidrv = {
> +       .remove = __devexit_p(altera_spi_remove),
> +       .driver = {
> +               .name = DRV_NAME,
> +               .owner = THIS_MODULE,
> +               .pm = NULL,
> +#ifdef CONFIG_OF
> +               .of_match_table = altera_spi_match,
> +#endif
> +       },
> +};
> +
> +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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRV_NAME);
> --
> 1.7.3.4
>
>



-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3] spi: New driver for Altera SPI
       [not found]                         ` <4D33EF7C.7030005-w0xpxkV3uqlBDgjK7y7TUQ@public.gmane.org>
@ 2011-01-18  1:33                           ` Thomas Chou
  2011-01-19  1:36                           ` Thomas Chou
  1 sibling, 0 replies; 27+ messages in thread
From: Thomas Chou @ 2011-01-18  1:33 UTC (permalink / raw)
  To: Sinan Akman
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On 01/17/2011 03:27 PM, Sinan Akman wrote:
>> + if (hw->irq >= 0) {
>> + init_completion(&hw->done);
>
> I think you init this twice, once in altera_spi_probe
> below as well.

Hi Sinan,

Thanks. I will remove this one.

- Thomas

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v3] spi: New driver for Altera SPI
       [not found]                         ` <4D33EF7C.7030005-w0xpxkV3uqlBDgjK7y7TUQ@public.gmane.org>
  2011-01-18  1:33                           ` Thomas Chou
@ 2011-01-19  1:36                           ` Thomas Chou
  1 sibling, 0 replies; 27+ messages in thread
From: Thomas Chou @ 2011-01-19  1:36 UTC (permalink / raw)
  To: Sinan Akman
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	Mike Frysinger, devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On 01/17/2011 03:27 PM, Sinan Akman wrote:
>> + if (hw->irq >= 0) {
>> + init_completion(&hw->done);
>
> I think you init this twice, once in altera_spi_probe
> below as well.

I will remove this one, too. Thank you again.

- Thomas

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v5] spi: New driver for Altera SPI
       [not found]                 ` <AANLkTinXduw54sKNa8r9sLm_Q8YjvpqAseYPhRshbNpE-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2011-01-24  6:38                   ` Thomas Chou
  2011-02-03 10:02                     ` [PATCH v6] " Thomas Chou
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-01-24  6:38 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
---
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.

 drivers/spi/Kconfig      |    6 +
 drivers/spi/Makefile     |    1 +
 drivers/spi/spi_altera.c |  360 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 367 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/spi_altera.c

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..70480d5
--- /dev/null
+++ b/drivers/spi/spi_altera.c
@@ -0,0 +1,360 @@
+/*
+ * Altera SPI driver
+ *
+ * Copyright (C) 2008 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/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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.3.4

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v6] spi: New driver for Altera SPI
  2011-01-24  6:38                   ` [PATCH v5] " Thomas Chou
@ 2011-02-03 10:02                     ` Thomas Chou
       [not found]                       ` <1296727326-2581-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-02-03 10:02 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: linux-kernel, nios2-dev, devicetree-discuss, spi-devel-general,
	Thomas Chou

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 <thomas@wytron.com.tw>
---
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 <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/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 <thomas@wytron.com.tw>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.3.5

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v6] spi: New driver for Altera SPI
       [not found]                       ` <1296727326-2581-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-02-03 17:34                         ` Grant Likely
  2011-02-05 13:08                           ` Thomas Chou
  2011-02-05 14:02                           ` [PATCH v7] " Thomas Chou
  0 siblings, 2 replies; 27+ messages in thread
From: Grant Likely @ 2011-02-03 17:34 UTC (permalink / raw)
  To: Thomas Chou
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
> ---
> 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 <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/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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRV_NAME);
> -- 
> 1.7.3.5
> 

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v6] spi: New driver for Altera SPI
  2011-02-03 17:34                         ` Grant Likely
@ 2011-02-05 13:08                           ` Thomas Chou
  2011-02-05 14:02                           ` [PATCH v7] " Thomas Chou
  1 sibling, 0 replies; 27+ messages in thread
From: Thomas Chou @ 2011-02-05 13:08 UTC (permalink / raw)
  To: Grant Likely
  Cc: David Brownell, linux-kernel, nios2-dev, devicetree-discuss,
	spi-devel-general

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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v7] spi: New driver for Altera SPI
  2011-02-03 17:34                         ` Grant Likely
  2011-02-05 13:08                           ` Thomas Chou
@ 2011-02-05 14:02                           ` Thomas Chou
  2011-02-06 20:23                             ` Peter Korsgaard
  1 sibling, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-02-05 14:02 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: linux-kernel, nios2-dev, devicetree-discuss, spi-devel-general,
	Thomas Chou

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 <thomas@wytron.com.tw>
---
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 <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/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 <thomas@wytron.com.tw>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.3.5

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v7] spi: New driver for Altera SPI
  2011-02-05 14:02                           ` [PATCH v7] " Thomas Chou
@ 2011-02-06 20:23                             ` Peter Korsgaard
       [not found]                               ` <87aai8zzd1.fsf-uXGAPMMVk8amE9MCos8gUmSdvHPH+/yF@public.gmane.org>
  2011-02-08  5:21                               ` [PATCH v8] " Thomas Chou
  0 siblings, 2 replies; 27+ messages in thread
From: Peter Korsgaard @ 2011-02-06 20:23 UTC (permalink / raw)
  To: Thomas Chou
  Cc: David Brownell, Grant Likely, devicetree-discuss,
	spi-devel-general, nios2-dev, linux-kernel

>>>>> "Thomas" == Thomas Chou <thomas@wytron.com.tw> 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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v7] spi: New driver for Altera SPI
       [not found]                               ` <87aai8zzd1.fsf-uXGAPMMVk8amE9MCos8gUmSdvHPH+/yF@public.gmane.org>
@ 2011-02-08  2:43                                 ` Thomas Chou
  0 siblings, 0 replies; 27+ messages in thread
From: Thomas Chou @ 2011-02-08  2:43 UTC (permalink / raw)
  To: Peter Korsgaard
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

On 02/07/2011 04:23 AM, Peter Korsgaard wrote:
>>>>>> "Thomas" == Thomas Chou<thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>  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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH v8] spi: New driver for Altera SPI
  2011-02-06 20:23                             ` Peter Korsgaard
       [not found]                               ` <87aai8zzd1.fsf-uXGAPMMVk8amE9MCos8gUmSdvHPH+/yF@public.gmane.org>
@ 2011-02-08  5:21                               ` Thomas Chou
       [not found]                                 ` <1297142509-20158-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  1 sibling, 1 reply; 27+ messages in thread
From: Thomas Chou @ 2011-02-08  5:21 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: linux-kernel, nios2-dev, devicetree-discuss, spi-devel-general,
	Thomas Chou

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 <thomas@wytron.com.tw>
---
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 <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/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 <thomas@wytron.com.tw>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.4

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH v9] spi: New driver for Altera SPI
       [not found]                                 ` <1297142509-20158-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-02-14  2:10                                   ` Thomas Chou
  2011-02-14  2:20                                     ` Ryan Mallon
       [not found]                                     ` <1297649443-11491-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  0 siblings, 2 replies; 27+ messages in thread
From: Thomas Chou @ 2011-02-14  2:10 UTC (permalink / raw)
  To: David Brownell, Grant Likely
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
---
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 <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/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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
-- 
1.7.4

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v9] spi: New driver for Altera SPI
  2011-02-14  2:10                                   ` [PATCH v9] " Thomas Chou
@ 2011-02-14  2:20                                     ` Ryan Mallon
       [not found]                                       ` <4D58917E.9050408-7Wk5F4Od5/oYd5yxfr4S2w@public.gmane.org>
       [not found]                                     ` <1297649443-11491-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  1 sibling, 1 reply; 27+ messages in thread
From: Ryan Mallon @ 2011-02-14  2:20 UTC (permalink / raw)
  To: Thomas Chou
  Cc: David Brownell, Grant Likely, linux-kernel, nios2-dev,
	devicetree-discuss, spi-devel-general

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 <thomas@wytron.com.tw>
> ---

> +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
ryan@bluewatersys.com         	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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v9] spi: New driver for Altera SPI
       [not found]                                       ` <4D58917E.9050408-7Wk5F4Od5/oYd5yxfr4S2w@public.gmane.org>
@ 2011-02-15  7:04                                         ` Thomas Chou
       [not found]                                           ` <4D5A2591.5090901-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
  2011-02-15 19:42                                           ` Grant Likely
  0 siblings, 2 replies; 27+ messages in thread
From: Thomas Chou @ 2011-02-15  7:04 UTC (permalink / raw)
  To: Ryan Mallon
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

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<thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
>> ---
>
>> +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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v9] spi: New driver for Altera SPI
       [not found]                                           ` <4D5A2591.5090901-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-02-15  9:12                                             ` Ryan Mallon
  0 siblings, 0 replies; 27+ messages in thread
From: Ryan Mallon @ 2011-02-15  9:12 UTC (permalink / raw)
  To: Thomas Chou
  Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f

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<thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
>>> ---
>>
>>> +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

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v9] spi: New driver for Altera SPI
  2011-02-15  7:04                                         ` Thomas Chou
       [not found]                                           ` <4D5A2591.5090901-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-02-15 19:42                                           ` Grant Likely
  2011-02-15 21:58                                             ` Ryan Mallon
  1 sibling, 1 reply; 27+ messages in thread
From: Grant Likely @ 2011-02-15 19:42 UTC (permalink / raw)
  To: Thomas Chou
  Cc: Ryan Mallon, David Brownell, linux-kernel, nios2-dev,
	devicetree-discuss, spi-devel-general

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<thomas@wytron.com.tw>
> >>---
> >
> >>+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.

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH v9] spi: New driver for Altera SPI
  2011-02-15 19:42                                           ` Grant Likely
@ 2011-02-15 21:58                                             ` Ryan Mallon
  0 siblings, 0 replies; 27+ messages in thread
From: Ryan Mallon @ 2011-02-15 21:58 UTC (permalink / raw)
  To: Grant Likely
  Cc: Thomas Chou, David Brownell, linux-kernel, nios2-dev,
	devicetree-discuss, spi-devel-general

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<thomas@wytron.com.tw>
>>>> ---
>>>> +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);
 

^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH v9] spi: New driver for Altera SPI
       [not found]                                     ` <1297649443-11491-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
@ 2011-02-16  2:47                                       ` Grant Likely
  0 siblings, 0 replies; 27+ messages in thread
From: Grant Likely @ 2011-02-16  2:47 UTC (permalink / raw)
  To: Thomas Chou
  Cc: devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
	nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH, David Brownell,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA

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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>

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 <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/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 <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:" DRV_NAME);
> -- 
> 1.7.4
> 

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2011-02-16  2:47 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-10-08  6:03 [PATCH] spi: New driver for Altera SPI Thomas Chou
     [not found] ` <1254981838-20584-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-01-17  5:46   ` [PATCH v2] " Thomas Chou
     [not found]     ` <1295243200-28230-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-01-17  5:59       ` Stephen Rothwell
     [not found]         ` <20110117165958.f89e8220.sfr-3FnU+UHB4dNDw9hX6IcOSA@public.gmane.org>
2011-01-17  6:22           ` Thomas Chou
     [not found]             ` <4D33E00E.5050802-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-01-17  6:45               ` Grant Likely
     [not found]                 ` <AANLkTi=D4Rcxbei2oo+EHc6X1O-2Oo+VWHAasK_JLj5W-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-01-17  7:04                   ` [PATCH v3] " Thomas Chou
     [not found]                     ` <1295247869-4968-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-01-17  7:27                       ` Sinan Akman
     [not found]                         ` <4D33EF7C.7030005-w0xpxkV3uqlBDgjK7y7TUQ@public.gmane.org>
2011-01-18  1:33                           ` Thomas Chou
2011-01-19  1:36                           ` Thomas Chou
2011-01-17  6:56       ` [PATCH v2] " Grant Likely
     [not found]         ` <AANLkTimCvYai7mvrrHP9=EXB4L+yw3W2ad-7DU_iEzLy-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-01-17  7:13           ` [PATCH v4] " Thomas Chou
     [not found]             ` <1295248436-5049-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-01-17  7:42               ` Grant Likely
     [not found]                 ` <AANLkTinXduw54sKNa8r9sLm_Q8YjvpqAseYPhRshbNpE-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2011-01-24  6:38                   ` [PATCH v5] " Thomas Chou
2011-02-03 10:02                     ` [PATCH v6] " Thomas Chou
     [not found]                       ` <1296727326-2581-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-02-03 17:34                         ` Grant Likely
2011-02-05 13:08                           ` Thomas Chou
2011-02-05 14:02                           ` [PATCH v7] " Thomas Chou
2011-02-06 20:23                             ` Peter Korsgaard
     [not found]                               ` <87aai8zzd1.fsf-uXGAPMMVk8amE9MCos8gUmSdvHPH+/yF@public.gmane.org>
2011-02-08  2:43                                 ` Thomas Chou
2011-02-08  5:21                               ` [PATCH v8] " Thomas Chou
     [not found]                                 ` <1297142509-20158-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-02-14  2:10                                   ` [PATCH v9] " Thomas Chou
2011-02-14  2:20                                     ` Ryan Mallon
     [not found]                                       ` <4D58917E.9050408-7Wk5F4Od5/oYd5yxfr4S2w@public.gmane.org>
2011-02-15  7:04                                         ` Thomas Chou
     [not found]                                           ` <4D5A2591.5090901-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-02-15  9:12                                             ` Ryan Mallon
2011-02-15 19:42                                           ` Grant Likely
2011-02-15 21:58                                             ` Ryan Mallon
     [not found]                                     ` <1297649443-11491-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
2011-02-16  2:47                                       ` Grant Likely

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).