All of lore.kernel.org
 help / color / mirror / Atom feed
From: Thomas Chou <thomas@wytron.com.tw>
To: David Brownell <dbrownell@users.sourceforge.net>,
	Grant Likely <grant.likely@secretlab.ca>
Cc: linux-kernel@vger.kernel.org, nios2-dev@sopc.et.ntust.edu.tw,
	devicetree-discuss@lists.ozlabs.org,
	spi-devel-general@lists.sourceforge.net,
	Jonas Bonn <jonas@southpole.se>,
	Thomas Chou <thomas@wytron.com.tw>
Subject: [PATCH v4] spi: add OpenCores tiny SPI driver
Date: Thu,  3 Feb 2011 18:37:46 +0800	[thread overview]
Message-ID: <1296729466-2936-1-git-send-email-thomas@wytron.com.tw> (raw)
In-Reply-To: <1295925450-28550-1-git-send-email-thomas@wytron.com.tw>

This patch adds support of OpenCores tiny SPI driver.

http://opencores.org/project,tiny_spi

Signed-off-by: Thomas Chou <thomas@wytron.com.tw>
---
v2 minor cleanup, same as Grant suggest for spi_altera.
v3 rename driver to spi_oc_tiny as Grant suggested.
   set version to tiny-spi-rtlsvn2 as Jonas suggested.
v4 add dts binding doc.

 .../devicetree/bindings/spi/spi_oc_tiny.txt        |   11 +
 drivers/spi/Kconfig                                |    6 +
 drivers/spi/Makefile                               |    1 +
 drivers/spi/spi_oc_tiny.c                          |  396 ++++++++++++++++++++
 include/linux/spi/spi_oc_tiny.h                    |   14 +
 5 files changed, 428 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/spi/spi_oc_tiny.txt
 create mode 100644 drivers/spi/spi_oc_tiny.c
 create mode 100644 include/linux/spi/spi_oc_tiny.h

diff --git a/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt
new file mode 100644
index 0000000..fba0f30
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt
@@ -0,0 +1,11 @@
+OpenCores tiny SPI
+
+Required properties:
+- compatible : should be "opencores,tiny-spi-rtlsvn2".
+Optional properties:
+- clock-frequency : input clock frequency to the core.
+- baud-width: width, in bits, of the programmable divider used to scale
+	the input clock to SCLK.
+
+The clock-frequency and baud-width properties are needed only if the divider
+is programmable. They are not needed if the divider is fixed.
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bb233a9..fbb82ee 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -231,6 +231,12 @@ config SPI_FSL_ESPI
 	  From MPC8536, 85xx platform uses the controller, and all P10xx,
 	  P20xx, P30xx,P40xx, P50xx uses this controller.
 
+config SPI_OC_TINY
+	tristate "OpenCores tiny SPI"
+	select SPI_BITBANG
+	help
+	  This is the driver for OpenCores tiny SPI master controller.
+
 config SPI_OMAP_UWIRE
 	tristate "OMAP1 MicroWire"
 	depends on ARCH_OMAP1
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 86d1b5f..c7e4c23 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SPI_IMX)			+= spi_imx.o
 obj-$(CONFIG_SPI_LM70_LLP)		+= spi_lm70llp.o
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
 obj-$(CONFIG_SPI_PXA2XX_PCI)		+= pxa2xx_spi_pci.o
+obj-$(CONFIG_SPI_OC_TINY)		+= spi_oc_tiny.o
 obj-$(CONFIG_SPI_OMAP_UWIRE)		+= omap_uwire.o
 obj-$(CONFIG_SPI_OMAP24XX)		+= omap2_mcspi.o
 obj-$(CONFIG_SPI_OMAP_100K)		+= omap_spi_100k.o
