From patchwork Fri Oct 31 11:07:46 2008 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Roese X-Patchwork-Id: 6686 X-Patchwork-Delegate: jwboyer@gmail.com Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from ozlabs.org (localhost [127.0.0.1]) by ozlabs.org (Postfix) with ESMTP id D592EDDF92 for ; Fri, 31 Oct 2008 22:08:10 +1100 (EST) X-Original-To: linuxppc-dev@ozlabs.org Delivered-To: linuxppc-dev@ozlabs.org Received: from moutng.kundenserver.de (moutng.kundenserver.de [212.227.126.171]) by ozlabs.org (Postfix) with ESMTP id 05AC3DDDF5 for ; Fri, 31 Oct 2008 22:07:50 +1100 (EST) Received: from localhost.localdomain (achn-4db4985e.pool.einsundeins.de [77.180.152.94]) by mrelayeu.kundenserver.de (node=mrelayeu8) with ESMTP (Nemesis) id 0ML31I-1Kvrr11jGz-0007e0; Fri, 31 Oct 2008 12:07:49 +0100 From: Stefan Roese To: linuxppc-dev@ozlabs.org, spi-devel-general@lists.sourceforge.net Subject: [PATCH v4] spi: Add PPC4xx SPI driver Date: Fri, 31 Oct 2008 12:07:46 +0100 Message-Id: <1225451266-23740-1-git-send-email-sr@denx.de> X-Mailer: git-send-email 1.6.0.3 X-Provags-ID: V01U2FsdGVkX19+NCHweBE7RSdOI2uFf76dzLbFm2SqQ7fNXsa b8718DcPnbxo7WYZbU190n32kgO5QtpsaUIlRrfvKldNI2HI6/ I2xrBSrZwLeaTGcH9WveOsXdvgkvjU+ X-BeenThere: linuxppc-dev@ozlabs.org X-Mailman-Version: 2.1.11 Precedence: list List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@ozlabs.org Errors-To: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@ozlabs.org This adds a SPI driver for the SPI controller found in the IBM/AMCC 4xx PowerPC's. Signed-off-by: Stefan Roese Signed-off-by: Wolfgang Ocker Acked-by: Josh Boyer --- Changes in v4: - Added fixes suggested by Josh Boyer - Changed compatible property from "ibm,spi" to "ibm,ppc4xx-spi" Changes in v3: - When the device is removed the GPIOs are released. The memory for the GPIO array is freed. Changes in v2: - Now the gpios property is correctly decoded and the resulting gpio numbers are used as the devices chip selects. So we can describe the SPI node like this: SPI0: spi@ef600900 { #address-cells = <1>; #size-cells = <0>; ... /* two GPIOs, representing two chip selects: 0 and 1 */ gpios = <&GPIO0 5 0 &GPIO1 16 0>; mmc-slot@0 { reg = <0>; ... }; touchscreen@1 { reg = <1>; ... }; } Special thanks to Wolfgang Ocker and Anton Vorontsov for their input. drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi_ppc4xx.c | 594 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 602 insertions(+), 0 deletions(-) create mode 100644 drivers/spi/spi_ppc4xx.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index b9d0efb..69d5fee 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -155,6 +155,13 @@ config SPI_ORION help This enables using the SPI master controller on the Orion chips. +config SPI_PPC4xx + tristate "PPC4xx SPI Controller" + depends on 4xx && SPI_MASTER + select SPI_BITBANG + help + This selects a driver for the PPC4xx SPI Controller. + config SPI_PXA2XX tristate "PXA2xx SSP SPI master" depends on ARCH_PXA && EXPERIMENTAL diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index ccf18de..a2e5816 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -24,6 +24,7 @@ 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_PPC4xx) += spi_ppc4xx.o obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o diff --git a/drivers/spi/spi_ppc4xx.c b/drivers/spi/spi_ppc4xx.c new file mode 100644 index 0000000..cfdb57d --- /dev/null +++ b/drivers/spi/spi_ppc4xx.c @@ -0,0 +1,594 @@ +/* + * SPI_PPC4XX SPI controller driver. + * + * Copyright (C) 2007 Gary Jennejohn + * Copyright 2008 Stefan Roese , DENX Software Engineering + * + * Based in part on drivers/spi/spi_s3c24xx.c + * + * Copyright (c) 2006 Ben Dooks + * Copyright (c) 2006 Simtec Electronics + * Ben Dooks + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +/* bits in mode register - bit 0 ist MSb */ +/* data latched on leading edge of clock, else trailing edge */ +#define SPI_PPC4XX_MODE_SCP (0x80 >> 3) +/* port enabled */ +#define SPI_PPC4XX_MODE_SPE (0x80 >> 4) +/* MSB first, else LSB first */ +#define SPI_PPC4XX_MODE_RD (0x80 >> 5) +/* clock invert - idle clock = 1, active clock = 0; else reversed */ +#define SPI_PPC4XX_MODE_CI (0x80 >> 6) +/* loopback enable */ +#define SPI_PPC4XX_MODE_IL (0x80 >> 7) +/* bits in control register */ +/* starts a transfer when set */ +#define SPI_PPC4XX_CR_STR (0x80 >> 7) +/* bits in status register */ +/* port is busy with a transfer */ +#define SPI_PPC4XX_SR_BSY (0x80 >> 6) +/* RxD ready */ +#define SPI_PPC4XX_SR_RBR (0x80 >> 7) + +/* the spi->mode bits understood by this driver: */ +#define MODEBITS (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST) + +/* clock settings (SCP and CI) for various SPI modes */ +#define SPI_CLK_MODE0 SPI_PPC4XX_MODE_SCP +#define SPI_CLK_MODE1 0 +#define SPI_CLK_MODE2 SPI_PPC4XX_MODE_CI +#define SPI_CLK_MODE3 (SPI_PPC4XX_MODE_SCP | SPI_PPC4XX_MODE_CI) + +#define DRIVER_NAME "spi_ppc4xx_of" + +struct spi_ppc4xx_regs { + u8 mode; + u8 rxd; + u8 txd; + u8 cr; + u8 sr; + u8 dummy; + /* + * Clock divisor modulus register + * This uses the follwing formula: + * SCPClkOut = OPBCLK/(4(CDM + 1)) + * or + * CDM = (OPBCLK/4*SCPClkOut) - 1 + * bit 0 is the MSb! + */ + u8 cdm; +}; + +/* SPI Controller driver's private data. */ +struct ppc4xx_spi { + /* bitbang has to be first */ + struct spi_bitbang bitbang; + struct completion done; + + u64 mapbase; + u64 mapsize; + int irqnum; + /* need this to set the SPI clock */ + unsigned int opb_freq; + + /* for transfers */ + int len; + int count; + /* data buffers */ + const unsigned char *tx; + unsigned char *rx; + + int *gpios; + unsigned int num_gpios; + + struct spi_ppc4xx_regs __iomem *regs; /* pointer to the registers */ + struct spi_master *master; + struct device *dev; +}; + +/* need this so we can set the clock in the chipselect routine */ +struct spi_ppc4xx_cs { + /* speed in hertz */ + u32 speed_hz; + /* bits per word - must be 8! */ + u8 bpw; +}; + +static int spi_ppc4xx_txrx(struct spi_device *spi, struct spi_transfer *t) +{ + struct ppc4xx_spi *hw; + u8 data; + + dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", + t->tx_buf, t->rx_buf, t->len); + + hw = spi_master_get_devdata(spi->master); + + hw->tx = t->tx_buf; + hw->rx = t->rx_buf; + hw->len = t->len; + hw->count = 0; + + /* send the first byte */ + data = hw->tx ? hw->tx[0] : 0; + out_8(&hw->regs->txd, data); + out_8(&hw->regs->cr, SPI_PPC4XX_CR_STR); + wait_for_completion(&hw->done); + + return hw->count; +} + +static int spi_ppc4xx_setupxfer(struct spi_device *spi, struct spi_transfer *t) +{ + struct ppc4xx_spi *hw = spi_master_get_devdata(spi->master); + struct spi_ppc4xx_cs *cs = spi->controller_state; + unsigned char cdm = 0; + int scr; + + if (cs == NULL) { + cs = kzalloc(sizeof(*cs), GFP_KERNEL); + if (!cs) + return -ENOMEM; + spi->controller_state = cs; + } + + /* + * Allow platform reduce the interrupt load on the CPU during SPI + * transfers. We do not target maximum performance, but rather allow + * platform to limit SPI bus frequency and interrupt rate. + */ + cs->bpw = t ? t->bits_per_word : spi->bits_per_word; + cs->speed_hz = t ? min(t->speed_hz, spi->max_speed_hz) : + spi->max_speed_hz; + + if (cs->bpw != 8) { + dev_err(&spi->dev, "invalid bits-per-word (%d)\n", cs->bpw); + return -EINVAL; + } + + if (cs->speed_hz == 0) { + dev_err(&spi->dev, "invalid speed_hz (must be non-zero)\n"); + return -EINVAL; + } + + /* set the clock */ + /* opb_freq was already divided by 4 */ + scr = (hw->opb_freq / cs->speed_hz) - 1; + + if (scr > 0) + cdm = min(scr, 0xff); + + dev_dbg(&spi->dev, "setting pre-scaler to %d (hz %d)\n", cdm, + cs->speed_hz); + + if (in_8(&hw->regs->cdm) != cdm) + out_8(&hw->regs->cdm, cdm); + + spin_lock(&hw->bitbang.lock); + if (!hw->bitbang.busy) { + hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE); + /* need to ndelay here? */ + } + spin_unlock(&hw->bitbang.lock); + + return 0; +} + +static int spi_ppc4xx_setup(struct spi_device *spi) +{ + struct ppc4xx_spi *hw = spi_master_get_devdata(spi->master); + int ret; + struct spi_ppc4xx_cs *cs = spi->controller_state; + unsigned char mode; + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + if (spi->mode & ~MODEBITS) { + dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n", + spi->mode & ~MODEBITS); + return -EINVAL; + } + + if (cs == NULL) { + cs = kzalloc(sizeof *cs, GFP_KERNEL); + if (!cs) + return -ENOMEM; + spi->controller_state = cs; + } + + ret = spi_ppc4xx_setupxfer(spi, NULL); + if (ret < 0) { + dev_err(&spi->dev, "setupxfer returned %d\n", ret); + return ret; + } + + /* + * We set all bits of the SPI0_MODE register, so, + * no need to read-modify-write + */ + mode = SPI_PPC4XX_MODE_SPE; + + switch (spi->mode & (SPI_CPHA | SPI_CPOL)) { + case SPI_MODE_0: + mode |= SPI_CLK_MODE0; + break; + case SPI_MODE_1: + mode |= SPI_CLK_MODE1; + break; + case SPI_MODE_2: + mode |= SPI_CLK_MODE2; + break; + case SPI_MODE_3: + mode |= SPI_CLK_MODE3; + break; + } + + if (spi->mode & SPI_LSB_FIRST) { + /* this assumes that bit 7 is the LSb! */ + mode |= SPI_PPC4XX_MODE_RD; + } + + /* write new configration */ + out_8(&hw->regs->mode, mode); + + dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n", + __FUNCTION__, spi->mode, spi->bits_per_word, + spi->max_speed_hz); + + return 0; +} + +static void spi_ppc4xx_chipsel(struct spi_device *spi, int value) +{ + struct ppc4xx_spi *hw = spi_master_get_devdata(spi->master); + unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0; + unsigned int cs = spi->chip_select; + + if (!hw->num_gpios) + return; + + if (cs >= hw->num_gpios) + return; + + if (value != BITBANG_CS_INACTIVE && value != BITBANG_CS_ACTIVE) + return; + + if (value == BITBANG_CS_INACTIVE) + cspol = !cspol; + + gpio_set_value(hw->gpios[cs], cspol); +} + +static irqreturn_t spi_ppc4xx_int(int irq, void *dev_id) +{ + struct ppc4xx_spi *hw; + u8 status; + u8 data; + unsigned int count; + + hw = (struct ppc4xx_spi *)dev_id; + + status = in_8(&hw->regs->sr); + if (!status) + return IRQ_NONE; + + /* should never happen but check anyway */ + if (status & SPI_PPC4XX_SR_BSY) { + u8 lstatus; + int cnt = 0; + + dev_dbg(hw->dev, "got interrupt but spi still busy?\n"); + do { + ndelay(10); + lstatus = in_8(&hw->regs->sr); + } while (++cnt < 100 && lstatus & SPI_PPC4XX_SR_BSY); + + if (cnt >= 100) { + dev_err(hw->dev, "busywait: too many loops!\n"); + complete(&hw->done); + return IRQ_HANDLED; + } else { + /* status is always 1 (RBR) here */ + status = in_8(&hw->regs->sr); + dev_dbg(hw->dev, "loops %d status %x\n", cnt, status); + } + } + + count = hw->count; + hw->count++; + + if (status & SPI_PPC4XX_SR_RBR) { + /* Data Ready */ + data = in_8(&hw->regs->rxd); + if (hw->rx) + hw->rx[count] = data; + } + + count++; + + if (count < hw->len) { + data = hw->tx ? hw->tx[count] : 0; + out_8(&hw->regs->txd, data); + out_8(&hw->regs->cr, SPI_PPC4XX_CR_STR); + } else { + complete(&hw->done); + } + + return IRQ_HANDLED; +} + +static void spi_ppc4xx_cleanup(struct spi_device *spi) +{ + kfree(spi->controller_state); +} + +static void spi_ppc4xx_enable(struct ppc4xx_spi *hw) +{ + /* + * On all 4xx PPC's the SPI bus is shared/multiplexed with + * the 2nd I2C bus. We need to enable the the SPI bus before + * using it. + */ + + /* need to clear bit 14 to enable SPC */ + dcri_clrset(SDR0, SDR0_PFC1, 0x80000000 >> 14, 0); +} + +static unsigned int of_num_gpios(struct device_node *node) +{ + unsigned int num = 0; + + while (gpio_is_valid(of_get_gpio(node, num))) + num++; + return num; +} + +static void free_gpios(struct ppc4xx_spi *hw) +{ + if (hw->num_gpios) { + int i; + for (i = 0; i < hw->num_gpios; i++) + gpio_free(hw->gpios[i]); + + kfree(hw->gpios); + hw->gpios = NULL; + } +} + +/* + * of_device layer stuff... + */ +static int __init spi_ppc4xx_of_probe(struct of_device *op, + const struct of_device_id *match) +{ + struct ppc4xx_spi *hw; + struct spi_master *master; + struct spi_bitbang *bbp; + struct resource resource; + struct device_node *np = op->node; + struct device *dev = &op->dev; + struct device_node *opbnp; + int ret; + const unsigned int *clk; + + master = spi_alloc_master(dev, sizeof *hw); + if (master == NULL) + return -ENOMEM; + dev_set_drvdata(dev, master); + hw = spi_master_get_devdata(master); + memset(hw, 0, sizeof(struct ppc4xx_spi)); + + hw->master = spi_master_get(master); + hw->dev = dev; + + init_completion(&hw->done); + + hw->num_gpios = of_num_gpios(np); + if (hw->num_gpios) { + int i; + + hw->gpios = kzalloc(sizeof(int) * hw->num_gpios, + GFP_KERNEL); + if (!hw->gpios) { + ret = -ENOMEM; + goto free_master; + } + + for (i = 0; i < hw->num_gpios; i++) { + int gpio = of_get_gpio(np, i); + if (gpio < 0) { + dev_err(dev, "Invalid gpio spec %d\n", i); + ret = gpio; + goto free_gpios; + } + + ret = gpio_request(gpio, np->name); + if (ret < 0) { + dev_err(dev, "gpio %d already in use\n", i); + ret = gpio; + goto free_gpios; + } + + gpio_direction_output(gpio, 0); + hw->gpios[i] = gpio; + } + } + + /* Setup the state for the bitbang driver */ + bbp = &hw->bitbang; + bbp->master = hw->master; + bbp->setup_transfer = spi_ppc4xx_setupxfer; + bbp->chipselect = spi_ppc4xx_chipsel; + bbp->txrx_bufs = spi_ppc4xx_txrx; + bbp->use_dma = 0; + bbp->master->setup = spi_ppc4xx_setup; + bbp->master->cleanup = spi_ppc4xx_cleanup; + /* only one SPI controller */ + bbp->master->bus_num = 0; + if (bbp->master->num_chipselect == 0) { + /* this many pins in al GPIO controllers */ + bbp->master->num_chipselect = hw->num_gpios; + } + + /* Get the clock for the OPB */ + opbnp = of_find_compatible_node(NULL, NULL, "ibm,opb"); + if (opbnp == NULL) { + dev_err(dev, "OPB: cannot find node\n"); + ret = -ENODEV; + goto free_gpios; + } + /* Get the clock (Hz) for the OPB */ + clk = of_get_property(opbnp, "clock-frequency", NULL); + if (clk == NULL) { + dev_err(dev, "OPB: no clock-frequency property set\n"); + of_node_put(opbnp); + ret = -ENODEV; + goto free_gpios; + } + hw->opb_freq = *clk; + hw->opb_freq >>= 2; + of_node_put(opbnp); + + ret = of_address_to_resource(np, 0, &resource); + if (ret) { + dev_err(dev, "error while parsing device node resource\n"); + goto free_gpios; + } + hw->mapbase = resource.start; + hw->mapsize = resource.end - resource.start + 1; + + /* Sanity check */ + if (hw->mapsize < sizeof(struct spi_ppc4xx_regs)) { + dev_err(dev, "too small to map registers\n"); + ret = -EINVAL; + goto free_gpios; + } + + /* Request IRQ */ + hw->irqnum = irq_of_parse_and_map(np, 0); + ret = request_irq(hw->irqnum, spi_ppc4xx_int, + IRQF_DISABLED, "spi_ppc4xx_of", (void *)hw); + if (ret) { + dev_err(dev, "unable to allocate interrupt\n"); + goto free_gpios; + } + + if (!request_mem_region(hw->mapbase, hw->mapsize, DRIVER_NAME)) { + dev_err(dev, "resource unavailable\n"); + ret = -EBUSY; + goto request_mem_error; + } + + hw->regs = ioremap(hw->mapbase, sizeof(struct spi_ppc4xx_regs)); + + if (!hw->regs) { + dev_err(dev, "unable to memory map registers\n"); + ret = -ENXIO; + goto map_io_error; + } + + spi_ppc4xx_enable(hw); + + /* Finally register our spi controller */ + dev->dma_mask = 0; + ret = spi_bitbang_start(bbp); + if (ret) { + dev_err(dev, "failed to register SPI master\n"); + goto unmap_regs; + } + + dev_info(dev, "driver initialized\n"); + of_register_spi_devices(master, np); + + return 0; + +unmap_regs: + iounmap(hw->regs); +map_io_error: + release_mem_region(hw->mapbase, hw->mapsize); +request_mem_error: + free_irq(hw->irqnum, hw); +free_gpios: + free_gpios(hw); +free_master: + dev_set_drvdata(dev, NULL); + spi_master_put(master); + + dev_err(dev, "initialization failed\n"); + return ret; +} + +static int __exit spi_ppc4xx_of_remove(struct of_device *op) +{ + struct spi_master *master = dev_get_drvdata(&op->dev); + struct ppc4xx_spi *hw = spi_master_get_devdata(master); + + spi_bitbang_stop(&hw->bitbang); + dev_set_drvdata(&op->dev, NULL); + release_mem_region(hw->mapbase, hw->mapsize); + free_irq(hw->irqnum, hw); + iounmap(hw->regs); + free_gpios(hw); + return 0; +} + +static struct of_device_id spi_ppc4xx_of_match[] = { + { .compatible = "ibm,ppc4xx-spi", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, spi_ppc4xx_of_match); + +static struct of_platform_driver spi_ppc4xx_of_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .match_table = spi_ppc4xx_of_match, + .probe = spi_ppc4xx_of_probe, + .remove = __exit_p(spi_ppc4xx_of_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init spi_ppc4xx_init(void) +{ + return of_register_platform_driver(&spi_ppc4xx_of_driver); +} + +static void __exit spi_ppc4xx_exit(void) +{ + of_unregister_platform_driver(&spi_ppc4xx_of_driver); +} + +module_init(spi_ppc4xx_init); +module_exit(spi_ppc4xx_exit); + +MODULE_AUTHOR("Gary Jennejohn & Stefan Roese"); +MODULE_DESCRIPTION("Simple PPC4xx SPI Driver"); +MODULE_LICENSE("GPL");