* [PATCH v4] spi: Add PPC4xx SPI driver
@ 2008-10-31 11:07 Stefan Roese
[not found] ` <1225451266-23740-1-git-send-email-sr-ynQEQJNshbs@public.gmane.org>
0 siblings, 1 reply; 5+ messages in thread
From: Stefan Roese @ 2008-10-31 11:07 UTC (permalink / raw)
To: linuxppc-dev-mnsaURCQ41sdnm+yROfE0A,
spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f
This adds a SPI driver for the SPI controller found in the IBM/AMCC
4xx PowerPC's.
Signed-off-by: Stefan Roese <sr-ynQEQJNshbs@public.gmane.org>
Signed-off-by: Wolfgang Ocker <weo-pMF0Ia4UJXeoYr4blSSd5g@public.gmane.org>
---
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 <garyj-ynQEQJNshbs@public.gmane.org>
+ * Copyright 2008 Stefan Roese <sr-ynQEQJNshbs@public.gmane.org>, DENX Software Engineering
+ *
+ * Based in part on drivers/spi/spi_s3c24xx.c
+ *
+ * Copyright (c) 2006 Ben Dooks
+ * Copyright (c) 2006 Simtec Electronics
+ * Ben Dooks <ben-Y5A6D6n0/KfQXOPxS62xeg@public.gmane.org>
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/wait.h>
+#include <linux/of_platform.h>
+#include <linux/of_spi.h>
+#include <linux/of_gpio.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <linux/gpio.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+
+#include <asm/io.h>
+#include <asm/dcr.h>
+#include <asm/dcr-regs.h>
+
+/* 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");
--
1.6.0.3
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v4] spi: Add PPC4xx SPI driver
[not found] ` <1225451266-23740-1-git-send-email-sr-ynQEQJNshbs@public.gmane.org>
@ 2008-11-04 13:10 ` Josh Boyer
2008-11-21 3:40 ` David Brownell
2008-11-21 3:41 ` David Brownell
2 siblings, 0 replies; 5+ messages in thread
From: Josh Boyer @ 2008-11-04 13:10 UTC (permalink / raw)
To: Stefan Roese
Cc: linuxppc-dev-mnsaURCQ41sdnm+yROfE0A,
spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f
On Fri, Oct 31, 2008 at 12:07:46PM +0100, Stefan Roese wrote:
>This adds a SPI driver for the SPI controller found in the IBM/AMCC
>4xx PowerPC's.
>
>Signed-off-by: Stefan Roese <sr-ynQEQJNshbs@public.gmane.org>
>Signed-off-by: Wolfgang Ocker <weo-pMF0Ia4UJXeoYr4blSSd5g@public.gmane.org>
Acked-by: Josh Boyer <jwboyer-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8@public.gmane.org>
>---
>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 <garyj-ynQEQJNshbs@public.gmane.org>
>+ * Copyright 2008 Stefan Roese <sr-ynQEQJNshbs@public.gmane.org>, DENX Software Engineering
>+ *
>+ * Based in part on drivers/spi/spi_s3c24xx.c
>+ *
>+ * Copyright (c) 2006 Ben Dooks
>+ * Copyright (c) 2006 Simtec Electronics
>+ * Ben Dooks <ben-Y5A6D6n0/KfQXOPxS62xeg@public.gmane.org>
>+ *
>+ * 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/module.h>
>+#include <linux/init.h>
>+#include <linux/sched.h>
>+#include <linux/errno.h>
>+#include <linux/wait.h>
>+#include <linux/of_platform.h>
>+#include <linux/of_spi.h>
>+#include <linux/of_gpio.h>
>+#include <linux/interrupt.h>
>+#include <linux/delay.h>
>+
>+#include <linux/gpio.h>
>+#include <linux/spi/spi.h>
>+#include <linux/spi/spi_bitbang.h>
>+
>+#include <asm/io.h>
>+#include <asm/dcr.h>
>+#include <asm/dcr-regs.h>
>+
>+/* 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");
>--
>1.6.0.3
>
>_______________________________________________
>Linuxppc-dev mailing list
>Linuxppc-dev-mnsaURCQ41sdnm+yROfE0A@public.gmane.org
>https://ozlabs.org/mailman/listinfo/linuxppc-dev
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] spi: Add PPC4xx SPI driver
[not found] ` <1225451266-23740-1-git-send-email-sr-ynQEQJNshbs@public.gmane.org>
2008-11-04 13:10 ` Josh Boyer
@ 2008-11-21 3:40 ` David Brownell
2008-11-21 3:41 ` David Brownell
2 siblings, 0 replies; 5+ messages in thread
From: David Brownell @ 2008-11-21 3:40 UTC (permalink / raw)
To: Stefan Roese
Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
linuxppc-dev-mnsaURCQ41sdnm+yROfE0A
On Friday 31 October 2008, Stefan Roese wrote:
> +static int spi_ppc4xx_setupxfer(struct spi_device *spi, struct spi_transfer *t)
> +{
> ...
>
> + if (in_8(&hw->regs->cdm) != cdm)
> + out_8(&hw->regs->cdm, cdm);
... writes to hardware, updating SPI the clock rate ...
> +static int spi_ppc4xx_setup(struct spi_device *spi)
> +{
> ...
> +
> + ret = spi_ppc4xx_setupxfer(spi, NULL);
... hence this also writes to the hardware.
Except ... the setup() method may be called for one SPI device
in the middle of a transfer for another device. This might for
example cause the clock rate to speed up so it's too fast, thus
corrupting a transfer for that other device. So you should NOT
be calling setupxfer() here.
> + /* write new configration */
> + out_8(&hw->regs->mode, mode);
Or here: changing from a device using MSB-first mode 3 to
one using LSB-first mode 1. This could also corrupt a transfer.
It might be better to have the setup() validate its inputs
and store them in the spi->controller_state, and then have
the setup_transfer() pull values from there ... but not make
setup() call setupxfer(). The best model would be that each
chipselect has its own set of speed/mode registers; if the
hardware doesn't work that way, then it can be emulated.
> +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);
I prefer to see module_{init,exit} annotations snugged up next
to the functions to which they apply ... just like EXPORT_SYMBOL
annotations, and for the same reasons.
- Dave
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] spi: Add PPC4xx SPI driver
[not found] ` <1225451266-23740-1-git-send-email-sr-ynQEQJNshbs@public.gmane.org>
2008-11-04 13:10 ` Josh Boyer
2008-11-21 3:40 ` David Brownell
@ 2008-11-21 3:41 ` David Brownell
2008-11-21 18:01 ` [spi-devel-general] " Stefan Roese
2 siblings, 1 reply; 5+ messages in thread
From: David Brownell @ 2008-11-21 3:41 UTC (permalink / raw)
To: Stefan Roese
Cc: spi-devel-general-5NWGOfrQmneRv+LV9MX5uipxlwaOVQ5f,
linuxppc-dev-mnsaURCQ41sdnm+yROfE0A
On Friday 31 October 2008, Stefan Roese wrote:
> + dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
> + __FUNCTION__, spi->mode, spi->bits_per_word,
> + spi->max_speed_hz);
Oh, and checkpatch.pl would warn about __FUNCTION__ vs __func__ ...
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [spi-devel-general] [PATCH v4] spi: Add PPC4xx SPI driver
2008-11-21 3:41 ` David Brownell
@ 2008-11-21 18:01 ` Stefan Roese
0 siblings, 0 replies; 5+ messages in thread
From: Stefan Roese @ 2008-11-21 18:01 UTC (permalink / raw)
To: David Brownell; +Cc: spi-devel-general, linuxppc-dev
Dave,
On Friday 21 November 2008, David Brownell wrote:
> On Friday 31 October 2008, Stefan Roese wrote:
> > + dev_dbg(&spi->dev, "%s: mode %d, %u bpw, %d hz\n",
> > + __FUNCTION__, spi->mode, spi->bits_per_word,
> > + spi->max_speed_hz);
>
> Oh, and checkpatch.pl would warn about __FUNCTION__ vs __func__ ...
Thanks for the review. I'll address the issues in an updated patch next week.
Best regards,
Stefan
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2008-11-21 18:01 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-10-31 11:07 [PATCH v4] spi: Add PPC4xx SPI driver Stefan Roese
[not found] ` <1225451266-23740-1-git-send-email-sr-ynQEQJNshbs@public.gmane.org>
2008-11-04 13:10 ` Josh Boyer
2008-11-21 3:40 ` David Brownell
2008-11-21 3:41 ` David Brownell
2008-11-21 18:01 ` [spi-devel-general] " Stefan Roese
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).