diff --git a/drivers/spi/spi_oc_tiny.c b/drivers/spi/spi_oc_tiny.c
new file mode 100644
index 0000000..1ea3223
--- /dev/null
+++ b/drivers/spi/spi_oc_tiny.c
@@ -0,0 +1,396 @@
+/*
+ * OpenCores tiny SPI master driver
+ *
+ * http://opencores.org/project,tiny_spi
+ *
+ * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/spi/spi_oc_tiny.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_oc_tiny"
+
+#define TINY_SPI_RXDATA 0
+#define TINY_SPI_TXDATA 4
+#define TINY_SPI_STATUS 8
+#define TINY_SPI_CONTROL 12
+#define TINY_SPI_BAUD 16
+
+#define TINY_SPI_STATUS_TXE 0x1
+#define TINY_SPI_STATUS_TXR 0x2
+
+struct tiny_spi {
+	/* bitbang has to be first */
+	struct spi_bitbang bitbang;
+	struct completion done;
+
+	void __iomem *base;
+	int irq;
+	unsigned int freq;
+	unsigned int baudwidth;
+	unsigned int baud;
+	unsigned int speed_hz;
+	unsigned int mode;
+	unsigned int len;
+	unsigned int txc, rxc;
+	const u8 *txp;
+	u8 *rxp;
+};
+
+static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev)
+{
+	return spi_master_get_devdata(sdev->master);
+}
+
+static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+
+	return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1;
+}
+
+static void tiny_spi_chipselect(struct spi_device *spi, int is_active)
+{
+	gpio_set_value(spi->chip_select,
+		       (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
+}
+
+static int tiny_spi_setup_transfer(struct spi_device *spi,
+				   struct spi_transfer *t)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+	unsigned int baud = hw->baud;
+
+	if (t) {
+		if (t->speed_hz && t->speed_hz != hw->speed_hz)
+			baud = tiny_spi_baud(spi, t->speed_hz);
+	}
+	writel(baud, hw->base + TINY_SPI_BAUD);
+	writel(hw->mode, hw->base + TINY_SPI_CONTROL);
+	return 0;
+}
+
+static int tiny_spi_setup(struct spi_device *spi)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+
+	if (spi->max_speed_hz != hw->speed_hz) {
+		hw->speed_hz = spi->max_speed_hz;
+		hw->baud = tiny_spi_baud(spi, hw->speed_hz);
+	}
+	hw->mode = spi->mode & (SPI_CPOL | SPI_CPHA);
+	return 0;
+}
+
+static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+	const u8 *txp = t->tx_buf;
+	u8 *rxp = t->rx_buf;
+	unsigned int i;
+
+	if (hw->irq >= 0) {
+		/* use intrrupt driven data transfer */
+		hw->len = t->len;
+		hw->txp = t->tx_buf;
+		hw->rxp = t->rx_buf;
+		hw->txc = 0;
+		hw->rxc = 0;
+
+		/* send the first byte */
+		if (t->len > 1) {
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS);
+		} else {
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS);
+		}
+
+		wait_for_completion(&hw->done);
+	} else if (txp && rxp) {
+		/* we need to tighten the transfer loop */
+		writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				u8 rx, tx = *txp++;
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				rx = readb(hw->base + TINY_SPI_TXDATA);
+				writeb(tx, hw->base + TINY_SPI_TXDATA);
+				*rxp++ = rx;
+			}
+			while (!(readb(hw->base + TINY_SPI_STATUS) &
+				 TINY_SPI_STATUS_TXR))
+				cpu_relax();
+			*rxp++ = readb(hw->base + TINY_SPI_TXDATA);
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+		*rxp++ = readb(hw->base + TINY_SPI_RXDATA);
+	} else if (rxp) {
+		writeb(0, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(0,
+			       hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				u8 rx;
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				rx = readb(hw->base + TINY_SPI_TXDATA);
+				writeb(0,
+				       hw->base + TINY_SPI_TXDATA);
+				*rxp++ = rx;
+			}
+			while (!(readb(hw->base + TINY_SPI_STATUS) &
+				 TINY_SPI_STATUS_TXR))
+				cpu_relax();
+			*rxp++ = readb(hw->base + TINY_SPI_TXDATA);
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+		*rxp++ = readb(hw->base + TINY_SPI_RXDATA);
+	} else if (txp) {
+		writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				u8 tx = *txp++;
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				writeb(tx, hw->base + TINY_SPI_TXDATA);
+			}
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+	} else {
+		writeb(0, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(0,
+			       hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				writeb(0,
+				       hw->base + TINY_SPI_TXDATA);
+			}
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+	}
+	return t->len;
+}
+
+static irqreturn_t tiny_spi_irq(int irq, void *dev)
+{
+	struct tiny_spi *hw = dev;
+
+	writeb(0, hw->base + TINY_SPI_STATUS);
+	if (hw->rxc + 1 == hw->len) {
+		if (hw->rxp)
+			*hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA);
+		hw->rxc++;
+		complete(&hw->done);
+	} else {
+		if (hw->rxp)
+			*hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA);
+		hw->rxc++;
+		if (hw->txc < hw->len) {
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(TINY_SPI_STATUS_TXR,
+			       hw->base + TINY_SPI_STATUS);
+		} else {
+			writeb(TINY_SPI_STATUS_TXE,
+			       hw->base + TINY_SPI_STATUS);
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+static int __devinit tiny_spi_probe(struct platform_device *pdev)
+{
+	struct tiny_spi_platform_data *platp = pdev->dev.platform_data;
+	struct tiny_spi *hw;
+	struct spi_master *master;
+	struct resource *res;
+	int err = 0;
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi));
+	if (master == NULL) {
+		dev_err(&pdev->dev, "No memory for spi_master\n");
+		err = -ENOMEM;
+		goto err_no_mem;
+	}
+
+	/* setup the master state. */
+	master->bus_num = pdev->id;
+	master->num_chipselect = 255;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+	master->setup = tiny_spi_setup;
+
+	hw = spi_master_get_devdata(master);
+	platform_set_drvdata(pdev, hw);
+
+	/* setup the state for the bitbang driver */
+	hw->bitbang.master = spi_master_get(master);
+	if (hw->bitbang.master == NULL) {
+		dev_err(&pdev->dev, "Cannot get device\n");
+		err = -ENODEV;
+		goto err_no_dev;
+	}
+	hw->bitbang.setup_transfer = tiny_spi_setup_transfer;
+	hw->bitbang.chipselect = tiny_spi_chipselect;
+	hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs;
+
+	/* find and map our resources */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
+		err = -ENOENT;
+		goto err_no_iores;
+	}
+	hw->base = ioremap(res->start, (res->end - res->start) + 1);
+	if (hw->base == 0) {
+		dev_err(&pdev->dev, "Cannot map IO\n");
+		err = -ENXIO;
+		goto err_no_iomap;
+	}
+	/* irq is optional */
+	hw->irq = platform_get_irq(pdev, 0);
+	if (hw->irq >= 0) {
+		init_completion(&hw->done);
+		err = request_irq(hw->irq, tiny_spi_irq, 0, pdev->name, hw);
+		if (err) {
+			dev_err(&pdev->dev, "Cannot claim IRQ\n");
+			goto err_no_irq;
+		}
+	}
+	/* find platform data */
+	if (platp) {
+		hw->freq = platp->freq;
+		hw->baudwidth = platp->baudwidth;
+	} else {
+#ifdef CONFIG_OF
+		const __be32 *val;
+
+		hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+		val = of_get_property(pdev->dev.of_node,
+				      "clock-frequency", NULL);
+		if (val)
+			hw->freq = be32_to_cpup(val);
+		val = of_get_property(pdev->dev.of_node, "baud-width", NULL);
+		if (val)
+			hw->baudwidth = be32_to_cpup(val);
+#endif
+	}
+
+	/* register our spi controller */
+	err = spi_bitbang_start(&hw->bitbang);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register SPI master\n");
+		goto err_register;
+	}
+	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
+
+	return 0;
+
+err_register:
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+err_no_irq:
+	iounmap(hw->base);
+err_no_iomap:
+err_no_iores:
+	spi_master_put(master);
+err_no_mem:
+err_no_dev:
+	return err;
+}
+
+static int __devexit tiny_spi_remove(struct platform_device *dev)
+{
+	struct tiny_spi *hw = platform_get_drvdata(dev);
+	struct spi_master *master = hw->bitbang.master;
+
+	spi_bitbang_stop(&hw->bitbang);
+
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+	iounmap(hw->base);
+
+	platform_set_drvdata(dev, NULL);
+	spi_master_put(master);
+	return 0;
+}
+
+static const struct of_device_id tiny_spi_match[] = {
+	{ .compatible = "opencores,tiny-spi-rtlsvn2", },
+	{},
+}
+MODULE_DEVICE_TABLE(of, tiny_spi_match);
+
+static struct platform_driver tiny_spidrv = {
+	.remove = __devexit_p(tiny_spi_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.pm = NULL,
+		.of_match_table = tiny_spi_match,
+	},
+};
+
+static int __init tiny_spi_init(void)
+{
+	return platform_driver_probe(&tiny_spidrv, tiny_spi_probe);
+}
+module_init(tiny_spi_init);
+
+static void __exit tiny_spi_exit(void)
+{
+	platform_driver_unregister(&tiny_spidrv);
+}
+module_exit(tiny_spi_exit);
+
+MODULE_DESCRIPTION("OpenCores tiny SPI driver");
+MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/include/linux/spi/spi_oc_tiny.h b/include/linux/spi/spi_oc_tiny.h
new file mode 100644
index 0000000..5080e1c
--- /dev/null
+++ b/include/linux/spi/spi_oc_tiny.h
@@ -0,0 +1,14 @@
+#ifndef _LINUX_SPI_SPI_OC_TINY_H
+#define _LINUX_SPI_SPI_OC_TINY_H
+
+/*
+ * struct tiny_spi_platform_data - platform data of the OpenCores tiny SPI
+ * @freq:	input clock freq to the core.
+ * @baudwidth:	baud rate divider width of the core.
+ */
+struct tiny_spi_platform_data {
+	uint freq;
+	uint baudwidth;
+};
+
+#endif /* _LINUX_SPI_SPI_OC_TINY_H */
-- 
1.7.3.5


