On Thu, May 17, 2007 at 10:11:12AM +0200, Domen Puncer wrote:
> On 04/04/07 13:09 +0200, Sascha Hauer wrote:
> > Hi,
> 
> Hi!
> 
> > 
> > I'm currently writing a driver for the mpc5200 spi controller (the
> > dedicated one, not the PSC ones).
> 
> Any update on this, code releases, or should I start writing from
> scratch?


Ups, forgot this one. Here it is. I just grabbed it from my patch stack, but
I last worked in february on this driver, so it might not be up-to-date.
BTW I was not able to get interrupts from the SPI device, maybe that
there was something wrong in my oftree. But I think it is not worth it
anyway since the controller only has a one byte fifo.

Sascha

Signed-off-by: Sascha Hauer <s,hauer@pengutronix.de>

---
 drivers/spi/Kconfig       |    8 +
 drivers/spi/Makefile      |    1 
 drivers/spi/mpc5200_spi.c |  353 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 362 insertions(+)

Index: work-powerpc.git/drivers/spi/Kconfig
===================================================================
--- work-powerpc.git.orig/drivers/spi/Kconfig
+++ work-powerpc.git/drivers/spi/Kconfig
@@ -123,6 +123,14 @@ config SPI_MPC52xx_PSC
 	  This enables using the Freescale MPC52xx Programmable Serial
 	  Controller in master SPI mode.
 
+config SPI_MPC5200
+	tristate "Freescale MPC5200 SPI Controller"
+	depends on PPC_MPC5200 && SPI_MASTER
+	help
+	  This selects a driver for the Freescale MPC5200 SPI controller in
+	  master mode. Note that this is a driver for the dedicated SPI
+	  controller, not for the PSCs in SPI mode.
+
 config SPI_MPC83xx
 	tristate "Freescale MPC83xx SPI controller"
 	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
