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 --- 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 + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +#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 "); +MODULE_DESCRIPTION("Freescale MPC5200 SPI driver"); +MODULE_LICENSE("GPL"); +