WARNING: multiple messages have this Message-ID (diff)
From: Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
To: David Brownell
	<dbrownell-Rn4VEauK+AKRv+LV9MX5uipxlwaOVQ5f@public.gmane.org>,
	Grant Likely
	<grant.likely-s3s/WqlpOiPyB63q8FvJNQ@public.gmane.org>
Cc: nios2-dev-1eJk0qcHJCcaeqlQEoCUNoJY59XmG8rH@public.gmane.org,
	devicetree-discuss-uLR06cmDAlY/bJ5BZ2RsiQ@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f@public.gmane.org
Subject: [PATCH v4] spi: add OpenCores tiny SPI driver
Date: Thu,  3 Feb 2011 18:37:46 +0800	[thread overview]
Message-ID: <1296729466-2936-1-git-send-email-thomas@wytron.com.tw> (raw)
In-Reply-To: <1295925450-28550-1-git-send-email-thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>

This patch adds support of OpenCores tiny SPI driver.

http://opencores.org/project,tiny_spi

Signed-off-by: Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
---
v2 minor cleanup, same as Grant suggest for spi_altera.
v3 rename driver to spi_oc_tiny as Grant suggested.
   set version to tiny-spi-rtlsvn2 as Jonas suggested.
v4 add dts binding doc.

 .../devicetree/bindings/spi/spi_oc_tiny.txt        |   11 +
 drivers/spi/Kconfig                                |    6 +
 drivers/spi/Makefile                               |    1 +
 drivers/spi/spi_oc_tiny.c                          |  396 ++++++++++++++++++++
 include/linux/spi/spi_oc_tiny.h                    |   14 +
 5 files changed, 428 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/spi/spi_oc_tiny.txt
 create mode 100644 drivers/spi/spi_oc_tiny.c
 create mode 100644 include/linux/spi/spi_oc_tiny.h