Index: work-powerpc.git/drivers/spi/Makefile
===================================================================
--- work-powerpc.git.orig/drivers/spi/Makefile
+++ work-powerpc.git/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_MPC5200)		+= mpc5200_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
Index: work-powerpc.git/drivers/spi/mpc5200_spi.c
===================================================================
--- /dev/null
+++ work-powerpc.git/drivers/spi/mpc5200_spi.c
@@ -0,0 +1,353 @@
+/*
+ * (c) 2007 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <asm/of_platform.h>
+
+#include <asm/mpc52xx.h>
+#include <asm/io.h>
+
+#define SPI_CTRL1	0x0
+#define SPI_CTRL1_SPIE		(1 << 7)
+#define SPI_CTRL1_SPE		(1 << 6)
+#define SPI_CTRL1_MSTR		(1 << 4)
+#define SPI_CTRL1_CPOL		(1 << 3)
+#define SPI_CTRL1_CPHA		(1 << 2)
+#define SPI_CTRL1_SSOE		(1 << 1)
+#define SPI_CTRL1_LSBFE		(1 << 0)
+
+#define SPI_CTRL2	0x1
+#define SPI_CTRL2_SPISWAI	(1 << 1)
+#define SPI_CTRL2_SPC0		(1 << 0)
+
+#define SPI_BAUD	0x4
+#define BAUD_SPPR_SHIFT		3
+#define BAUD_SPR_SHIFT		0
+
+#define SPI_STATUS	0x5
+#define SPI_STATUS_SPIF		(1 << 7)
+#define SPI_STATUS_WCOL		(1 << 6)
+#define SPI_STATUS_MODF		(1 << 4)
+
+#define SPI_DATA	0x9
+#define SPI_PORT_DATA	0xd
+#define SPI_DATA_DIR	0x10
+
+struct mpc5200_spi {
+	struct spi_bitbang	 bitbang;
+	struct completion	 done;
+
+	struct spi_master	*master;
+
+	int			irq;
+	void __iomem		*base;
+
+	unsigned int		ipb_freq;
+
+	/* data buffers */
+	const unsigned char	*tx;
+	unsigned char		*rx;
+	int			 len;
+	int			 count;
+
+	unsigned int		speed_hz;
+};
+
+/* FIXME: This function needs proper CS_INACTIVE handling */
+static void mpc5200_spi_chipsel(struct spi_device *spi, int value)
+{
+	struct mpc5200_spi *port = spi_master_get_devdata(spi->master);
+	unsigned int ctrl1;
+
+	ctrl1 = SPI_CTRL1_SPIE | SPI_CTRL1_SPE | SPI_CTRL1_MSTR |
+		SPI_CTRL1_SSOE;
+
+	switch (value) {
+	case BITBANG_CS_INACTIVE:
+#if 0
+		if (!(spi->mode & SPI_CPOL))
+			ctrl1 |= SPI_CTRL1_CPOL;
+#endif
+		break;
+
+	case BITBANG_CS_ACTIVE:
+		if (spi->mode & SPI_CPHA)
+			ctrl1 |= SPI_CTRL1_CPHA;
+
+		if (spi->mode & SPI_CPOL)
+			ctrl1 |= SPI_CTRL1_CPOL;
+
+		if (spi->mode & SPI_LSB_FIRST)
+			ctrl1 |= SPI_CTRL1_LSBFE;
+		writeb(ctrl1, port->base + SPI_CTRL1);
+		break;
+	}
+}
+
+static int mpc5200_spi_setupxfer(struct spi_device *spi,
+				 struct spi_transfer *t)
+{
+	struct mpc5200_spi *port = spi_master_get_devdata(spi->master);
+	unsigned int bpw, hz, div, spr, sppr, act_div;
+
+	bpw = t ? t->bits_per_word : spi->bits_per_word;
+	hz  = t ? t->speed_hz : spi->max_speed_hz;
+
+	/* FIXME: This is a limitation of our particular hardware,
+	 *        and should not be in the driver
+	 */
+	if(hz > 10000000)
+		hz = 10000000;
+
+	if (bpw != 8) {
+		dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw);
+		return -EINVAL;
+	}
+
+	div = port->ipb_freq / hz;
+
+	spr = 0;
+	sppr = 7;
+
+	while (spr < 7) {
+		act_div = (sppr + 1) * (2 << (spr + 1));
+		if (port->ipb_freq / act_div <= hz)
+			break;
+		spr++;
+	}
+
+	sppr = 0;
+	while (sppr < 7) {
+		act_div = (sppr + 1) * (2 << (spr + 1));
+		if (port->ipb_freq / act_div <= hz)
+			break;
+		sppr++;
+	}
+
+	port->speed_hz = port->ipb_freq / ((sppr + 1) * (2 << (spr + 1)));
+
+	dev_dbg(&spi->dev, "%s speed wanted: %d speed: %d\n",__FUNCTION__, hz, port->ipb_freq / (sppr + 1) / (2 << (spr + 1)) );
+
+	writeb( (sppr << BAUD_SPPR_SHIFT) | (spr << BAUD_SPR_SHIFT), port->base + SPI_BAUD);
+	return 0;
+}
+
+static int mpc5200_spi_setup(struct spi_device *spi)
+{
+	int ret;
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+
+	ret = mpc5200_spi_setupxfer(spi, NULL);
+	if (ret < 0) {
+		dev_err(&spi->dev, "setupxfer returned %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static inline unsigned int hw_txbyte(struct mpc5200_spi *port, int count)
+{
+	return port->tx ? port->tx[count] : 0;
+}
+
+static int mpc5200_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc5200_spi *port = spi_master_get_devdata(spi->master);
+	int count = 0;
+	unsigned int status;
+
+	dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
+		t->tx_buf, t->rx_buf, t->len);
+
+	port->tx = t->tx_buf;
+	port->rx = t->rx_buf;
+	port->len = t->len;
+
+	while(count < t->len) {
+		unsigned char data;
+		unsigned long start;
+
+		writeb(hw_txbyte(port, count), port->base + SPI_DATA);
+
+		start = jiffies;
+		do {
+			status = readb(port->base + SPI_STATUS);
+			if (status & SPI_STATUS_WCOL) {
+				dev_err(&spi->dev, "Write collision on master only bus?\n");
+				goto out;
+			}
+			if(time_after(jiffies, start + msecs_to_jiffies(10000))) {
+				dev_err(&spi->dev, "SPI timeout\n");
+				goto out;
+			}
+		} while (!(status & SPI_STATUS_SPIF));
+
+		data = readb(port->base + SPI_DATA);
+
+		if (port->rx)
+			port->rx[count] = data;
+
+		count++;
+
+		if (port->speed_hz < 200000)
+			udelay(1000000 / port->speed_hz);
+	}
+
+	dev_dbg(&spi->dev, "transfer done\n");
+out:
+	return count;
+}
+
+static irqreturn_t mpc5200_spi_irq(int irq, void *dev)
+{
+	printk("%s\n",__FUNCTION__);
+	return IRQ_HANDLED;
+}
+
+static int __devinit
+mpc5200_spi_probe(struct of_device *odev, const struct of_device_id *match)
+{
+	int ret;
+	struct spi_master *master;
+	struct resource res;
+	struct mpc5200_spi *port;
+
+	master = spi_alloc_master(&odev->dev, sizeof(struct mpc5200_spi));
+	if (!master) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	port = spi_master_get_devdata(master);
+
+	dev_set_drvdata(&odev->dev, port);
+	init_completion(&port->done);
+
+	port->master = spi_master_get(master);
+
+	/* Search for IRQ and mapbase */
+	if ((ret = of_address_to_resource(odev->node, 0, &res)) != 0) {
+		dev_err(&odev->dev, "Unable to get resource\n");
+		goto err_free;
+	}
+
+	port->irq = irq_of_parse_and_map(odev->node, 0);
+
+	port->base = ioremap(res.start, res.end - res.start + 1);
+	if (!port->base) {
+		dev_err(&odev->dev, "ioremap failed\n");
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	writeb(0xe, port->base + SPI_DATA_DIR);
+
+	ret = request_irq(port->irq, mpc5200_spi_irq, 0, "mpc5200-spi", port);
+	if (ret) {
+		dev_err(&odev->dev, "Cannot claim IRQ\n");
+		goto err_no_irq;
+	}
+
+	dev_info(&odev->dev, "MPC5200 spi: base: 0x%p irq: %d\n",port->base, port->irq);
+
+	port->bitbang.master         = port->master;
+	port->bitbang.setup_transfer = mpc5200_spi_setupxfer;
+	port->bitbang.chipselect     = mpc5200_spi_chipsel;
+	port->bitbang.txrx_bufs      = mpc5200_spi_txrx;
+	port->bitbang.master->setup  = mpc5200_spi_setup;
+
+	port->ipb_freq = mpc52xx_find_ipb_freq(odev->node);
+
+	ret = spi_bitbang_start(&port->bitbang);
+	if (ret) {
+		dev_err(&odev->dev, "Failed to register SPI master\n");
+		goto err_register;
+	}
+
+	return 0;
+
+err_register:
+	free_irq(port->irq, port);
+err_no_irq:
+	iounmap(port->base);
+err_free:
+	kfree(master);
+err:
+	return ret;
+}
+
+static int mpc5200_spi_remove(struct of_device *odev)
+{
+	struct mpc5200_spi *port = dev_get_drvdata(&odev->dev);
+
+	dev_set_drvdata(&odev->dev, NULL);
+
+	spi_unregister_master(port->master);
+
+	free_irq(port->irq, port);
+	iounmap(port->base);
+
+	spi_master_put(port->master);
+
+	return 0;
+}
+
+static struct of_device_id mpc5200_spi_of_match[] = {
+	{ .type = "spi", .compatible = "mpc5200-spi", },
+	{},
+};
+
+static struct of_platform_driver mpc5200_spi_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "mpc5200-spi",
+	.match_table	= mpc5200_spi_of_match,
+	.probe		= mpc5200_spi_probe,
+	.remove		= mpc5200_spi_remove,
+#ifdef CONFIG_PM
+//	.suspend	= mpc5200_spi_of_suspend,
+//	.resume		= mpc5200_spi_of_resume,
+#endif
+	.driver		= {
+		.name	= "mpc5200-spi",
+	},
+};
+
+static int __init mpc5200_spi_init(void)
+{
+	return of_register_platform_driver(&mpc5200_spi_driver);
+}
+
+static void __exit mpc5200_spi_exit(void)
+{
+	of_unregister_platform_driver(&mpc5200_spi_driver);
+}
+
+module_init(mpc5200_spi_init);
+module_exit(mpc5200_spi_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("Freescale MPC5200 SPI driver");
+MODULE_LICENSE("GPL");
+
