lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1240916464-4187-1-git-send-email-thierry.reding@avionic-design.de>
Date:	Tue, 28 Apr 2009 13:01:04 +0200
From:	Thierry Reding <thierry.reding@...onic-design.de>
To:	David Brownell <david-b@...bell.net>
Cc:	spi-devel-general@...ts.sourceforge.net,
	linux-kernel@...r.kernel.org
Subject: [PATCH v2] spi: Add support for the OpenCores SPI controller.

This patch adds a platform device driver that supports the OpenCores SPI
controller.

The driver expects two resources: an IORESOURCE_MEM resource defining the
core's memory-mapped registers and an IORESOURCE_IRQ for the associated
interrupt. It also requires a clock, "spi-master-clk", used to compute the
clock divider.

Signed-off-by: Thierry Reding <thierry.reding@...onic-design.de>

---
 drivers/spi/Kconfig  |    5 +
 drivers/spi/Makefile |    1 +
 drivers/spi/spioc.c  |  633 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 639 insertions(+), 0 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 83a185d..ff76d29 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -151,6 +151,11 @@ config SPI_MPC83xx
 	  technology. This driver uses a simple set of shift registers for data
 	  (opposed to the CPM based descriptor model).
 
+config SPI_OCORES
+	tristate "OpenCores SPI Controller"
+	help
+	  This enables using the OpenCores SPI controller.
+
 config SPI_OMAP_UWIRE
 	tristate "OMAP1 MicroWire"
 	depends on ARCH_OMAP1
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 5d04519..7802d0c 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_SPI_S3C24XX)		+= spi_s3c24xx.o
 obj-$(CONFIG_SPI_TXX9)			+= spi_txx9.o
 obj-$(CONFIG_SPI_XILINX)		+= xilinx_spi.o
 obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.o
+obj-$(CONFIG_SPI_OCORES)		+= spioc.o
 # 	... add above this line ...
 
 # SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/spioc.c b/drivers/spi/spioc.c