diff --git a/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt
new file mode 100644
index 0000000..fba0f30
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt
@@ -0,0 +1,11 @@
+OpenCores tiny SPI
+
+Required properties:
+- compatible : should be "opencores,tiny-spi-rtlsvn2".
+Optional properties:
+- clock-frequency : input clock frequency to the core.
+- baud-width: width, in bits, of the programmable divider used to scale
+	the input clock to SCLK.
+
+The clock-frequency and baud-width properties are needed only if the divider
+is programmable. They are not needed if the divider is fixed.
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index bb233a9..fbb82ee 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -231,6 +231,12 @@ config SPI_FSL_ESPI
 	  From MPC8536, 85xx platform uses the controller, and all P10xx,
 	  P20xx, P30xx,P40xx, P50xx uses this controller.
 
+config SPI_OC_TINY
+	tristate "OpenCores tiny SPI"
+	select SPI_BITBANG
+	help
+	  This is the driver for OpenCores tiny SPI master controller.
+
 config SPI_OMAP_UWIRE
 	tristate "OMAP1 MicroWire"
 	depends on ARCH_OMAP1
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 86d1b5f..c7e4c23 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SPI_IMX)			+= spi_imx.o
 obj-$(CONFIG_SPI_LM70_LLP)		+= spi_lm70llp.o
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
 obj-$(CONFIG_SPI_PXA2XX_PCI)		+= pxa2xx_spi_pci.o
