+ spi-new-orion_spi-driver.patch added to -mm tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The patch titled
     spi: new orion_spi driver
has been added to the -mm tree.  Its filename is
     spi-new-orion_spi-driver.patch

Before you just go and hit "reply", please:
   a) Consider who else should be cc'ed
   b) Prefer to cc a suitable mailing list as well
   c) Ideally: find the original patch on the mailing list and do a
      reply-to-all to that, adding suitable additional cc's

*** Remember to use Documentation/SubmitChecklist when testing your code ***

See http://www.zip.com.au/~akpm/linux/patches/stuff/added-to-mm.txt to find
out what to do about this

The current -mm tree may be found at http://userweb.kernel.org/~akpm/mmotm/

------------------------------------------------------
Subject: spi: new orion_spi driver
From: Shadi Ammouri <shadi@xxxxxxxxxxx>

This adds an SPI driver for the SPI controller found in various Marvell
Orion ARM SoCs.  It currently supports only one slave, which must use SPI
mode 0.

[dbrownell@xxxxxxxxxxxxxxxxxxxxx: cleanups, meet specs, pass "sparse"]
Signed-off-by: Shadi Ammouri <shadi@xxxxxxxxxxx>
Signed-off-by: Saeed Bishara <saeed@xxxxxxxxxxx>
Signed-off-by: Lennert Buytenhek <buytenh@xxxxxxxxxxx>
Signed-off-by: David Brownell <dbrownell@xxxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
---

 drivers/spi/Kconfig           |    6 
 drivers/spi/Makefile          |    1 
 drivers/spi/orion_spi.c       |  572 ++++++++++++++++++++++++++++++++
 include/linux/spi/orion_spi.h |   17 
 4 files changed, 596 insertions(+)

diff -puN drivers/spi/Kconfig~spi-new-orion_spi-driver drivers/spi/Kconfig
--- a/drivers/spi/Kconfig~spi-new-orion_spi-driver
+++ a/drivers/spi/Kconfig
@@ -149,6 +149,12 @@ config SPI_OMAP24XX
 	  SPI master controller for OMAP24xx/OMAP34xx Multichannel SPI
 	  (McSPI) modules.
 
+config SPI_ORION
+	tristate "Orion SPI master (EXPERIMENTAL)"
+	depends on PLAT_ORION && EXPERIMENTAL
+	help
+	  This enables using the SPI master controller on the Orion chips.
+
 config SPI_PXA2XX
 	tristate "PXA2xx SSP SPI master"
 	depends on ARCH_PXA && EXPERIMENTAL
diff -puN drivers/spi/Makefile~spi-new-orion_spi-driver drivers/spi/Makefile
--- a/drivers/spi/Makefile~spi-new-orion_spi-driver
+++ a/drivers/spi/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_SPI_LM70_LLP)		+= spi_lm70l
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
 obj-$(CONFIG_SPI_OMAP_UWIRE)		+= omap_uwire.o
 obj-$(CONFIG_SPI_OMAP24XX)		+= omap2_mcspi.o
+obj-$(CONFIG_SPI_ORION)			+= orion_spi.o
 obj-$(CONFIG_SPI_MPC52xx_PSC)		+= mpc52xx_psc_spi.o
 obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 obj-$(CONFIG_SPI_S3C24XX_GPIO)		+= spi_s3c24xx_gpio.o