new file mode 100644
index 0000000..231d43c
--- /dev/null
+++ b/drivers/spi/spioc.c
@@ -0,0 +1,633 @@
+/*
+ * linux/drivers/spi/spioc.c
+ *
+ * Copyright (C) 2007-2008 Avionic Design Development GmbH
+ * Copyright (C) 2008-2009 Avionic Design GmbH
+ *
+ * Partially inspired by code from linux/drivers/spi/pxa2xx_spi.c.
+ *
+ * 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.
+ *
+ * Written by Thierry Reding <thierry.reding@...onic-design.de>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+
+/* register definitions */
+#define	SPIOC_RX(i)	(i * 4)
+#define	SPIOC_TX(i)	(i * 4)
+#define	SPIOC_CTRL	0x10
+#define	SPIOC_DIV	0x14
+#define	SPIOC_SS	0x18
+
+/* SPIOC_CTRL register */
+#define	CTRL_LEN(x)	((x < 128) ? x : 0)
+#define	CTRL_BUSY	(1 <<  8)
+#define	CTRL_RXNEG	(1 <<  9)
+#define	CTRL_TXNEG	(1 << 10)
+#define	CTRL_LSB	(1 << 11)
+#define	CTRL_IE		(1 << 12)
+#define	CTRL_ASS	(1 << 13)
+
+#define	SPIOC_MAX_LEN	((unsigned int)16)
+
+static const u32 clock_mode[4] = {
+	[SPI_MODE_0] = CTRL_TXNEG,
+	[SPI_MODE_1] = CTRL_RXNEG,
+	[SPI_MODE_2] = CTRL_TXNEG,
+	[SPI_MODE_3] = CTRL_RXNEG,
+};
+
+/* valid SPI mode bits */
+#define	MODEBITS	(SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST)
+
+struct spioc_ctldata {
+	u32 ctrl;
+	u32 div;
+};
+
+struct spioc {
+	struct spi_master *master;
+	void __iomem *base;
+	struct clk *clk;
+	int irq;
+
+	struct workqueue_struct *workqueue;
+	struct work_struct work;
+	struct tasklet_struct tasklet;
+
+	struct list_head queue;
+	struct completion msg_done;
+	unsigned int state;
+	unsigned int busy;
+	spinlock_t lock;
+
+	struct spi_device *slave;
+	struct spi_message *message;
+	struct spi_transfer *transfer;
+	unsigned int cur_pos;
+	unsigned int cur_len;
+};
+
+/* queue states */
+#define	QUEUE_STOPPED	0
+#define	QUEUE_RUNNING	1
+
+static inline u32 spioc_read(struct spioc *spioc, unsigned long offset)
+{
+	return ioread32(spioc->base + offset);
+}
+
+static inline void spioc_write(struct spioc *spioc, unsigned offset,
+		u32 value)
+{
+	iowrite32(value, spioc->base + offset);
+}
+
+static u32 spioc_get_clkdiv(struct spioc *spioc, unsigned long speed)
+{
+	unsigned long rate = clk_get_rate(spioc->clk);
+	return DIV_ROUND_UP(rate, 2 * speed) - 1;
+}
+
+static void spioc_chipselect(struct spioc *spioc, struct spi_device *spi)
+{
+	if (spi != spioc->slave) {
+		u32 ss = spi ? (1 << spi->chip_select) : 0;
+		spioc_write(spioc, SPIOC_SS, ss);
+		spioc->slave = spi;
+	}
+}
+
+static void spioc_copy_tx(struct spioc *spioc)
+{
+	const void *src;
+	u32 val = 0;
+	int i;
+
+	if (!spioc->transfer->tx_buf)
+		return;
+
+	src = spioc->transfer->tx_buf + spioc->cur_pos;
+
+	for (i = 0; i < spioc->cur_len; i++) {
+		int rem = spioc->cur_len - i;
+		int reg = (rem - 1) / 4;
+		int ofs = (rem - 1) % 4;
+
+		val |= (((u8 *)src)[i] & 0xff) << (ofs * 8);
+		if (!ofs) {
+			spioc_write(spioc, SPIOC_TX(reg), val);
+			val = 0;
+		}
+	}
+}
+
+static void spioc_copy_rx(struct spioc *spioc)
+{
+	void *dest;
+	u32 val = 0;
+	int i;
+
+	if (!spioc->transfer->rx_buf)
+		return;
+
+	dest = spioc->transfer->rx_buf + spioc->cur_pos;
+
+	for (i = 0; i < spioc->cur_len; i++) {
+		int rem = spioc->cur_len - i;
+		int reg = (rem - 1) / 4;
+		int ofs = (rem - 1) % 4;
+
+		if ((i == 0) || (rem % 4 == 0))
+			val = spioc_read(spioc, SPIOC_RX(reg));
+
+		((u8 *)dest)[i] = (val >> (ofs * 8)) & 0xff;
+	}
+}
+
+static inline struct spi_transfer *next_transfer(struct list_head *head)
+{
+	return list_entry(head->next, struct spi_transfer, transfer_list);
+}
+
+static void finish_message(struct spioc *spioc, int ec)
+{
+	struct spi_message *message = spioc->message;
+
+	spioc->transfer = NULL;
+	spioc->message = NULL;
+
+	message->status = ec;
+
+	if (message->complete)
+		message->complete(message->context);
+}
+
+static void queue_message(struct spioc *spioc)
+{
+	if (spioc->state == QUEUE_RUNNING)
+		queue_work(spioc->workqueue, &spioc->work);
+
+	if (spioc->state == QUEUE_STOPPED)
+		complete(&spioc->msg_done);
+}
+
+static struct spi_transfer *continue_message(struct spioc *spioc)
+{
+	struct spi_transfer *transfer = spioc->transfer;
+	struct spi_message *message = spioc->message;
+	unsigned long flags;
+
+	if (!transfer)
+		return next_transfer(&message->transfers);
+
+	if (transfer->transfer_list.next != &message->transfers)
+		return next_transfer(&transfer->transfer_list);
+
+	spin_lock_irqsave(&spioc->lock, flags);
+	finish_message(spioc, 0);
+	queue_message(spioc);
+	spin_unlock_irqrestore(&spioc->lock, flags);
+
+	return NULL;
+}
+
+static void process_transfers(unsigned long data)
+{
+	struct spioc *spioc = (struct spioc *)data;
+	struct spi_transfer *transfer;
+	struct spi_message *message;
+	struct spioc_ctldata *ctl;
+	u32 ctrl = 0;
+	u32 div = 0;
+
+	WARN_ON(spioc->message == NULL);
+	message = spioc->message;
+	transfer = spioc->transfer;
+
+	/* finish up the last partial transfer */
+	if (transfer) {
+		spioc_copy_rx(spioc);
+		spioc->message->actual_length += spioc->cur_len;
+		spioc->cur_pos += spioc->cur_len;
+	}
+
+	/* proceed to next (or first) transfer in message */
+	if (!transfer || (spioc->cur_pos >= transfer->len)) {
+		if (transfer) {
+			if (transfer->delay_usecs)
+				udelay(transfer->delay_usecs);
+
+			if (!transfer->cs_change)
+				spioc_chipselect(spioc, NULL);
+		}
+
+		transfer = continue_message(spioc);
+		if (!transfer)
+			return;
+
+		spioc->transfer = transfer;
+		spioc->cur_pos = 0;
+		spioc->cur_len = 0;
+	}
+
+	ctl = spi_get_ctldata(message->spi);
+	div = ctl->div;
+
+	if (transfer->speed_hz) {
+		div = spioc_get_clkdiv(spioc, transfer->speed_hz);
+		if (div > 0xffff) {
+			finish_message(spioc, -EIO);
+			return;
+		}
+	}
+
+	spioc->cur_len = min(transfer->len - spioc->cur_pos, SPIOC_MAX_LEN);
+	spioc_copy_tx(spioc);
+
+	ctrl = ctl->ctrl;
+	ctrl |= CTRL_LEN(spioc->cur_len * 8);
+	ctrl |= CTRL_BUSY;
+	ctrl |= CTRL_IE;
+
+	spioc_chipselect(spioc, spioc->message->spi);
+	spioc_write(spioc, SPIOC_DIV, div);
+	spioc_write(spioc, SPIOC_CTRL, ctrl);
+}
+
+static void process_messages(struct work_struct *work)
+{
+	struct spioc *spioc = container_of(work, struct spioc, work);
+	struct spi_message *message;
+	unsigned long flags;
+
+	WARN_ON(spioc->message != NULL);
+
+	spin_lock_irqsave(&spioc->lock, flags);
+
+	if (list_empty(&spioc->queue)) {
+		spioc->busy = 0;
+		spin_unlock_irqrestore(&spioc->lock, flags);
+		return;
+	}
+
+	message = list_entry(spioc->queue.next, struct spi_message, queue);
+	list_del_init(&message->queue);
+	spioc->message = message;
+	tasklet_schedule(&spioc->tasklet);
+	spioc->busy = 1;
+
+	spin_unlock_irqrestore(&spioc->lock, flags);
+}
+
+static int spioc_setup(struct spi_device *spi)
+{
+	struct spioc *spioc = spi_master_get_devdata(spi->master);
+	struct spioc_ctldata *ctl = spi_get_ctldata(spi);
+	u32 div = 0;
+
+	if (spi->mode & ~MODEBITS)
+		return -EINVAL;
+
+	if (!spi->max_speed_hz)
+		return -EINVAL;
+
+	div = spioc_get_clkdiv(spioc, spi->max_speed_hz);
+	if (div > 0xffff)
+		return -EINVAL;
+
+	if (!ctl) {
+		ctl = kzalloc(sizeof(*ctl), GFP_KERNEL);
+		if (!ctl)
+			return -EINVAL;
+
+		spi_set_ctldata(spi, ctl);
+	}
+
+	ctl->div = div;
+	ctl->ctrl = 0;
+
+	if (spi->mode & SPI_LSB_FIRST)
+		ctl->ctrl |= CTRL_LSB;
+
+	ctl->ctrl |= clock_mode[spi->mode & 0x3];
+
+	return 0;
+}
+
+static int spioc_transfer(struct spi_device *spi, struct spi_message *message)
+{
+	struct spioc *spioc = spi_master_get_devdata(spi->master);
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&spioc->lock, flags);
+
+	if (spioc->state == QUEUE_STOPPED) {
+		spin_unlock_irqrestore(&spioc->lock, flags);
+		return -ESHUTDOWN;
+	}
+
+	message->status = -EINPROGRESS;
+	message->actual_length = 0;
+
+	list_add_tail(&message->queue, &spioc->queue);
+
+	if ((spioc->state == QUEUE_RUNNING) && !spioc->busy)
+		queue_work(spioc->workqueue, &spioc->work);
+
+	spin_unlock_irqrestore(&spioc->lock, flags);
+
+	return 0;
+}
+
+static void spioc_cleanup(struct spi_device *spi)
+{
+	struct spioc_ctldata *ctl = spi_get_ctldata(spi);
+	spi_set_ctldata(spi, NULL);
+	kfree(ctl);
+}
+
+static irqreturn_t spioc_interrupt(int irq, void *dev_id)
+{
+	struct spi_master *master = dev_id;
+	struct spioc *spioc;
+
+	if (!dev_id)
+		return IRQ_NONE;
+
+	spioc = spi_master_get_devdata(master);
+	tasklet_schedule(&spioc->tasklet);
+
+	return IRQ_HANDLED;
+}
+
+static int init_queue(struct spi_master *master)
+{
+	struct spioc *spioc = spi_master_get_devdata(master);
+
+	spioc->workqueue = create_workqueue(dev_name(&master->dev));
+	if (!spioc->workqueue)
+		return -ENOMEM;
+
+	INIT_WORK(&spioc->work, process_messages);
+	tasklet_init(&spioc->tasklet, process_transfers,
+			(unsigned long)spioc);
+
+	INIT_LIST_HEAD(&spioc->queue);
+	init_completion(&spioc->msg_done);
+	spin_lock_init(&spioc->lock);
+
+	spioc->state = QUEUE_STOPPED;
+	spioc->busy = 0;
+
+	return 0;
+}
+
+static int start_queue(struct spi_master *master)
+{
+	struct spioc *spioc = spi_master_get_devdata(master);
+	unsigned long flags;
+
+	spin_lock_irqsave(&spioc->lock, flags);
+
+	if ((spioc->state == QUEUE_RUNNING) || spioc->busy) {
+		spin_unlock_irqrestore(&spioc->lock, flags);
+		return -EBUSY;
+	}
+
+	spioc->state = QUEUE_RUNNING;
+	spioc->message = NULL;
+	spioc->transfer = NULL;
+
+	spin_unlock_irqrestore(&spioc->lock, flags);
+
+	queue_work(spioc->workqueue, &spioc->work);
+	return 0;
+}
+
+static int stop_queue(struct spi_master *master)
+{
+	struct spioc *spioc = spi_master_get_devdata(master);
+	unsigned long flags;
+	unsigned int empty;
+	unsigned int busy;
+
+	spin_lock_irqsave(&spioc->lock, flags);
+
+	empty = list_empty(&spioc->queue);
+	spioc->state = QUEUE_STOPPED;
+	busy = spioc->busy;
+
+	spin_unlock_irqrestore(&spioc->lock, flags);
+
+	if (!empty && busy)
+		wait_for_completion(&spioc->msg_done);
+
+	return 0;
+}
+
+static int destroy_queue(struct spi_master *master)
+{
+	struct spioc *spioc = spi_master_get_devdata(master);
+	int ret;
+
+	ret = stop_queue(master);
+	if (ret < 0)
+		return ret;
+
+	destroy_workqueue(spioc->workqueue);
+
+	return 0;
+}
+
+static int __devinit spioc_probe(struct platform_device *pdev)
+{
+	struct resource *res = NULL;
+	int irq = 0;
+	void __iomem *mmio = NULL;
+	struct spi_master *master = NULL;
+	struct spioc *spioc = NULL;
+	int ret = 0;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(&pdev->dev, "MMIO resource not defined\n");
+		return -ENXIO;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "IRQ not defined\n");
+		return -ENXIO;
+	}
+
+	res = devm_request_mem_region(&pdev->dev, res->start,
+			resource_size(res), res->name);
+	if (!res) {
+		dev_err(&pdev->dev, "failed to request memory region\n");
+		return -ENXIO;
+	}
+
+	mmio = devm_ioremap_nocache(&pdev->dev, res->start,
+			resource_size(res));
+	if (!mmio) {
+		dev_err(&pdev->dev, "failed to remap I/O region\n");
+		ret = -ENXIO;
+		goto free;
+	}
+
+	master = spi_alloc_master(&pdev->dev, sizeof(struct spioc));
+	if (!master) {
+		dev_err(&pdev->dev, "failed to allocate SPI master\n");
+		ret = -ENOMEM;
+		goto free;
+	}
+
+	master->setup = spioc_setup;
+	master->transfer = spioc_transfer;
+	master->cleanup = spioc_cleanup;
+
+	spioc = spi_master_get_devdata(master);
+	if (!spioc) {
+		ret = -ENXIO;
+		goto free;
+	}
+
+	spioc->master = master;
+	spioc->base = mmio;
+	spioc->irq = irq;
+	spioc->slave = NULL;
+
+	ret = init_queue(master);
+	if (ret < 0) {
+		dev_err(&master->dev, "failed to initialize message queue\n");
+		goto free;
+	}
+
+	ret = start_queue(master);
+	if (ret < 0) {
+		dev_err(&master->dev, "failed to start message queue\n");
+		goto free;
+	}
+
+	spioc->clk = clk_get(NULL, "spi-master-clk");
+	if (IS_ERR(spioc->clk)) {
+		dev_err(&master->dev, "SPI master clock undefined\n");
+		ret = PTR_ERR(spioc->clk);
+		spioc->clk = NULL;
+		goto free;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, spioc_interrupt, IRQF_SHARED,
+			"spioc", master);
+	if (ret) {
+		dev_err(&master->dev, "failed to install handler for "
+				"IRQ%d\n", irq);
+		goto put_clock;
+	}
+
+	/* set SPI bus number and number of chipselects */
+	master->bus_num = pdev->id;
+	master->num_chipselect = 8;
+
+	ret = spi_register_master(master);
+	if (ret) {
+		dev_err(&master->dev, "failed to register SPI master\n");
+		goto put_clock;
+	}
+
+	platform_set_drvdata(pdev, master);
+	return ret;
+
+put_clock:
+	clk_put(spioc->clk);
+free:
+	spi_master_put(master);
+	return ret;
+}
+
+static int __devexit spioc_remove(struct platform_device *pdev)
+{
+	struct spi_master *master = platform_get_drvdata(pdev);
+	struct spioc *spioc = spi_master_get_devdata(master);
+
+	spi_master_get(master);
+	platform_set_drvdata(pdev, NULL);
+	spi_unregister_master(master);
+	destroy_queue(master);
+	clk_put(spioc->clk);
+	spi_master_put(master);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int spioc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct spi_master *master;
+	int ret = 0;
+
+	master = platform_get_drvdata(pdev);
+
+	ret = stop_queue(master);
+	if (ret < 0)
+		dev_err(&master->dev, "failed to stop queue\n");
+
+	return ret;
+}
+
+static int spioc_resume(struct platform_device *pdev)
+{
+	struct spi_master *master;
+	int ret = 0;
+
+	master = platform_get_drvdata(pdev);
+
+	ret = start_queue(master);
+	if (ret < 0)
+		dev_err(&master->dev, "failed to start queue\n");
+
+	return ret;
+}
+#else
+#define spioc_suspend NULL
+#define spioc_resume  NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver spioc_driver = {
+	.probe = spioc_probe,
+	.remove = __devexit_p(spioc_remove),
+	.suspend = spioc_suspend,
+	.resume = spioc_resume,
+	.driver = {
+		.name  = "spioc",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init spioc_init(void)
+{
+	return platform_driver_register(&spioc_driver);
+}
+
+static void __exit spioc_exit(void)
+{
+	platform_driver_unregister(&spioc_driver);
+}
+
+module_init(spioc_init);
+module_exit(spioc_exit);
+
+MODULE_AUTHOR("Thierry Reding <thierry.reding@...onic-design.de>");
+MODULE_DESCRIPTION("OpenCores SPI controller driver");
+MODULE_LICENSE("GPL v2");
+
-- 
tg: (f2deb5b..) adx/spi/spioc (depends on: adx/master)
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