+obj-$(CONFIG_SPI_OC_TINY)		+= spi_oc_tiny.o
 obj-$(CONFIG_SPI_OMAP_UWIRE)		+= omap_uwire.o
 obj-$(CONFIG_SPI_OMAP24XX)		+= omap2_mcspi.o
 obj-$(CONFIG_SPI_OMAP_100K)		+= omap_spi_100k.o
diff --git a/drivers/spi/spi_oc_tiny.c b/drivers/spi/spi_oc_tiny.c
new file mode 100644
index 0000000..1ea3223
--- /dev/null
+++ b/drivers/spi/spi_oc_tiny.c
@@ -0,0 +1,396 @@
+/*
+ * OpenCores tiny SPI master driver
+ *
+ * http://opencores.org/project,tiny_spi
+ *
+ * Copyright (C) 2011 Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>
+ *
+ * Based on spi_s3c24xx.c, which is:
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ *	Ben Dooks <ben-Y5A6D6n0/KfQXOPxS62xeg@public.gmane.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/spi/spi_oc_tiny.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+
+#define DRV_NAME "spi_oc_tiny"
+
+#define TINY_SPI_RXDATA 0
+#define TINY_SPI_TXDATA 4
+#define TINY_SPI_STATUS 8
+#define TINY_SPI_CONTROL 12
+#define TINY_SPI_BAUD 16
+
+#define TINY_SPI_STATUS_TXE 0x1
+#define TINY_SPI_STATUS_TXR 0x2
+
+struct tiny_spi {
+	/* bitbang has to be first */
+	struct spi_bitbang bitbang;
+	struct completion done;
+
+	void __iomem *base;
+	int irq;
+	unsigned int freq;
+	unsigned int baudwidth;
+	unsigned int baud;
+	unsigned int speed_hz;
+	unsigned int mode;
+	unsigned int len;
+	unsigned int txc, rxc;
+	const u8 *txp;
+	u8 *rxp;
+};
+
+static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev)
+{
+	return spi_master_get_devdata(sdev->master);
+}
+
+static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+
+	return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1;
+}
+
+static void tiny_spi_chipselect(struct spi_device *spi, int is_active)
+{
+	gpio_set_value(spi->chip_select,
+		       (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
+}
+
+static int tiny_spi_setup_transfer(struct spi_device *spi,
+				   struct spi_transfer *t)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+	unsigned int baud = hw->baud;
+
+	if (t) {
+		if (t->speed_hz && t->speed_hz != hw->speed_hz)
+			baud = tiny_spi_baud(spi, t->speed_hz);
+	}
+	writel(baud, hw->base + TINY_SPI_BAUD);
+	writel(hw->mode, hw->base + TINY_SPI_CONTROL);
+	return 0;
+}
+
+static int tiny_spi_setup(struct spi_device *spi)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+
+	if (spi->max_speed_hz != hw->speed_hz) {
+		hw->speed_hz = spi->max_speed_hz;
+		hw->baud = tiny_spi_baud(spi, hw->speed_hz);
+	}
+	hw->mode = spi->mode & (SPI_CPOL | SPI_CPHA);
+	return 0;
+}
+
+static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct tiny_spi *hw = tiny_spi_to_hw(spi);
+	const u8 *txp = t->tx_buf;
+	u8 *rxp = t->rx_buf;
+	unsigned int i;
+
+	if (hw->irq >= 0) {
+		/* use intrrupt driven data transfer */
+		hw->len = t->len;
+		hw->txp = t->tx_buf;
+		hw->rxp = t->rx_buf;
+		hw->txc = 0;
+		hw->rxc = 0;
+
+		/* send the first byte */
+		if (t->len > 1) {
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS);
+		} else {
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS);
+		}
+
+		wait_for_completion(&hw->done);
+	} else if (txp && rxp) {
+		/* we need to tighten the transfer loop */
+		writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				u8 rx, tx = *txp++;
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				rx = readb(hw->base + TINY_SPI_TXDATA);
+				writeb(tx, hw->base + TINY_SPI_TXDATA);
+				*rxp++ = rx;
+			}
+			while (!(readb(hw->base + TINY_SPI_STATUS) &
+				 TINY_SPI_STATUS_TXR))
+				cpu_relax();
+			*rxp++ = readb(hw->base + TINY_SPI_TXDATA);
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+		*rxp++ = readb(hw->base + TINY_SPI_RXDATA);
+	} else if (rxp) {
+		writeb(0, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(0,
+			       hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				u8 rx;
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				rx = readb(hw->base + TINY_SPI_TXDATA);
+				writeb(0,
+				       hw->base + TINY_SPI_TXDATA);
+				*rxp++ = rx;
+			}
+			while (!(readb(hw->base + TINY_SPI_STATUS) &
+				 TINY_SPI_STATUS_TXR))
+				cpu_relax();
+			*rxp++ = readb(hw->base + TINY_SPI_TXDATA);
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+		*rxp++ = readb(hw->base + TINY_SPI_RXDATA);
+	} else if (txp) {
+		writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(*txp++, hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				u8 tx = *txp++;
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				writeb(tx, hw->base + TINY_SPI_TXDATA);
+			}
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+	} else {
+		writeb(0, hw->base + TINY_SPI_TXDATA);
+		if (t->len > 1) {
+			writeb(0,
+			       hw->base + TINY_SPI_TXDATA);
+			for (i = 2; i < t->len; i++) {
+				while (!(readb(hw->base + TINY_SPI_STATUS) &
+					 TINY_SPI_STATUS_TXR))
+					cpu_relax();
+				writeb(0,
+				       hw->base + TINY_SPI_TXDATA);
+			}
+		}
+		while (!(readb(hw->base + TINY_SPI_STATUS) &
+			 TINY_SPI_STATUS_TXE))
+			cpu_relax();
+	}
+	return t->len;
+}
+
+static irqreturn_t tiny_spi_irq(int irq, void *dev)
+{
+	struct tiny_spi *hw = dev;
+
+	writeb(0, hw->base + TINY_SPI_STATUS);
+	if (hw->rxc + 1 == hw->len) {
+		if (hw->rxp)
+			*hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA);
+		hw->rxc++;
+		complete(&hw->done);
+	} else {
+		if (hw->rxp)
+			*hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA);
+		hw->rxc++;
+		if (hw->txc < hw->len) {
+			writeb(hw->txp ? *hw->txp++ : 0,
+			       hw->base + TINY_SPI_TXDATA);
+			hw->txc++;
+			writeb(TINY_SPI_STATUS_TXR,
+			       hw->base + TINY_SPI_STATUS);
+		} else {
+			writeb(TINY_SPI_STATUS_TXE,
+			       hw->base + TINY_SPI_STATUS);
+		}
+	}
+	return IRQ_HANDLED;
+}
+
+static int __devinit tiny_spi_probe(struct platform_device *pdev)
+{
+	struct tiny_spi_platform_data *platp = pdev->dev.platform_data;
+	struct tiny_spi *hw;
+	struct spi_master *master;
+	struct resource *res;
+	int err = 0;
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi));
+	if (master == NULL) {
+		dev_err(&pdev->dev, "No memory for spi_master\n");
+		err = -ENOMEM;
+		goto err_no_mem;
+	}
+
+	/* setup the master state. */
+	master->bus_num = pdev->id;
+	master->num_chipselect = 255;
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+	master->setup = tiny_spi_setup;
+
+	hw = spi_master_get_devdata(master);
+	platform_set_drvdata(pdev, hw);
+
+	/* setup the state for the bitbang driver */
+	hw->bitbang.master = spi_master_get(master);
+	if (hw->bitbang.master == NULL) {
+		dev_err(&pdev->dev, "Cannot get device\n");
+		err = -ENODEV;
+		goto err_no_dev;
+	}
+	hw->bitbang.setup_transfer = tiny_spi_setup_transfer;
+	hw->bitbang.chipselect = tiny_spi_chipselect;
+	hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs;
+
+	/* find and map our resources */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
+		err = -ENOENT;
+		goto err_no_iores;
+	}
+	hw->base = ioremap(res->start, (res->end - res->start) + 1);
+	if (hw->base == 0) {
+		dev_err(&pdev->dev, "Cannot map IO\n");
+		err = -ENXIO;
+		goto err_no_iomap;
+	}
+	/* irq is optional */
+	hw->irq = platform_get_irq(pdev, 0);
+	if (hw->irq >= 0) {
+		init_completion(&hw->done);
+		err = request_irq(hw->irq, tiny_spi_irq, 0, pdev->name, hw);
+		if (err) {
+			dev_err(&pdev->dev, "Cannot claim IRQ\n");
+			goto err_no_irq;
+		}
+	}
+	/* find platform data */
+	if (platp) {
+		hw->freq = platp->freq;
+		hw->baudwidth = platp->baudwidth;
+	} else {
+#ifdef CONFIG_OF
+		const __be32 *val;
+
+		hw->bitbang.master->dev.of_node = pdev->dev.of_node;
+		val = of_get_property(pdev->dev.of_node,
+				      "clock-frequency", NULL);
+		if (val)
+			hw->freq = be32_to_cpup(val);
+		val = of_get_property(pdev->dev.of_node, "baud-width", NULL);
+		if (val)
+			hw->baudwidth = be32_to_cpup(val);
+#endif
+	}
+
+	/* register our spi controller */
+	err = spi_bitbang_start(&hw->bitbang);
+	if (err) {
+		dev_err(&pdev->dev, "Failed to register SPI master\n");
+		goto err_register;
+	}
+	dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq);
+
+	return 0;
+
+err_register:
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+err_no_irq:
+	iounmap(hw->base);
+err_no_iomap:
+err_no_iores:
+	spi_master_put(master);
+err_no_mem:
+err_no_dev:
+	return err;
+}
+
+static int __devexit tiny_spi_remove(struct platform_device *dev)
+{
+	struct tiny_spi *hw = platform_get_drvdata(dev);
+	struct spi_master *master = hw->bitbang.master;
+
+	spi_bitbang_stop(&hw->bitbang);
+
+	if (hw->irq >= 0)
+		free_irq(hw->irq, hw);
+	iounmap(hw->base);
+
+	platform_set_drvdata(dev, NULL);
+	spi_master_put(master);
+	return 0;
+}
+
+static const struct of_device_id tiny_spi_match[] = {
+	{ .compatible = "opencores,tiny-spi-rtlsvn2", },
+	{},
+}
+MODULE_DEVICE_TABLE(of, tiny_spi_match);
+
+static struct platform_driver tiny_spidrv = {
+	.remove = __devexit_p(tiny_spi_remove),
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.pm = NULL,
+		.of_match_table = tiny_spi_match,
+	},
+};
+
+static int __init tiny_spi_init(void)
+{
+	return platform_driver_probe(&tiny_spidrv, tiny_spi_probe);
+}
+module_init(tiny_spi_init);
+
+static void __exit tiny_spi_exit(void)
+{
+	platform_driver_unregister(&tiny_spidrv);
+}
+module_exit(tiny_spi_exit);
+
+MODULE_DESCRIPTION("OpenCores tiny SPI driver");
+MODULE_AUTHOR("Thomas Chou <thomas-SDxUXYEhEBiCuPEqFHbRBg@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/include/linux/spi/spi_oc_tiny.h b/include/linux/spi/spi_oc_tiny.h
new file mode 100644
index 0000000..5080e1c
--- /dev/null
+++ b/include/linux/spi/spi_oc_tiny.h
@@ -0,0 +1,14 @@
+#ifndef _LINUX_SPI_SPI_OC_TINY_H
+#define _LINUX_SPI_SPI_OC_TINY_H
+
+/*
+ * struct tiny_spi_platform_data - platform data of the OpenCores tiny SPI
+ * @freq:	input clock freq to the core.
+ * @baudwidth:	baud rate divider width of the core.
+ */
+struct tiny_spi_platform_data {
+	uint freq;
+	uint baudwidth;
+};
+
+#endif /* _LINUX_SPI_SPI_OC_TINY_H */
-- 
1.7.3.5

  reply	other threads:[~2011-02-03 10:37 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <1294645208-6322-1-git-send-email-thomas@wytron.com.tw>
2011-01-12  2:41 ` [PATCH v2] spi: add OpenCores tiny SPI driver Thomas Chou
2011-01-12  2:41   ` Thomas Chou
2011-01-17  7:32   ` Thomas Chou
2011-01-17  7:32     ` Thomas Chou
2011-01-20 16:54     ` Grant Likely
2011-01-20 16:54       ` Grant Likely
2011-01-20 21:36       ` Thomas Chou
2011-01-21 12:27         ` Jonas Bonn
2011-01-21 12:27           ` Jonas Bonn
2011-01-24  1:19           ` Thomas Chou
2011-01-24  1:19             ` Thomas Chou
2011-01-24 10:59             ` Jonas Bonn
2011-01-24 10:59               ` Jonas Bonn
2011-01-24 14:50               ` Thomas Chou
2011-01-24 14:50                 ` Thomas Chou
2011-01-24 15:50                 ` Grant Likely
2011-01-24 15:50                   ` Grant Likely
2011-01-25  3:17               ` [PATCH v3] " Thomas Chou
2011-01-25  3:17                 ` Thomas Chou
2011-02-03 10:37                 ` Thomas Chou [this message]
2011-02-03 10:37                   ` [PATCH v4] " Thomas Chou
2011-02-03 11:03                   ` Wolfram Sang
2011-02-03 15:20                   ` Dirk Brandewie

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1296729466-2936-1-git-send-email-thomas@wytron.com.tw \
    --to=thomas@wytron.com.tw \
    --cc=dbrownell@users.sourceforge.net \
    --cc=devicetree-discuss@lists.ozlabs.org \
    --cc=grant.likely@secretlab.ca \
    --cc=jonas@southpole.se \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nios2-dev@sopc.et.ntust.edu.tw \
    --cc=spi-devel-general@lists.sourceforge.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.