diff -puN /dev/null drivers/spi/orion_spi.c
--- /dev/null
+++ a/drivers/spi/orion_spi.c
@@ -0,0 +1,572 @@
+/*
+ * orion_spi.c -- Marvell Orion SPI controller driver
+ *
+ * Author: Shadi Ammouri <shadi@xxxxxxxxxxx>
+ * Copyright (C) 2007-2008 Marvell Ltd.
+ *
+ * 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/delay.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/orion_spi.h>
+#include <asm/unaligned.h>
+
+#define DRIVER_NAME			"orion_spi"
+
+#define ORION_NUM_CHIPSELECTS		1 /* only one slave is supported*/
+#define ORION_SPI_WAIT_RDY_MAX_LOOP	2000 /* in usec */
+
+#define ORION_SPI_IF_CTRL_REG		0x00
+#define ORION_SPI_IF_CONFIG_REG		0x04
+#define ORION_SPI_DATA_OUT_REG		0x08
+#define ORION_SPI_DATA_IN_REG		0x0c
+#define ORION_SPI_INT_CAUSE_REG		0x10
+
+#define ORION_SPI_IF_8_16_BIT_MODE	(1 << 5)
+#define ORION_SPI_CLK_PRESCALE_MASK	0x1F
+
+struct orion_spi {
+	struct work_struct	work;
+
+	/* Lock access to transfer list.	*/
+	spinlock_t		lock;
+
+	struct list_head	msg_queue;
+	struct spi_master	*master;
+	void __iomem		*base;
+	unsigned int		max_speed;
+	unsigned int		min_speed;
+	struct orion_spi_info	*spi_info;
+};
+
+static struct workqueue_struct *orion_spi_wq;
+
+static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg)
+{
+	return orion_spi->base + reg;
+}
+
+static inline void
+orion_spi_setbits(struct orion_spi *orion_spi, u32 reg, u32 mask)
+{
+	void __iomem *reg_addr = spi_reg(orion_spi, reg);
+	u32 val;
+
+	val = readl(reg_addr);
+	val |= mask;
+	writel(val, reg_addr);
+}
+
+static inline void
+orion_spi_clrbits(struct orion_spi *orion_spi, u32 reg, u32 mask)
+{
+	void __iomem *reg_addr = spi_reg(orion_spi, reg);
+	u32 val;
+
+	val = readl(reg_addr);
+	val &= ~mask;
+	writel(val, reg_addr);
+}
+
+static int orion_spi_set_transfer_size(struct orion_spi *orion_spi, int size)
+{
+	if (size == 16) {
+		orion_spi_setbits(orion_spi, ORION_SPI_IF_CONFIG_REG,
+				  ORION_SPI_IF_8_16_BIT_MODE);
+	} else if (size == 8) {
+		orion_spi_clrbits(orion_spi, ORION_SPI_IF_CONFIG_REG,
+				  ORION_SPI_IF_8_16_BIT_MODE);
+	} else {
+		pr_debug("Bad bits per word value %d (only 8 or 16 are "
+			 "allowed).\n", size);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int orion_spi_baudrate_set(struct spi_device *spi, unsigned int speed)
+{
+	u32 tclk_hz;
+	u32 rate;
+	u32 prescale;
+	u32 reg;
+	struct orion_spi *orion_spi;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+
+	tclk_hz = orion_spi->spi_info->tclk;
+
+	/*
+	 * the supported rates are: 4,6,8...30
+	 * round up as we look for equal or less speed
+	 */
+	rate = DIV_ROUND_UP(tclk_hz, speed);
+	rate = roundup(rate, 2);
+
+	/* check if requested speed is too small */
+	if (rate > 30)
+		return -EINVAL;
+
+	if (rate < 4)
+		rate = 4;
+
+	/* Convert the rate to SPI clock divisor value.	*/
+	prescale = 0x10 + rate/2;
+
+	reg = readl(spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG));
+	reg = ((reg & ~ORION_SPI_CLK_PRESCALE_MASK) | prescale);
+	writel(reg, spi_reg(orion_spi, ORION_SPI_IF_CONFIG_REG));
+
+	return 0;
+}
+
+/*
+ * called only when no transfer is active on the bus
+ */
+static int
+orion_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct orion_spi *orion_spi;
+	unsigned int speed = spi->max_speed_hz;
+	unsigned int bits_per_word = spi->bits_per_word;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+
+	if ((t != NULL) && t->speed_hz)
+		speed = t->speed_hz;
+
+	if ((t != NULL) && t->bits_per_word)
+		bits_per_word = t->bits_per_word;
+
+	if (orion_spi_baudrate_set(spi, speed) != 0)
+		return -EINVAL;
+
+	if (orion_spi_set_transfer_size(orion_spi, bits_per_word) != 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void orion_spi_set_cs(struct orion_spi *orion_spi, int enable)
+{
+	if (enable)
+		orion_spi_setbits(orion_spi, ORION_SPI_IF_CTRL_REG, 0x1);
+	else
+		orion_spi_clrbits(orion_spi, ORION_SPI_IF_CTRL_REG, 0x1);
+}
+
+static inline int orion_spi_wait_till_ready(struct orion_spi *orion_spi)
+{
+	int i;
+
+	for (i = 0; i < ORION_SPI_WAIT_RDY_MAX_LOOP; i++) {
+		if (readl(spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG)))
+			return 1;
+		else
+			udelay(1);
+	}
+
+	return -1;
+}
+
+static inline int
+orion_spi_write_read_8bit(struct spi_device *spi,
+			  const u8 **tx_buf, u8 **rx_buf)
+{
+	void __iomem *tx_reg, *rx_reg, *int_reg;
+	struct orion_spi *orion_spi;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+	tx_reg = spi_reg(orion_spi, ORION_SPI_DATA_OUT_REG);
+	rx_reg = spi_reg(orion_spi, ORION_SPI_DATA_IN_REG);
+	int_reg = spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG);
+
+	/* clear the interrupt cause register */
+	writel(0x0, int_reg);
+
+	if (tx_buf && *tx_buf)
+		writel(*(*tx_buf)++, tx_reg);
+	else
+		writel(0, tx_reg);
+
+	if (orion_spi_wait_till_ready(orion_spi) < 0) {
+		dev_err(&spi->dev, "TXS timed out\n");
+		return -1;
+	}
+
+	if (rx_buf && *rx_buf)
+		*(*rx_buf)++ = readl(rx_reg);
+
+	return 1;
+}
+
+static inline int
+orion_spi_write_read_16bit(struct spi_device *spi,
+			   const u16 **tx_buf, u16 **rx_buf)
+{
+	void __iomem *tx_reg, *rx_reg, *int_reg;
+	struct orion_spi *orion_spi;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+	tx_reg = spi_reg(orion_spi, ORION_SPI_DATA_OUT_REG);
+	rx_reg = spi_reg(orion_spi, ORION_SPI_DATA_IN_REG);
+	int_reg = spi_reg(orion_spi, ORION_SPI_INT_CAUSE_REG);
+
+	/* clear the interrupt cause register */
+	writel(0x0, int_reg);
+
+	if (tx_buf && *tx_buf)
+		writel(__cpu_to_le16(get_unaligned((*tx_buf)++)), tx_reg);
+	else
+		writel(0, tx_reg);
+
+	if (orion_spi_wait_till_ready(orion_spi) < 0) {
+		dev_err(&spi->dev, "TXS timed out\n");
+		return -1;
+	}
+
+	if (rx_buf && *rx_buf)
+		put_unaligned(__le16_to_cpu(readl(rx_reg)), (*rx_buf)++);
+
+	return 1;
+}
+
+static unsigned int
+orion_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer)
+{
+	struct orion_spi *orion_spi;
+	unsigned int count;
+	int word_len;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+	word_len = spi->bits_per_word;
+	count = xfer->len;
+
+	if (word_len == 8) {
+		const u8 *tx = xfer->tx_buf;
+		u8 *rx = xfer->rx_buf;
+
+		do {
+			if (orion_spi_write_read_8bit(spi, &tx, &rx) < 0)
+				goto out;
+			count--;
+		} while (count);
+	} else if (word_len == 16) {
+		const u16 *tx = xfer->tx_buf;
+		u16 *rx = xfer->rx_buf;
+
+		do {
+			if (orion_spi_write_read_16bit(spi, &tx, &rx) < 0)
+				goto out;
+			count -= 2;
+		} while (count);
+	}
+
+out:
+	return xfer->len - count;
+}
+
+
+static void orion_spi_work(struct work_struct *work)
+{
+	struct orion_spi *orion_spi =
+		container_of(work, struct orion_spi, work);
+
+	spin_lock_irq(&orion_spi->lock);
+	while (!list_empty(&orion_spi->msg_queue)) {
+		struct spi_message *m;
+		struct spi_device *spi;
+		struct spi_transfer *t = NULL;
+		int par_override = 0;
+		int status = 0;
+		int cs_active = 0;
+
+		m = container_of(orion_spi->msg_queue.next, struct spi_message,
+				 queue);
+
+		list_del_init(&m->queue);
+		spin_unlock_irq(&orion_spi->lock);
+
+		spi = m->spi;
+
+		/* Load defaults */
+		status = orion_spi_setup_transfer(spi, NULL);
+
+		if (status < 0)
+			goto msg_done;
+
+		list_for_each_entry(t, &m->transfers, transfer_list) {
+			if (par_override || t->speed_hz || t->bits_per_word) {
+				par_override = 1;
+				status = orion_spi_setup_transfer(spi, t);
+				if (status < 0)
+					break;
+				if (!t->speed_hz && !t->bits_per_word)
+					par_override = 0;
+			}
+
+			if (!cs_active) {
+				orion_spi_set_cs(orion_spi, 1);
+				cs_active = 1;
+			}
+
+			if (t->len)
+				m->actual_length +=
+					orion_spi_write_read(spi, t);
+
+			if (t->delay_usecs)
+				udelay(t->delay_usecs);
+
+			if (t->cs_change) {
+				orion_spi_set_cs(orion_spi, 0);
+				cs_active = 0;
+			}
+		}
+
+msg_done:
+		if (cs_active)
+			orion_spi_set_cs(orion_spi, 0);
+
+		m->status = status;
+		m->complete(m->context);
+
+		spin_lock_irq(&orion_spi->lock);
+	}
+
+	spin_unlock_irq(&orion_spi->lock);
+}
+
+static int __init orion_spi_reset(struct orion_spi *orion_spi)
+{
+	/* Verify that the CS is deasserted */
+	orion_spi_set_cs(orion_spi, 0);
+
+	return 0;
+}
+
+static int orion_spi_setup(struct spi_device *spi)
+{
+	struct orion_spi *orion_spi;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+
+	if (spi->mode) {
+		dev_err(&spi->dev, "setup: unsupported mode bits %x\n",
+			spi->mode);
+		return -EINVAL;
+	}
+
+	if (spi->bits_per_word == 0)
+		spi->bits_per_word = 8;
+
+	if ((spi->max_speed_hz == 0)
+			|| (spi->max_speed_hz > orion_spi->max_speed))
+		spi->max_speed_hz = orion_spi->max_speed;
+
+	if (spi->max_speed_hz < orion_spi->min_speed) {
+		dev_err(&spi->dev, "setup: requested speed too low %d Hz\n",
+			spi->max_speed_hz);
+		return -EINVAL;
+	}
+
+	/*
+	 * baudrate & width will be set orion_spi_setup_transfer
+	 */
+	return 0;
+}
+
+static int orion_spi_transfer(struct spi_device *spi, struct spi_message *m)
+{
+	struct orion_spi *orion_spi;
+	struct spi_transfer *t = NULL;
+	unsigned long flags;
+
+	m->actual_length = 0;
+	m->status = 0;
+
+	/* reject invalid messages and transfers */
+	if (list_empty(&m->transfers) || !m->complete)
+		return -EINVAL;
+
+	orion_spi = spi_master_get_devdata(spi->master);
+
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		unsigned int bits_per_word = spi->bits_per_word;
+
+		if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) {
+			dev_err(&spi->dev,
+				"message rejected : "
+				"invalid transfer data buffers\n");
+			goto msg_rejected;
+		}
+
+		if ((t != NULL) && t->bits_per_word)
+			bits_per_word = t->bits_per_word;
+
+		if ((bits_per_word != 8) && (bits_per_word != 16)) {
+			dev_err(&spi->dev,
+				"message rejected : "
+				"invalid transfer bits_per_word (%d bits)\n",
+				bits_per_word);
+			goto msg_rejected;
+		}
+		/*make sure buffer length is even when working in 16 bit mode*/
+		if ((t != NULL) && (t->bits_per_word == 16) && (t->len & 1)) {
+			dev_err(&spi->dev,
+				"message rejected : "
+				"odd data length (%d) while in 16 bit mode\n",
+				t->len);
+			goto msg_rejected;
+		}
+
+		if (t->speed_hz < orion_spi->min_speed) {
+			dev_err(&spi->dev,
+				"message rejected : "
+				"device min speed (%d Hz) exceeds "
+				"required transfer speed (%d Hz)\n",
+				orion_spi->min_speed, t->speed_hz);
+			goto msg_rejected;
+		}
+	}
+
+
+	spin_lock_irqsave(&orion_spi->lock, flags);
+	list_add_tail(&m->queue, &orion_spi->msg_queue);
+	queue_work(orion_spi_wq, &orion_spi->work);
+	spin_unlock_irqrestore(&orion_spi->lock, flags);
+
+	return 0;
+msg_rejected:
+	/* Message rejected and not queued */
+	m->status = -EINVAL;
+	if (m->complete)
+		m->complete(m->context);
+	return -EINVAL;
+}
+
+static int __init orion_spi_probe(struct platform_device *pdev)
+{
+	struct spi_master *master;
+	struct orion_spi *spi;
+	struct resource *r;
+	struct orion_spi_info *spi_info;
+	int status = 0;
+
+	spi_info = pdev->dev.platform_data;
+
+	master = spi_alloc_master(&pdev->dev, sizeof *spi);
+	if (master == NULL) {
+		dev_dbg(&pdev->dev, "master allocation failed\n");
+		return -ENOMEM;
+	}
+
+	if (pdev->id != -1)
+		master->bus_num = pdev->id;
+
+	master->setup = orion_spi_setup;
+	master->transfer = orion_spi_transfer;
+	master->num_chipselect = ORION_NUM_CHIPSELECTS;
+
+	dev_set_drvdata(&pdev->dev, master);
+
+	spi = spi_master_get_devdata(master);
+	spi->master = master;
+	spi->spi_info = spi_info;
+
+	spi->max_speed = DIV_ROUND_UP(spi_info->tclk, 4);
+	spi->min_speed = DIV_ROUND_UP(spi_info->tclk, 30);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		status = -ENODEV;
+		goto out;
+	}
+
+	if (!request_mem_region(r->start, (r->end - r->start) + 1,
+				pdev->dev.bus_id)) {
+		status = -EBUSY;
+		goto out;
+	}
+	spi->base = ioremap(r->start, SZ_1K);
+
+	INIT_WORK(&spi->work, orion_spi_work);
+
+	spin_lock_init(&spi->lock);
+	INIT_LIST_HEAD(&spi->msg_queue);
+
+	if (orion_spi_reset(spi) < 0)
+		goto out_rel_mem;
+
+	status = spi_register_master(master);
+	if (status < 0)
+		goto out_rel_mem;
+
+	return status;
+
+out_rel_mem:
+	release_mem_region(r->start, (r->end - r->start) + 1);
+
+out:
+	spi_master_put(master);
+	return status;
+}
+
+
+static int __exit orion_spi_remove(struct platform_device *pdev)
+{
+	struct spi_master *master;
+	struct orion_spi *spi;
+	struct resource *r;
+
+	master = dev_get_drvdata(&pdev->dev);
+	spi = spi_master_get_devdata(master);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	release_mem_region(r->start, (r->end - r->start) + 1);
+
+	spi_unregister_master(master);
+
+	return 0;
+}
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+
+static struct platform_driver orion_spi_driver = {
+	.driver = {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.remove		= __exit_p(orion_spi_remove),
+};
+
+static int __init orion_spi_init(void)
+{
+	orion_spi_wq = create_singlethread_workqueue(
+				orion_spi_driver.driver.name);
+	if (orion_spi_wq == NULL)
+		return -ENOMEM;
+
+	return platform_driver_probe(&orion_spi_driver, orion_spi_probe);
+}
+module_init(orion_spi_init);
+
+static void __exit orion_spi_exit(void)
+{
+	platform_driver_unregister(&orion_spi_driver);
+
+	destroy_workqueue(orion_spi_wq);
+}
+module_exit(orion_spi_exit);
+
+MODULE_DESCRIPTION("Orion SPI driver");
+MODULE_AUTHOR("Shadi Ammouri <shadi@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
diff -puN /dev/null include/linux/spi/orion_spi.h
--- /dev/null
+++ a/include/linux/spi/orion_spi.h
@@ -0,0 +1,17 @@
+/*
+ * orion_spi.h
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __LINUX_SPI_ORION_SPI_H
+#define __LINUX_SPI_ORION_SPI_H
+
+struct orion_spi_info {
+	u32	tclk;		/* no <linux/clk.h> support yet */
+};
+
+
+#endif
_

Patches currently in -mm which might be from shadi@xxxxxxxxxxx are

spi-new-orion_spi-driver.patch

--
To unsubscribe from this list: send the line "unsubscribe mm-commits" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Kernel Newbies FAQ]     [Kernel Archive]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [Bugtraq]     [Photo]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]

  Powered by Linux