diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-18 10:56:02 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-03-18 10:56:02 -0700 |
commit | b061c59c27e0385e53c961d9fbd18c1c078d9823 (patch) | |
tree | 56240ef8e98e9e4712ee58aa8e6e3d51f6ab001f | |
parent | 99f4065bac7b8c3f829334b4218a5c2e68cbe440 (diff) | |
parent | 568a60eda2e90a11bb3d7f8ef3f6800e9b60d4e5 (diff) |
Merge branch 'spi/next' of git://git.secretlab.ca/git/linux-2.6
* 'spi/next' of git://git.secretlab.ca/git/linux-2.6: (34 commits)
spi/dw_spi: move dw_spi.h into drivers/spi
spi/dw_spi: Fix missing header
gpio/langwell: Clear edge bit before handling
gpio/langwell: Simplify demux loop
gpio/langwell: Convert irq name space
gpio/langwell: Fix broken irq_eoi change.
gpio; Make Intel chipset gpio drivers depend on x86
gpio/cs5535-gpio: Fix section mismatch
spi/rtc-{ds1390,ds3234,m41t94}: Use spi_get_drvdata() for SPI devices
spi/davinci: Support DMA transfers larger than 65535 words
spi/davinci: Use correct length parameter to dma_map_single calls
gpio: Use __devexit at necessary places
gpio: add MODULE_DEVICE_TABLE to pch_gpio and ml_ioh_gpio
gpio/mcp23s08: support mcp23s17 variant
of_mmc_spi: add card detect irq support
spi/omap_mcspi: catch xfers of non-multiple SPI word size
spi/omap_mcspi: Off-by-one error in finding the right divisor
gpio/pca953x: Fix wrong pointer type
spi/pl022: rid dangling labels
spi: add support for SuperH SPI
...
35 files changed, 1920 insertions, 261 deletions
diff --git a/Documentation/devicetree/bindings/mmc/mmc-spi-slot.txt b/Documentation/devicetree/bindings/mmc/mmc-spi-slot.txt index c39ac2891951..89a0084df2f7 100644 --- a/Documentation/devicetree/bindings/mmc/mmc-spi-slot.txt +++ b/Documentation/devicetree/bindings/mmc/mmc-spi-slot.txt @@ -7,8 +7,13 @@ Required properties: - voltage-ranges : two cells are required, first cell specifies minimum slot voltage (mV), second cell specifies maximum slot voltage (mV). Several ranges could be specified. -- gpios : (optional) may specify GPIOs in this order: Card-Detect GPIO, + +Optional properties: +- gpios : may specify GPIOs in this order: Card-Detect GPIO, Write-Protect GPIO. +- interrupts : the interrupt of a card detect interrupt. +- interrupt-parent : the phandle for the interrupt controller that + services interrupts for this device. Example: @@ -20,4 +25,6 @@ Example: &qe_pio_d 15 0>; voltage-ranges = <3300 3300>; spi-max-frequency = <50000000>; + interrupts = <42>; + interrupt-parent = <&PIC>; }; diff --git a/Documentation/devicetree/bindings/spi/spi_altera.txt b/Documentation/devicetree/bindings/spi/spi_altera.txt new file mode 100644 index 000000000000..dda375943506 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi_altera.txt @@ -0,0 +1,4 @@ +Altera SPI + +Required properties: +- compatible : should be "ALTR,spi-1.0". diff --git a/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt new file mode 100644 index 000000000000..d95c0b367a04 --- /dev/null +++ b/Documentation/devicetree/bindings/spi/spi_oc_tiny.txt @@ -0,0 +1,12 @@ +OpenCores tiny SPI + +Required properties: +- compatible : should be "opencores,tiny-spi-rtlsvn2". +- gpios : should specify GPIOs used for chipselect. +Optional properties: +- clock-frequency : input clock frequency to the core. +- baud-width: width, in bits, of the programmable divider used to scale + the input clock to SCLK. + +The clock-frequency and baud-width properties are needed only if the divider +is programmable. They are not needed if the divider is fixed. diff --git a/drivers/gpio/74x164.c b/drivers/gpio/74x164.c index d91ff4c282e9..84e070219839 100644 --- a/drivers/gpio/74x164.c +++ b/drivers/gpio/74x164.c @@ -133,7 +133,7 @@ exit_destroy: return ret; } -static int gen_74x164_remove(struct spi_device *spi) +static int __devexit gen_74x164_remove(struct spi_device *spi) { struct gen_74x164_chip *chip; int ret; diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 664660e56335..b46442d7d66e 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -101,7 +101,7 @@ config GPIO_VR41XX config GPIO_SCH tristate "Intel SCH GPIO" - depends on GPIOLIB && PCI + depends on GPIOLIB && PCI && X86 select MFD_CORE select LPC_SCH help @@ -321,13 +321,13 @@ config GPIO_BT8XX config GPIO_LANGWELL bool "Intel Langwell/Penwell GPIO support" - depends on PCI + depends on PCI && X86 help Say Y here to support Intel Langwell/Penwell GPIO. config GPIO_PCH tristate "PCH GPIO of Intel Topcliff" - depends on PCI + depends on PCI && X86 help This driver is for PCH(Platform controller Hub) GPIO of Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded processor. @@ -368,11 +368,11 @@ config GPIO_MAX7301 GPIO driver for Maxim MAX7301 SPI-based GPIO expander. config GPIO_MCP23S08 - tristate "Microchip MCP23S08 I/O expander" + tristate "Microchip MCP23Sxx I/O expander" depends on SPI_MASTER help - SPI driver for Microchip MCP23S08 I/O expander. This provides - a GPIO interface supporting inputs and outputs. + SPI driver for Microchip MCP23S08/MPC23S17 I/O expanders. + This provides a GPIO interface supporting inputs and outputs. config GPIO_MC33880 tristate "Freescale MC33880 high-side/low-side switch" diff --git a/drivers/gpio/cs5535-gpio.c b/drivers/gpio/cs5535-gpio.c index 0d05ea7d499b..6e16cba56ad2 100644 --- a/drivers/gpio/cs5535-gpio.c +++ b/drivers/gpio/cs5535-gpio.c @@ -373,7 +373,7 @@ static int __devexit cs5535_gpio_remove(struct platform_device *pdev) return 0; } -static struct platform_driver cs5535_gpio_drv = { +static struct platform_driver cs5535_gpio_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, @@ -384,12 +384,12 @@ static struct platform_driver cs5535_gpio_drv = { static int __init cs5535_gpio_init(void) { - return platform_driver_register(&cs5535_gpio_drv); + return platform_driver_register(&cs5535_gpio_driver); } static void __exit cs5535_gpio_exit(void) { - platform_driver_unregister(&cs5535_gpio_drv); + platform_driver_unregister(&cs5535_gpio_driver); } module_init(cs5535_gpio_init); diff --git a/drivers/gpio/langwell_gpio.c b/drivers/gpio/langwell_gpio.c index 54d70a47afc1..560ab648cf18 100644 --- a/drivers/gpio/langwell_gpio.c +++ b/drivers/gpio/langwell_gpio.c @@ -187,31 +187,28 @@ MODULE_DEVICE_TABLE(pci, lnw_gpio_ids); static void lnw_irq_handler(unsigned irq, struct irq_desc *desc) { - struct lnw_gpio *lnw = get_irq_data(irq); - u32 base, gpio; + struct irq_data *data = irq_desc_get_irq_data(desc); + struct lnw_gpio *lnw = irq_data_get_irq_handler_data(data); + struct irq_chip *chip = irq_data_get_irq_chip(data); + u32 base, gpio, mask; + unsigned long pending; void __iomem *gedr; - u32 gedr_v; /* check GPIO controller to check which pin triggered the interrupt */ for (base = 0; base < lnw->chip.ngpio; base += 32) { gedr = gpio_reg(&lnw->chip, base, GEDR); - gedr_v = readl(gedr); - if (!gedr_v) - continue; - for (gpio = base; gpio < base + 32; gpio++) - if (gedr_v & BIT(gpio % 32)) { - pr_debug("pin %d triggered\n", gpio); - generic_handle_irq(lnw->irq_base + gpio); - } - /* clear the edge detect status bit */ - writel(gedr_v, gedr); + pending = readl(gedr); + while (pending) { + gpio = __ffs(pending) - 1; + mask = BIT(gpio); + pending &= ~mask; + /* Clear before handling so we can't lose an edge */ + writel(mask, gedr); + generic_handle_irq(lnw->irq_base + base + gpio); + } } - if (desc->chip->irq_eoi) - desc->chip->irq_eoi(irq_get_irq_data(irq)); - else - dev_warn(lnw->chip.dev, "missing EOI handler for irq %d\n", irq); - + chip->irq_eoi(data); } static int __devinit lnw_gpio_probe(struct pci_dev *pdev, @@ -279,12 +276,12 @@ static int __devinit lnw_gpio_probe(struct pci_dev *pdev, dev_err(&pdev->dev, "langwell gpiochip_add error %d\n", retval); goto err5; } - set_irq_data(pdev->irq, lnw); - set_irq_chained_handler(pdev->irq, lnw_irq_handler); + irq_set_handler_data(pdev->irq, lnw); + irq_set_chained_handler(pdev->irq, lnw_irq_handler); for (i = 0; i < lnw->chip.ngpio; i++) { - set_irq_chip_and_handler_name(i + lnw->irq_base, &lnw_irqchip, - handle_simple_irq, "demux"); - set_irq_chip_data(i + lnw->irq_base, lnw); + irq_set_chip_and_handler_name(i + lnw->irq_base, &lnw_irqchip, + handle_simple_irq, "demux"); + irq_set_chip_data(i + lnw->irq_base, lnw); } spin_lock_init(&lnw->lock); diff --git a/drivers/gpio/mc33880.c b/drivers/gpio/mc33880.c index 935479da6705..00f6d24c669d 100644 --- a/drivers/gpio/mc33880.c +++ b/drivers/gpio/mc33880.c @@ -146,7 +146,7 @@ exit_destroy: return ret; } -static int mc33880_remove(struct spi_device *spi) +static int __devexit mc33880_remove(struct spi_device *spi) { struct mc33880 *mc; int ret; diff --git a/drivers/gpio/mcp23s08.c b/drivers/gpio/mcp23s08.c index 69f6f1955a31..40e076083ec0 100644 --- a/drivers/gpio/mcp23s08.c +++ b/drivers/gpio/mcp23s08.c @@ -10,7 +10,13 @@ #include <linux/spi/spi.h> #include <linux/spi/mcp23s08.h> #include <linux/slab.h> +#include <asm/byteorder.h> +/** + * MCP types supported by driver + */ +#define MCP_TYPE_S08 0 +#define MCP_TYPE_S17 1 /* Registers are all 8 bits wide. * @@ -35,27 +41,38 @@ #define MCP_GPIO 0x09 #define MCP_OLAT 0x0a +struct mcp23s08; + +struct mcp23s08_ops { + int (*read)(struct mcp23s08 *mcp, unsigned reg); + int (*write)(struct mcp23s08 *mcp, unsigned reg, unsigned val); + int (*read_regs)(struct mcp23s08 *mcp, unsigned reg, + u16 *vals, unsigned n); +}; + struct mcp23s08 { struct spi_device *spi; u8 addr; - u8 cache[11]; + u16 cache[11]; /* lock protects the cached values */ struct mutex lock; struct gpio_chip chip; struct work_struct work; + + const struct mcp23s08_ops *ops; }; -/* A given spi_device can represent up to four mcp23s08 chips +/* A given spi_device can represent up to eight mcp23sxx chips * sharing the same chipselect but using different addresses * (e.g. chips #0 and #3 might be populated, but not #1 or $2). * Driver data holds all the per-chip data. */ struct mcp23s08_driver_data { unsigned ngpio; - struct mcp23s08 *mcp[4]; + struct mcp23s08 *mcp[8]; struct mcp23s08 chip[]; }; @@ -70,7 +87,7 @@ static int mcp23s08_read(struct mcp23s08 *mcp, unsigned reg) return (status < 0) ? status : rx[0]; } -static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, u8 val) +static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) { u8 tx[3]; @@ -81,17 +98,81 @@ static int mcp23s08_write(struct mcp23s08 *mcp, unsigned reg, u8 val) } static int -mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u8 *vals, unsigned n) +mcp23s08_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) { - u8 tx[2]; + u8 tx[2], *tmp; + int status; if ((n + reg) > sizeof mcp->cache) return -EINVAL; tx[0] = mcp->addr | 0x01; tx[1] = reg; - return spi_write_then_read(mcp->spi, tx, sizeof tx, vals, n); + + tmp = (u8 *)vals; + status = spi_write_then_read(mcp->spi, tx, sizeof tx, tmp, n); + if (status >= 0) { + while (n--) + vals[n] = tmp[n]; /* expand to 16bit */ + } + return status; +} + +static int mcp23s17_read(struct mcp23s08 *mcp, unsigned reg) +{ + u8 tx[2], rx[2]; + int status; + + tx[0] = mcp->addr | 0x01; + tx[1] = reg << 1; + status = spi_write_then_read(mcp->spi, tx, sizeof tx, rx, sizeof rx); + return (status < 0) ? status : (rx[0] | (rx[1] << 8)); +} + +static int mcp23s17_write(struct mcp23s08 *mcp, unsigned reg, unsigned val) +{ + u8 tx[4]; + + tx[0] = mcp->addr; + tx[1] = reg << 1; + tx[2] = val; + tx[3] = val >> 8; + return spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); +} + +static int +mcp23s17_read_regs(struct mcp23s08 *mcp, unsigned reg, u16 *vals, unsigned n) +{ + u8 tx[2]; + int status; + + if ((n + reg) > sizeof mcp->cache) + return -EINVAL; + tx[0] = mcp->addr | 0x01; + tx[1] = reg << 1; + + status = spi_write_then_read(mcp->spi, tx, sizeof tx, + (u8 *)vals, n * 2); + if (status >= 0) { + while (n--) + vals[n] = __le16_to_cpu((__le16)vals[n]); + } + + return status; } +static const struct mcp23s08_ops mcp23s08_ops = { + .read = mcp23s08_read, + .write = mcp23s08_write, + .read_regs = mcp23s08_read_regs, +}; + +static const struct mcp23s08_ops mcp23s17_ops = { + .read = mcp23s17_read, + .write = mcp23s17_write, + .read_regs = mcp23s17_read_regs, +}; + + /*----------------------------------------------------------------------*/ static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) @@ -101,7 +182,7 @@ static int mcp23s08_direction_input(struct gpio_chip *chip, unsigned offset) mutex_lock(&mcp->lock); mcp->cache[MCP_IODIR] |= (1 << offset); - status = mcp23s08_write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); + status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); mutex_unlock(&mcp->lock); return status; } @@ -114,7 +195,7 @@ static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) mutex_lock(&mcp->lock); /* REVISIT reading this clears any IRQ ... */ - status = mcp23s08_read(mcp, MCP_GPIO); + status = mcp->ops->read(mcp, MCP_GPIO); if (status < 0) status = 0; else { @@ -127,20 +208,20 @@ static int mcp23s08_get(struct gpio_chip *chip, unsigned offset) static int __mcp23s08_set(struct mcp23s08 *mcp, unsigned mask, int value) { - u8 olat = mcp->cache[MCP_OLAT]; + unsigned olat = mcp->cache[MCP_OLAT]; if (value) olat |= mask; else olat &= ~mask; mcp->cache[MCP_OLAT] = olat; - return mcp23s08_write(mcp, MCP_OLAT, olat); + return mcp->ops->write(mcp, MCP_OLAT, olat); } static void mcp23s08_set(struct gpio_chip *chip, unsigned offset, int value) { struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); - u8 mask = 1 << offset; + unsigned mask = 1 << offset; mutex_lock(&mcp->lock); __mcp23s08_set(mcp, mask, value); @@ -151,14 +232,14 @@ static int mcp23s08_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct mcp23s08 *mcp = container_of(chip, struct mcp23s08, chip); - u8 mask = 1 << offset; + unsigned mask = 1 << offset; int status; mutex_lock(&mcp->lock); status = __mcp23s08_set(mcp, mask, value); if (status == 0) { mcp->cache[MCP_IODIR] &= ~mask; - status = mcp23s08_write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); + status = mcp->ops->write(mcp, MCP_IODIR, mcp->cache[MCP_IODIR]); } mutex_unlock(&mcp->lock); return status; @@ -184,16 +265,16 @@ static void mcp23s08_dbg_show(struct seq_file *s, struct gpio_chip *chip) mcp = container_of(chip, struct mcp23s08, chip); /* NOTE: we only handle one bank for now ... */ - bank = '0' + ((mcp->addr >> 1) & 0x3); + bank = '0' + ((mcp->addr >> 1) & 0x7); mutex_lock(&mcp->lock); - t = mcp23s08_read_regs(mcp, 0, mcp->cache, sizeof mcp->cache); + t = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); if (t < 0) { seq_printf(s, " I/O ERROR %d\n", t); goto done; } - for (t = 0, mask = 1; t < 8; t++, mask <<= 1) { + for (t = 0, mask = 1; t < chip->ngpio; t++, mask <<= 1) { const char *label; label = gpiochip_is_requested(chip, t); @@ -219,28 +300,33 @@ done: /*----------------------------------------------------------------------*/ static int mcp23s08_probe_one(struct spi_device *spi, unsigned addr, - unsigned base, unsigned pullups) + unsigned type, unsigned base, unsigned pullups) { struct mcp23s08_driver_data *data = spi_get_drvdata(spi); struct mcp23s08 *mcp = data->mcp[addr]; int status; - int do_update = 0; mutex_init(&mcp->lock); mcp->spi = spi; mcp->addr = 0x40 | (addr << 1); - mcp->chip.label = "mcp23s08", - mcp->chip.direction_input = mcp23s08_direction_input; mcp->chip.get = mcp23s08_get; mcp->chip.direction_output = mcp23s08_direction_output; mcp->chip.set = mcp23s08_set; mcp->chip.dbg_show = mcp23s08_dbg_show; + if (type == MCP_TYPE_S17) { + mcp->ops = &mcp23s17_ops; + mcp->chip.ngpio = 16; + mcp->chip.label = "mcp23s17"; + } else { + mcp->ops = &mcp23s08_ops; + mcp->chip.ngpio = 8; + mcp->chip.label = "mcp23s08"; + } mcp->chip.base = base; - mcp->chip.ngpio = 8; mcp->chip.can_sleep = 1; mcp->chip.dev = &spi->dev; mcp->chip.owner = THIS_MODULE; @@ -248,45 +334,39 @@ static int mcp23s08_probe_one(struct spi_device *spi, unsigned addr, /* verify MCP_IOCON.SEQOP = 0, so sequential reads work, * and MCP_IOCON.HAEN = 1, so we work with all chips. */ - status = mcp23s08_read(mcp, MCP_IOCON); + status = mcp->ops->read(mcp, MCP_IOCON); if (status < 0) goto fail; if ((status & IOCON_SEQOP) || !(status & IOCON_HAEN)) { - status &= ~IOCON_SEQOP; - status |= IOCON_HAEN; - status = mcp23s08_write(mcp, MCP_IOCON, (u8) status); + /* mcp23s17 has IOCON twice, make sure they are in sync */ + status &= ~(IOCON_SEQOP | (IOCON_SEQOP << 8)); + status |= IOCON_HAEN | (IOCON_HAEN << 8); + status = mcp->ops->write(mcp, MCP_IOCON, status); if (status < 0) goto fail; } /* configure ~100K pullups */ - status = mcp23s08_write(mcp, MCP_GPPU, pullups); + status = mcp->ops->write(mcp, MCP_GPPU, pullups); if (status < 0) goto fail; - status = mcp23s08_read_regs(mcp, 0, mcp->cache, sizeof mcp->cache); + status = mcp->ops->read_regs(mcp, 0, mcp->cache, ARRAY_SIZE(mcp->cache)); if (status < 0) goto fail; /* disable inverter on input */ if (mcp->cache[MCP_IPOL] != 0) { mcp->cache[MCP_IPOL] = 0; - do_update = 1; + status = mcp->ops->write(mcp, MCP_IPOL, 0); + if (status < 0) + goto fail; } /* disable irqs */ if (mcp->cache[MCP_GPINTEN] != 0) { mcp->cache[MCP_GPINTEN] = 0; - do_update = 1; - } - - if (do_update) { - u8 tx[4]; - - tx[0] = mcp->addr; - tx[1] = MCP_IPOL; - memcpy(&tx[2], &mcp->cache[MCP_IPOL], sizeof(tx) - 2); - status = spi_write_then_read(mcp->spi, tx, sizeof tx, NULL, 0); + status = mcp->ops->write(mcp, MCP_GPINTEN, 0); if (status < 0) goto fail; } @@ -305,19 +385,26 @@ static int mcp23s08_probe(struct spi_device *spi) unsigned addr; unsigned chips = 0; struct mcp23s08_driver_data *data; - int status; + int status, type; unsigned base; + type = spi_get_device_id(spi)->driver_data; + pdata = spi->dev.platform_data; if (!pdata || !gpio_is_valid(pdata->base)) { dev_dbg(&spi->dev, "invalid or missing platform data\n"); return -EINVAL; } - for (addr = 0; addr < 4; addr++) { + for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { if (!pdata->chip[addr].is_present) continue; chips++; + if ((type == MCP_TYPE_S08) && (addr > 3)) { + dev_err(&spi->dev, + "mcp23s08 only supports address 0..3\n"); + return -EINVAL; + } } if (!chips) return -ENODEV; @@ -329,16 +416,17 @@ static int mcp23s08_probe(struct spi_device *spi) spi_set_drvdata(spi, data); base = pdata->base; - for (addr = 0; addr < 4; addr++) { + for (addr = 0; addr < ARRAY_SIZE(pdata->chip); addr++) { if (!pdata->chip[addr].is_present) continue; chips--; data->mcp[addr] = &data->chip[chips]; - status = mcp23s08_probe_one(spi, addr, base, - pdata->chip[addr].pullups); + status = mcp23s08_probe_one(spi, addr, type, base, + pdata->chip[addr].pullups); if (status < 0) goto fail; - base += 8; + + base += (type == MCP_TYPE_S17) ? 16 : 8; } data->ngpio = base - pdata->base; @@ -358,7 +446,7 @@ static int mcp23s08_probe(struct spi_device *spi) return 0; fail: - for (addr = 0; addr < 4; addr++) { + for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { int tmp; if (!data->mcp[addr]) @@ -388,7 +476,7 @@ static int mcp23s08_remove(struct spi_device *spi) } } - for (addr = 0; addr < 4; addr++) { + for (addr = 0; addr < ARRAY_SIZE(data->mcp); addr++) { int tmp; if (!data->mcp[addr]) @@ -405,9 +493,17 @@ static int mcp23s08_remove(struct spi_device *spi) return status; } +static const struct spi_device_id mcp23s08_ids[] = { + { "mcp23s08", MCP_TYPE_S08 }, + { "mcp23s17", MCP_TYPE_S17 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, mcp23s08_ids); + static struct spi_driver mcp23s08_driver = { .probe = mcp23s08_probe, .remove = mcp23s08_remove, + .id_table = mcp23s08_ids, .driver = { .name = "mcp23s08", .owner = THIS_MODULE, @@ -432,4 +528,3 @@ static void __exit mcp23s08_exit(void) module_exit(mcp23s08_exit); MODULE_LICENSE("GPL"); -MODULE_ALIAS("spi:mcp23s08"); diff --git a/drivers/gpio/pca953x.c b/drivers/gpio/pca953x.c index b473429eee75..2fc25dec7cf5 100644 --- a/drivers/gpio/pca953x.c +++ b/drivers/gpio/pca953x.c @@ -462,7 +462,8 @@ pca953x_get_alt_pdata(struct i2c_client *client) { struct pca953x_platform_data *pdata; struct device_node *node; - const uint16_t *val; + const __be32 *val; + int size; node = client->dev.of_node; if (node == NULL) @@ -475,13 +476,13 @@ pca953x_get_alt_pdata(struct i2c_client *client) } pdata->gpio_base = -1; - val = of_get_property(node, "linux,gpio-base", NULL); + val = of_get_property(node, "linux,gpio-base", &size); if (val) { - if (*val < 0) - dev_warn(&client->dev, - "invalid gpio-base in device tree\n"); + if (size != sizeof(*val)) + dev_warn(&client->dev, "%s: wrong linux,gpio-base\n", + node->full_name); else - pdata->gpio_base = *val; + pdata->gpio_base = be32_to_cpup(val); } val = of_get_property(node, "polarity", NULL); diff --git a/drivers/gpio/sx150x.c b/drivers/gpio/sx150x.c index e60be0015c9b..d2f874c3d3d5 100644 --- a/drivers/gpio/sx150x.c +++ b/drivers/gpio/sx150x.c @@ -25,6 +25,8 @@ #include <linux/workqueue.h> #include <linux/i2c/sx150x.h> +#define NO_UPDATE_PENDING -1 + struct sx150x_device_data { u8 reg_pullup; u8 reg_pulldn; @@ -47,8 +49,11 @@ struct sx150x_chip { const struct sx150x_device_data *dev_cfg; int irq_summary; int irq_base; + int irq_update; u32 irq_sense; - unsigned long irq_set_type_pending; + u32 irq_masked; + u32 dev_sense; + u32 dev_masked; struct irq_chip irq_chip; struct mutex lock; }; @@ -312,9 +317,8 @@ static void sx150x_irq_mask(struct irq_data *d) chip = container_of(ic, struct sx150x_chip, irq_chip); n = d->irq - chip->irq_base; - - sx150x_write_cfg(chip, n, 1, chip->dev_cfg->reg_irq_mask, 1); - sx150x_write_cfg(chip, n, 2, chip->dev_cfg->reg_sense, 0); + chip->irq_masked |= (1 << n); + chip->irq_update = n; } static void sx150x_irq_unmask(struct irq_data *d) @@ -326,9 +330,8 @@ static void sx150x_irq_unmask(struct irq_data *d) chip = container_of(ic, struct sx150x_chip, irq_chip); n = d->irq - chip->irq_base; - sx150x_write_cfg(chip, n, 1, chip->dev_cfg->reg_irq_mask, 0); - sx150x_write_cfg(chip, n, 2, chip->dev_cfg->reg_sense, - chip->irq_sense >> (n * 2)); + chip->irq_masked &= ~(1 << n); + chip->irq_update = n; } static int sx150x_irq_set_type(struct irq_data *d, unsigned int flow_type) @@ -350,7 +353,7 @@ static int sx150x_irq_set_type(struct irq_data *d, unsigned int flow_type) chip->irq_sense &= ~(3UL << (n * 2)); chip->irq_sense |= val << (n * 2); - chip->irq_set_type_pending |= BIT(n); + chip->irq_update = n; return 0; } @@ -404,15 +407,29 @@ static void sx150x_irq_bus_sync_unlock(struct irq_data *d) chip = container_of(ic, struct sx150x_chip, irq_chip); - while (chip->irq_set_type_pending) { - n = __ffs(chip->irq_set_type_pending); - chip->irq_set_type_pending &= ~BIT(n); - if (!(irq_to_desc(n + chip->irq_base)->status & IRQ_MASKED)) - sx150x_write_cfg(chip, n, 2, - chip->dev_cfg->reg_sense, - chip->irq_sense >> (n * 2)); - } + if (chip->irq_update == NO_UPDATE_PENDING) + goto out; + + n = chip->irq_update; + chip->irq_update = NO_UPDATE_PENDING; + /* Avoid updates if nothing changed */ + if (chip->dev_sense == chip->irq_sense && + chip->dev_sense == chip->irq_masked) + goto out; + + chip->dev_sense = chip->irq_sense; + chip->dev_masked = chip->irq_masked; + + if (chip->irq_masked & (1 << n)) { + sx150x_write_cfg(chip, n, 1, chip->dev_cfg->reg_irq_mask, 1); + sx150x_write_cfg(chip, n, 2, chip->dev_cfg->reg_sense, 0); + } else { + sx150x_write_cfg(chip, n, 1, chip->dev_cfg->reg_irq_mask, 0); + sx150x_write_cfg(chip, n, 2, chip->dev_cfg->reg_sense, + chip->irq_sense >> (n * 2)); + } +out: mutex_unlock(&chip->lock); } @@ -445,8 +462,11 @@ static void sx150x_init_chip(struct sx150x_chip *chip, chip->irq_chip.irq_bus_sync_unlock = sx150x_irq_bus_sync_unlock; chip->irq_summary = -1; chip->irq_base = -1; + chip->irq_masked = ~0; chip->irq_sense = 0; - chip->irq_set_type_pending = 0; + chip->dev_masked = ~0; + chip->dev_sense = 0; + chip->irq_update = NO_UPDATE_PENDING; } static int sx150x_init_io(struct sx150x_chip *chip, u8 base, u16 cfg) diff --git a/drivers/mmc/host/of_mmc_spi.c b/drivers/mmc/host/of_mmc_spi.c index 1247e5de9faa..5530def54e5b 100644 --- a/drivers/mmc/host/of_mmc_spi.c +++ b/drivers/mmc/host/of_mmc_spi.c @@ -34,6 +34,7 @@ enum { struct of_mmc_spi { int gpios[NUM_GPIOS]; bool alow_gpios[NUM_GPIOS]; + int detect_irq; struct mmc_spi_platform_data pdata; }; @@ -61,6 +62,22 @@ static int of_mmc_spi_get_ro(struct device *dev) return of_mmc_spi_read_gpio(dev, WP_GPIO); } +static int of_mmc_spi_init(struct device *dev, + irqreturn_t (*irqhandler)(int, void *), void *mmc) +{ + struct of_mmc_spi *oms = to_of_mmc_spi(dev); + + return request_threaded_irq(oms->detect_irq, NULL, irqhandler, 0, + dev_name(dev), mmc); +} + +static void of_mmc_spi_exit(struct device *dev, void *mmc) +{ + struct of_mmc_spi *oms = to_of_mmc_spi(dev); + + free_irq(oms->detect_irq, mmc); +} + struct mmc_spi_platform_data *mmc_spi_get_pdata(struct spi_device *spi) { struct device *dev = &spi->dev; @@ -121,8 +138,13 @@ struct mmc_spi_platform_data *mmc_spi_get_pdata(struct spi_device *spi) if (gpio_is_valid(oms->gpios[WP_GPIO])) oms->pdata.get_ro = of_mmc_spi_get_ro; - /* We don't support interrupts yet, let's poll. */ - oms->pdata.caps |= MMC_CAP_NEEDS_POLL; + oms->detect_irq = irq_of_parse_and_map(np, 0); + if (oms->detect_irq != NO_IRQ) { + oms->pdata.init = of_mmc_spi_init; + oms->pdata.exit = of_mmc_spi_exit; + } else { + oms->pdata.caps |= MMC_CAP_NEEDS_POLL; + } dev->platform_data = &oms->pdata; return dev->platform_data; diff --git a/drivers/rtc/rtc-ds1390.c b/drivers/rtc/rtc-ds1390.c index 26a86d235051..b038d2cfef26 100644 --- a/drivers/rtc/rtc-ds1390.c +++ b/drivers/rtc/rtc-ds1390.c @@ -158,7 +158,7 @@ static int __devinit ds1390_probe(struct spi_device *spi) static int __devexit ds1390_remove(struct spi_device *spi) { - struct ds1390 *chip = platform_get_drvdata(spi); + struct ds1390 *chip = spi_get_drvdata(spi); rtc_device_unregister(chip->rtc); kfree(chip); diff --git a/drivers/rtc/rtc-ds3234.c b/drivers/rtc/rtc-ds3234.c index a774ca35b5f7..bbd26228f532 100644 --- a/drivers/rtc/rtc-ds3234.c +++ b/drivers/rtc/rtc-ds3234.c @@ -158,7 +158,7 @@ static int __devinit ds3234_probe(struct spi_device *spi) static int __devexit ds3234_remove(struct spi_device *spi) { - struct rtc_device *rtc = platform_get_drvdata(spi); + struct rtc_device *rtc = spi_get_drvdata(spi); rtc_device_unregister(rtc); return 0; diff --git a/drivers/rtc/rtc-m41t94.c b/drivers/rtc/rtc-m41t94.c index c8c97a4169d4..e259ed76ae85 100644 --- a/drivers/rtc/rtc-m41t94.c +++ b/drivers/rtc/rtc-m41t94.c @@ -136,7 +136,7 @@ static int __devinit m41t94_probe(struct spi_device *spi) static int __devexit m41t94_remove(struct spi_device *spi) { - struct rtc_device *rtc = platform_get_drvdata(spi); + struct rtc_device *rtc = spi_get_drvdata(spi); if (rtc) rtc_device_unregister(rtc); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 7b90fc361b52..fc14b8dea0d7 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -53,6 +53,12 @@ if SPI_MASTER comment "SPI Master Controller Drivers" +config SPI_ALTERA + tristate "Altera SPI Controller" + select SPI_BITBANG + help + This is the driver for the Altera SPI Controller. + config SPI_ATH79 tristate "Atheros AR71XX/AR724X/AR913X SPI controller driver" depends on ATH79 && GENERIC_GPIO @@ -231,6 +237,13 @@ config SPI_FSL_ESPI From MPC8536, 85xx platform uses the controller, and all P10xx, P20xx, P30xx,P40xx, P50xx uses this controller. +config SPI_OC_TINY + tristate "OpenCores tiny SPI" + depends on GENERIC_GPIO + select SPI_BITBANG + help + This is the driver for OpenCores tiny SPI master controller. + config SPI_OMAP_UWIRE tristate "OMAP1 MicroWire" depends on ARCH_OMAP1 @@ -330,6 +343,12 @@ config SPI_SH_MSIOF help SPI driver for SuperH MSIOF blocks. +config SPI_SH + tristate "SuperH SPI controller" + depends on SUPERH + help + SPI driver for SuperH SPI blocks. + config SPI_SH_SCI tristate "SuperH SCI SPI controller" depends on SUPERH diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index f3f31d988358..fd2fc5f6505f 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -9,6 +9,7 @@ ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG obj-$(CONFIG_SPI_MASTER) += spi.o # SPI master controller drivers (bus) +obj-$(CONFIG_SPI_ALTERA) += spi_altera.o obj-$(CONFIG_SPI_ATMEL) += atmel_spi.o obj-$(CONFIG_SPI_ATH79) += ath79_spi.o obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o @@ -27,6 +28,7 @@ obj-$(CONFIG_SPI_IMX) += spi_imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o obj-$(CONFIG_SPI_PXA2XX) += pxa2xx_spi.o obj-$(CONFIG_SPI_PXA2XX_PCI) += pxa2xx_spi_pci.o +obj-$(CONFIG_SPI_OC_TINY) += spi_oc_tiny.o obj-$(CONFIG_SPI_OMAP_UWIRE) += omap_uwire.o obj-$(CONFIG_SPI_OMAP24XX) += omap2_mcspi.o obj-$(CONFIG_SPI_OMAP_100K) += omap_spi_100k.o @@ -47,6 +49,7 @@ obj-$(CONFIG_SPI_TI_SSP) += ti-ssp-spi.o obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi_topcliff_pch.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o +obj-$(CONFIG_SPI_SH) += spi_sh.o obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o diff --git a/drivers/spi/amba-pl022.c b/drivers/spi/amba-pl022.c index 95e58c70a2c9..5c2b092a915e 100644 --- a/drivers/spi/amba-pl022.c +++ b/drivers/spi/amba-pl022.c @@ -329,15 +329,16 @@ struct vendor_data { /** * struct pl022 - This is the private SSP driver data structure * @adev: AMBA device model hookup - * @vendor: Vendor data for the IP block - * @phybase: The physical memory where the SSP device resides - * @virtbase: The virtual memory where the SSP is mapped + * @vendor: vendor data for the IP block + * @phybase: the physical memory where the SSP device resides + * @virtbase: the virtual memory where the SSP is mapped + * @clk: outgoing clock "SPICLK" for the SPI bus * @master: SPI framework hookup * @master_info: controller-specific data from machine setup - * @regs: SSP controller register's virtual address - * @pump_messages: Work struct for scheduling work to the workqueue - * @lock: spinlock to syncronise access to driver data * @workqueue: a workqueue on which any spi_message request is queued + * @pump_messages: work struct for scheduling work to the workqueue + * @queue_lock: spinlock to syncronise access to message queue + * @queue: message queue * @busy: workqueue is busy * @running: workqueue is running * @pump_transfers: Tasklet used in Interrupt Transfer mode @@ -348,8 +349,14 @@ struct vendor_data { * @tx_end: end position in TX buffer to be read * @rx: current position in RX buffer to be written * @rx_end: end position in RX buffer to be written - * @readingtype: the type of read currently going on - * @writingtype: the type or write currently going on + * @read: the type of read currently going on + * @write: the type of write currently going on + * @exp_fifo_level: expected FIFO level + * @dma_rx_channel: optional channel for RX DMA + * @dma_tx_channel: optional channel for TX DMA + * @sgt_rx: scattertable for the RX transfer + * @sgt_tx: scattertable for the TX transfer + * @dummypage: a dummy page used for driving data on the bus with DMA */ struct pl022 { struct amba_device *adev; @@ -397,8 +404,8 @@ struct pl022 { * @cpsr: Value of Clock prescale register * @n_bytes: how many bytes(power of 2) reqd for a given data width of client * @enable_dma: Whether to enable DMA or not - * @write: function ptr to be used to write when doing xfer for this chip * @read: function ptr to be used to read when doing xfer for this chip + * @write: function ptr to be used to write when doing xfer for this chip * @cs_control: chip select callback provided by chip * @xfer_type: polling/interrupt/DMA * @@ -508,9 +515,10 @@ static void giveback(struct pl022 *pl022) msg->state = NULL; if (msg->complete) msg->complete(msg->context); - /* This message is completed, so let's turn off the clocks! */ + /* This message is completed, so let's turn off the clocks & power */ clk_disable(pl022->clk); amba_pclk_disable(pl022->adev); + amba_vcore_disable(pl022->adev); } /** @@ -917,7 +925,6 @@ static int configure_dma(struct pl022 *pl022) struct dma_chan *txchan = pl022->dma_tx_channel; struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; - dma_cookie_t cookie; /* Check that the channels are available */ if (!rxchan || !txchan) @@ -962,10 +969,8 @@ static int configure_dma(struct pl022 *pl022) tx_conf.dst_addr_width = rx_conf.src_addr_width; BUG_ON(rx_conf.src_addr_width != tx_conf.dst_addr_width); - rxchan->device->device_control(rxchan, DMA_SLAVE_CONFIG, - (unsigned long) &rx_conf); - txchan->device->device_control(txchan, DMA_SLAVE_CONFIG, - (unsigned long) &tx_conf); + dmaengine_slave_config(rxchan, &rx_conf); + dmaengine_slave_config(txchan, &tx_conf); /* Create sglists for the transfers */ pages = (pl022->cur_transfer->len >> PAGE_SHIFT) + 1; @@ -1018,23 +1023,17 @@ static int configure_dma(struct pl022 *pl022) rxdesc->callback_param = pl022; /* Submit and fire RX and TX with TX last so we're ready to read! */ - cookie = rxdesc->tx_submit(rxdesc); - if (dma_submit_error(cookie)) - goto err_submit_rx; - cookie = txdesc->tx_submit(txdesc); - if (dma_submit_error(cookie)) - goto err_submit_tx; - rxchan->device->device_issue_pending(rxchan); - txchan->device->device_issue_pending(txchan); + dmaengine_submit(rxdesc); + dmaengine_submit(txdesc); + dma_async_issue_pending(rxchan); + dma_async_issue_pending(txchan); return 0; -err_submit_tx: -err_submit_rx: err_txdesc: - txchan->device->device_control(txchan, DMA_TERMINATE_ALL, 0); + dmaengine_terminate_all(txchan); err_rxdesc: - rxchan->device->device_control(rxchan, DMA_TERMINATE_ALL, 0); + dmaengine_terminate_all(rxchan); dma_unmap_sg(txchan->device->dev, pl022->sgt_tx.sgl, pl022->sgt_tx.nents, DMA_TO_DEVICE); err_tx_sgmap: @@ -1101,8 +1100,8 @@ static void terminate_dma(struct pl022 *pl022) struct dma_chan *rxchan = pl022->dma_rx_channel; struct dma_chan *txchan = pl022->dma_tx_channel; - rxchan->device->device_control(rxchan, DMA_TERMINATE_ALL, 0); - txchan->device->device_control(txchan, DMA_TERMINATE_ALL, 0); + dmaengine_terminate_all(rxchan); + dmaengine_terminate_all(txchan); unmap_free_dma_scatter(pl022); } @@ -1482,9 +1481,11 @@ static void pump_messages(struct work_struct *work) /* Setup the SPI using the per chip configuration */ pl022->cur_chip = spi_get_ctldata(pl022->cur_msg->spi); /* - * We enable the clocks here, then the clocks will be disabled when - * giveback() is called in each method (poll/interrupt/DMA) + * We enable the core voltage and clocks here, then the clocks + * and core will be disabled when giveback() is called in each method + * (poll/interrupt/DMA) */ + amba_vcore_enable(pl022->adev); amba_pclk_enable(pl022->adev); clk_enable(pl022->clk); restore_state(pl022); @@ -1910,8 +1911,6 @@ static int pl022_setup(struct spi_device *spi) && ((pl022->master_info)->enable_dma)) { chip->enable_dma = true; dev_dbg(&spi->dev, "DMA mode set in controller state\n"); - if (status < 0) - goto err_config_params; SSP_WRITE_BITS(chip->dmacr, SSP_DMA_ENABLED, SSP_DMACR_MASK_RXDMAE, 0); SSP_WRITE_BITS(chip->dmacr, SSP_DMA_ENABLED, @@ -2130,8 +2129,12 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) goto err_spi_register; } dev_dbg(dev, "probe succeded\n"); - /* Disable the silicon block pclk and clock it when needed */ + /* + * Disable the silicon block pclk and any voltage domain and just + * power it up and clock it when it's needed + */ amba_pclk_disable(adev); + amba_vcore_disable(adev); return 0; err_spi_register: @@ -2196,9 +2199,11 @@ static int pl022_suspend(struct amba_device *adev, pm_message_t state) return status; } + amba_vcore_enable(adev); amba_pclk_enable(adev); load_ssp_default_config(pl022); amba_pclk_disable(adev); + amba_vcore_disable(adev); dev_dbg(&adev->dev, "suspended\n"); return 0; } diff --git a/drivers/spi/davinci_spi.c b/drivers/spi/davinci_spi.c index 166a879fd9e8..1f0ed8005c91 100644 --- a/drivers/spi/davinci_spi.c +++ b/drivers/spi/davinci_spi.c @@ -571,6 +571,7 @@ static int davinci_spi_bufs(struct spi_device *spi, struct spi_transfer *t) unsigned long tx_reg, rx_reg; struct edmacc_param param; void *rx_buf; + int b, c; dma = &dspi->dma; @@ -591,22 +592,38 @@ static int davinci_spi_bufs(struct spi_device *spi, struct spi_transfer *t) if (t->tx_buf) { t->tx_dma = dma_map_single(&spi->dev, (void *)t->tx_buf, - dspi->wcount, DMA_TO_DEVICE); + t->len, DMA_TO_DEVICE); if (dma_mapping_error(&spi->dev, t->tx_dma)) { dev_dbg(sdev, "Unable to DMA map %d bytes" - "TX buffer\n", dspi->wcount); + "TX buffer\n", t->len); return -ENOMEM; } } + /* + * If number of words is greater than 65535, then we need + * to configure a 3 dimension transfer. Use the BCNTRLD + * feature to allow for transfers that aren't even multiples + * of 65535 (or any other possible b size) by first transferring + * the remainder amount then grabbing the next N blocks of + * 65535 words. + */ + + c = dspi->wcount / (SZ_64K - 1); /* N 65535 Blocks */ + b = dspi->wcount - c * (SZ_64K - 1); /* Remainder */ + if (b) + c++; + else + b = SZ_64K - 1; + param.opt = TCINTEN | EDMA_TCC(dma->tx_channel); param.src = t->tx_buf ? t->tx_dma : tx_reg; - param.a_b_cnt = dspi->wcount << 16 | data_type; + param.a_b_cnt = b << 16 | data_type; param.dst = tx_reg; param.src_dst_bidx = t->tx_buf ? data_type : 0; - param.link_bcntrld = 0xffff; - param.src_dst_cidx = 0; - param.ccnt = 1; + param.link_bcntrld = 0xffffffff; + param.src_dst_cidx = t->tx_buf ? data_type : 0; + param.ccnt = c; edma_write_slot(dma->tx_channel, ¶m); edma_link(dma->tx_channel, dma->dummy_param_slot); @@ -624,7 +641,7 @@ static int davinci_spi_bufs(struct spi_device *spi, struct spi_transfer *t) if (t->rx_buf) { rx_buf = t->rx_buf; - rx_buf_count = dspi->rcount; + rx_buf_count = t->len; } else { rx_buf = dspi->rx_tmp_buf; rx_buf_count = sizeof(dspi->rx_tmp_buf); @@ -636,19 +653,19 @@ static int davinci_spi_bufs(struct spi_device *spi, struct spi_transfer *t) dev_dbg(sdev, "Couldn't DMA map a %d bytes RX buffer\n", rx_buf_count); if (t->tx_buf) - dma_unmap_single(NULL, t->tx_dma, dspi->wcount, + dma_unmap_single(NULL, t->tx_dma, t->len, DMA_TO_DEVICE); return -ENOMEM; } param.opt = TCINTEN | EDMA_TCC(dma->rx_channel); param.src = rx_reg; - param.a_b_cnt = dspi->rcount << 16 | data_type; + param.a_b_cnt = b << 16 | data_type; param.dst = t->rx_dma; param.src_dst_bidx = (t->rx_buf ? data_type : 0) << 16; - param.link_bcntrld = 0xffff; - param.src_dst_cidx = 0; - param.ccnt = 1; + param.link_bcntrld = 0xffffffff; + param.src_dst_cidx = (t->rx_buf ? data_type : 0) << 16; + param.ccnt = c; edma_write_slot(dma->rx_channel, ¶m); if (pdata->cshold_bug) @@ -675,7 +692,7 @@ static int davinci_spi_bufs(struct spi_device *spi, struct spi_transfer *t) if (spicfg->io_type == SPI_IO_TYPE_DMA) { if (t->tx_buf) - dma_unmap_single(NULL, t->tx_dma, dspi->wcount, + dma_unmap_single(NULL, t->tx_dma, t->len, DMA_TO_DEVICE); dma_unmap_single(NULL, t->rx_dma, rx_buf_count, diff --git a/drivers/spi/dw_spi.c b/drivers/spi/dw_spi.c index 22af77f98816..9a6196461b27 100644 --- a/drivers/spi/dw_spi.c +++ b/drivers/spi/dw_spi.c @@ -22,10 +22,10 @@ #include <linux/highmem.h> #include <linux/delay.h> #include <linux/slab.h> - -#include <linux/spi/dw_spi.h> #include <linux/spi/spi.h> +#include "dw_spi.h" + #ifdef CONFIG_DEBUG_FS #include <linux/debugfs.h> #endif diff --git a/include/linux/spi/dw_spi.h b/drivers/spi/dw_spi.h index 6cd10f6ad472..fb0bce564844 100644 --- a/include/linux/spi/dw_spi.h +++ b/drivers/spi/dw_spi.h @@ -2,6 +2,7 @@ #define DW_SPI_HEADER_H #include <linux/io.h> +#include <linux/scatterlist.h> /* Bit fields in CTRLR0 */ #define SPI_DFS_OFFSET 0 diff --git a/drivers/spi/dw_spi_mid.c b/drivers/spi/dw_spi_mid.c index c91c966e0717..489178243d88 100644 --- a/drivers/spi/dw_spi_mid.c +++ b/drivers/spi/dw_spi_mid.c @@ -22,7 +22,8 @@ #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/spi/spi.h> -#include <linux/spi/dw_spi.h> + +#include "dw_spi.h" #ifdef CONFIG_SPI_DW_MID_DMA #include <linux/intel_mid_dma.h> diff --git a/drivers/spi/dw_spi_mmio.c b/drivers/spi/dw_spi_mmio.c index 2fa012c109bc..e0e813dad150 100644 --- a/drivers/spi/dw_spi_mmio.c +++ b/drivers/spi/dw_spi_mmio.c @@ -13,8 +13,10 @@ #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/slab.h> -#include <linux/spi/dw_spi.h> #include <linux/spi/spi.h> +#include <linux/scatterlist.h> + +#include "dw_spi.h" #define DRIVER_NAME "dw_spi_mmio" diff --git a/drivers/spi/dw_spi_pci.c b/drivers/spi/dw_spi_pci.c index 49ec3aa1219f..ad260aa5e526 100644 --- a/drivers/spi/dw_spi_pci.c +++ b/drivers/spi/dw_spi_pci.c @@ -20,9 +20,10 @@ #include <linux/interrupt.h> #include <linux/pci.h> #include <linux/slab.h> -#include <linux/spi/dw_spi.h> #include <linux/spi/spi.h> +#include "dw_spi.h" + #define DRIVER_NAME "dw_spi_pci" struct dw_spi_pci { diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c index 36501adc125d..3a5ed06d3d2f 100644 --- a/drivers/spi/omap2_mcspi.c +++ b/drivers/spi/omap2_mcspi.c @@ -467,6 +467,9 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) rx_reg = base + OMAP2_MCSPI_RX0; chstat_reg = base + OMAP2_MCSPI_CHSTAT0; + if (c < (word_len>>3)) + return 0; + if (word_len <= 8) { u8 *rx; const u8 *tx; @@ -514,7 +517,7 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) dev_vdbg(&spi->dev, "read-%d %02x\n", word_len, *(rx - 1)); } - } while (c); + } while (c > (word_len>>3)); } else if (word_len <= 16) { u16 *rx; const u16 *tx; @@ -561,7 +564,7 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) dev_vdbg(&spi->dev, "read-%d %04x\n", word_len, *(rx - 1)); } - } while (c); + } while (c > (word_len>>3)); } else if (word_len <= 32) { u32 *rx; const u32 *tx; @@ -608,7 +611,7 @@ omap2_mcspi_txrx_pio(struct spi_device *spi, struct spi_transfer *xfer) dev_vdbg(&spi->dev, "read-%d %08x\n", word_len, *(rx - 1)); } - } while (c); + } while (c > (word_len>>3)); } /* for TX_ONLY mode, be sure all words have shifted out */ @@ -631,6 +634,17 @@ out: return count - c; } +static u32 omap2_mcspi_calc_divisor(u32 speed_hz) +{ + u32 div; + + for (div = 0; div < 15; div++) + if (speed_hz >= (OMAP2_MCSPI_MAX_FREQ >> div)) + return div; + + return 15; +} + /* called only when no transfer is active to this device */ static int omap2_mcspi_setup_transfer(struct spi_device *spi, struct spi_transfer *t) @@ -653,12 +667,8 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi, if (t && t->speed_hz) speed_hz = t->speed_hz; - if (speed_hz) { - while (div <= 15 && (OMAP2_MCSPI_MAX_FREQ / (1 << div)) - > speed_hz) - div++; - } else - div = 15; + speed_hz = min_t(u32, speed_hz, OMAP2_MCSPI_MAX_FREQ); + div = omap2_mcspi_calc_divisor(speed_hz); l = mcspi_cached_chconf0(spi); @@ -695,7 +705,7 @@ static int omap2_mcspi_setup_transfer(struct spi_device *spi, mcspi_write_chconf0(spi, l); dev_dbg(&spi->dev, "setup: speed %d, sample %s edge, clk %s\n", - OMAP2_MCSPI_MAX_FREQ / (1 << div), + OMAP2_MCSPI_MAX_FREQ >> div, (spi->mode & SPI_CPHA) ? "trailing" : "leading", (spi->mode & SPI_CPOL) ? "inverted" : "normal"); @@ -996,10 +1006,10 @@ static int omap2_mcspi_transfer(struct spi_device *spi, struct spi_message *m) t->bits_per_word); return -EINVAL; } - if (t->speed_hz && t->speed_hz < OMAP2_MCSPI_MAX_FREQ/(1<<16)) { - dev_dbg(&spi->dev, "%d Hz max exceeds %d\n", - t->speed_hz, - OMAP2_MCSPI_MAX_FREQ/(1<<16)); + if (t->speed_hz && t->speed_hz < (OMAP2_MCSPI_MAX_FREQ >> 15)) { + dev_dbg(&spi->dev, "speed_hz %d below minimum %d Hz\n", + t->speed_hz, + OMAP2_MCSPI_MAX_FREQ >> 15); return -EINVAL; } diff --git a/drivers/spi/spi_altera.c b/drivers/spi/spi_altera.c new file mode 100644 index 000000000000..4813a63ce6fb --- /dev/null +++ b/drivers/spi/spi_altera.c @@ -0,0 +1,339 @@ +/* + * Altera SPI driver + * + * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw> + * + * Based on spi_s3c24xx.c, which is: + * Copyright (c) 2006 Ben Dooks + * Copyright (c) 2006 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * 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/errno.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> +#include <linux/io.h> +#include <linux/of.h> + +#define DRV_NAME "spi_altera" + +#define ALTERA_SPI_RXDATA 0 +#define ALTERA_SPI_TXDATA 4 +#define ALTERA_SPI_STATUS 8 +#define ALTERA_SPI_CONTROL 12 +#define ALTERA_SPI_SLAVE_SEL 20 + +#define ALTERA_SPI_STATUS_ROE_MSK 0x8 +#define ALTERA_SPI_STATUS_TOE_MSK 0x10 +#define ALTERA_SPI_STATUS_TMT_MSK 0x20 +#define ALTERA_SPI_STATUS_TRDY_MSK 0x40 +#define ALTERA_SPI_STATUS_RRDY_MSK 0x80 +#define ALTERA_SPI_STATUS_E_MSK 0x100 + +#define ALTERA_SPI_CONTROL_IROE_MSK 0x8 +#define ALTERA_SPI_CONTROL_ITOE_MSK 0x10 +#define ALTERA_SPI_CONTROL_ITRDY_MSK 0x40 +#define ALTERA_SPI_CONTROL_IRRDY_MSK 0x80 +#define ALTERA_SPI_CONTROL_IE_MSK 0x100 +#define ALTERA_SPI_CONTROL_SSO_MSK 0x400 + +struct altera_spi { + /* bitbang has to be first */ + struct spi_bitbang bitbang; + struct completion done; + + void __iomem *base; + int irq; + int len; + int count; + int bytes_per_word; + unsigned long imr; + + /* data buffers */ + const unsigned char *tx; + unsigned char *rx; +}; + +static inline struct altera_spi *altera_spi_to_hw(struct spi_device *sdev) +{ + return spi_master_get_devdata(sdev->master); +} + +static void altera_spi_chipsel(struct spi_device *spi, int value) +{ + struct altera_spi *hw = altera_spi_to_hw(spi); + + if (spi->mode & SPI_CS_HIGH) { + switch (value) { + case BITBANG_CS_INACTIVE: + writel(1 << spi->chip_select, + hw->base + ALTERA_SPI_SLAVE_SEL); + hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK; + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + break; + + case BITBANG_CS_ACTIVE: + hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK; + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + writel(0, hw->base + ALTERA_SPI_SLAVE_SEL); + break; + } + } else { + switch (value) { + case BITBANG_CS_INACTIVE: + hw->imr &= ~ALTERA_SPI_CONTROL_SSO_MSK; + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + break; + + case BITBANG_CS_ACTIVE: + writel(1 << spi->chip_select, + hw->base + ALTERA_SPI_SLAVE_SEL); + hw->imr |= ALTERA_SPI_CONTROL_SSO_MSK; + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + break; + } + } +} + +static int altera_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t) +{ + return 0; +} + +static int altera_spi_setup(struct spi_device *spi) +{ + return 0; +} + +static inline unsigned int hw_txbyte(struct altera_spi *hw, int count) +{ + if (hw->tx) { + switch (hw->bytes_per_word) { + case 1: + return hw->tx[count]; + case 2: + return (hw->tx[count * 2] + | (hw->tx[count * 2 + 1] << 8)); + } + } + return 0; +} + +static int altera_spi_txrx(struct spi_device *spi, struct spi_transfer *t) +{ + struct altera_spi *hw = altera_spi_to_hw(spi); + + hw->tx = t->tx_buf; + hw->rx = t->rx_buf; + hw->count = 0; + hw->bytes_per_word = (t->bits_per_word ? : spi->bits_per_word) / 8; + hw->len = t->len / hw->bytes_per_word; + + if (hw->irq >= 0) { + /* enable receive interrupt */ + hw->imr |= ALTERA_SPI_CONTROL_IRRDY_MSK; + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + + /* send the first byte */ + writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA); + + wait_for_completion(&hw->done); + /* disable receive interrupt */ + hw->imr &= ~ALTERA_SPI_CONTROL_IRRDY_MSK; + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + } else { + /* send the first byte */ + writel(hw_txbyte(hw, 0), hw->base + ALTERA_SPI_TXDATA); + + while (1) { + unsigned int rxd; + + while (!(readl(hw->base + ALTERA_SPI_STATUS) & + ALTERA_SPI_STATUS_RRDY_MSK)) + cpu_relax(); + + rxd = readl(hw->base + ALTERA_SPI_RXDATA); + if (hw->rx) { + switch (hw->bytes_per_word) { + case 1: + hw->rx[hw->count] = rxd; + break; + case 2: + hw->rx[hw->count * 2] = rxd; + hw->rx[hw->count * 2 + 1] = rxd >> 8; + break; + } + } + + hw->count++; + + if (hw->count < hw->len) + writel(hw_txbyte(hw, hw->count), + hw->base + ALTERA_SPI_TXDATA); + else + break; + } + + } + + return hw->count * hw->bytes_per_word; +} + +static irqreturn_t altera_spi_irq(int irq, void *dev) +{ + struct altera_spi *hw = dev; + unsigned int rxd; + + rxd = readl(hw->base + ALTERA_SPI_RXDATA); + if (hw->rx) { + switch (hw->bytes_per_word) { + case 1: + hw->rx[hw->count] = rxd; + break; + case 2: + hw->rx[hw->count * 2] = rxd; + hw->rx[hw->count * 2 + 1] = rxd >> 8; + break; + } + } + + hw->count++; + + if (hw->count < hw->len) + writel(hw_txbyte(hw, hw->count), hw->base + ALTERA_SPI_TXDATA); + else + complete(&hw->done); + + return IRQ_HANDLED; +} + +static int __devinit altera_spi_probe(struct platform_device *pdev) +{ + struct altera_spi_platform_data *platp = pdev->dev.platform_data; + struct altera_spi *hw; + struct spi_master *master; + struct resource *res; + int err = -ENODEV; + + master = spi_alloc_master(&pdev->dev, sizeof(struct altera_spi)); + if (!master) + return err; + + /* setup the master state. */ + master->bus_num = pdev->id; + master->num_chipselect = 16; + master->mode_bits = SPI_CS_HIGH; + master->setup = altera_spi_setup; + + hw = spi_master_get_devdata(master); + platform_set_drvdata(pdev, hw); + + /* setup the state for the bitbang driver */ + hw->bitbang.master = spi_master_get(master); + if (!hw->bitbang.master) + return err; + hw->bitbang.setup_transfer = altera_spi_setupxfer; + hw->bitbang.chipselect = altera_spi_chipsel; + hw->bitbang.txrx_bufs = altera_spi_txrx; + + /* find and map our resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto exit_busy; + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + pdev->name)) + goto exit_busy; + hw->base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!hw->base) + goto exit_busy; + /* program defaults into the registers */ + hw->imr = 0; /* disable spi interrupts */ + writel(hw->imr, hw->base + ALTERA_SPI_CONTROL); + writel(0, hw->base + ALTERA_SPI_STATUS); /* clear status reg */ + if (readl(hw->base + ALTERA_SPI_STATUS) & ALTERA_SPI_STATUS_RRDY_MSK) + readl(hw->base + ALTERA_SPI_RXDATA); /* flush rxdata */ + /* irq is optional */ + hw->irq = platform_get_irq(pdev, 0); + if (hw->irq >= 0) { + init_completion(&hw->done); + err = devm_request_irq(&pdev->dev, hw->irq, altera_spi_irq, 0, + pdev->name, hw); + if (err) + goto exit; + } + /* find platform data */ + if (!platp) + hw->bitbang.master->dev.of_node = pdev->dev.of_node; + + /* register our spi controller */ + err = spi_bitbang_start(&hw->bitbang); + if (err) + goto exit; + dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq); + + return 0; + +exit_busy: + err = -EBUSY; +exit: + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + return err; +} + +static int __devexit altera_spi_remove(struct platform_device *dev) +{ + struct altera_spi *hw = platform_get_drvdata(dev); + struct spi_master *master = hw->bitbang.master; + + spi_bitbang_stop(&hw->bitbang); + platform_set_drvdata(dev, NULL); + spi_master_put(master); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id altera_spi_match[] = { + { .compatible = "ALTR,spi-1.0", }, + {}, +}; +MODULE_DEVICE_TABLE(of, altera_spi_match); +#else /* CONFIG_OF */ +#define altera_spi_match NULL +#endif /* CONFIG_OF */ + +static struct platform_driver altera_spi_driver = { + .probe = altera_spi_probe, + .remove = __devexit_p(altera_spi_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = altera_spi_match, + }, +}; + +static int __init altera_spi_init(void) +{ + return platform_driver_register(&altera_spi_driver); +} +module_init(altera_spi_init); + +static void __exit altera_spi_exit(void) +{ + platform_driver_unregister(&altera_spi_driver); +} +module_exit(altera_spi_exit); + +MODULE_DESCRIPTION("Altera SPI driver"); +MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/spi/spi_bfin5xx.c b/drivers/spi/spi_bfin5xx.c index 3f223511127b..a28462486df8 100644 --- a/drivers/spi/spi_bfin5xx.c +++ b/drivers/spi/spi_bfin5xx.c @@ -425,6 +425,7 @@ static irqreturn_t bfin_spi_pio_irq_handler(int irq, void *dev_id) struct bfin_spi_slave_data *chip = drv_data->cur_chip; struct spi_message *msg = drv_data->cur_msg; int n_bytes = drv_data->n_bytes; + int loop = 0; /* wait until transfer finished. */ while (!(read_STAT(drv_data) & BIT_STAT_RXS)) @@ -435,10 +436,15 @@ static irqreturn_t bfin_spi_pio_irq_handler(int irq, void *dev_id) /* last read */ if (drv_data->rx) { dev_dbg(&drv_data->pdev->dev, "last read\n"); - if (n_bytes == 2) - *(u16 *) (drv_data->rx) = read_RDBR(drv_data); - else if (n_bytes == 1) - *(u8 *) (drv_data->rx) = read_RDBR(drv_data); + if (n_bytes % 2) { + u16 *buf = (u16 *)drv_data->rx; + for (loop = 0; loop < n_bytes / 2; loop++) + *buf++ = read_RDBR(drv_data); + } else { + u8 *buf = (u8 *)drv_data->rx; + for (loop = 0; loop < n_bytes; loop++) + *buf++ = read_RDBR(drv_data); + } drv_data->rx += n_bytes; } @@ -458,29 +464,53 @@ static irqreturn_t bfin_spi_pio_irq_handler(int irq, void *dev_id) if (drv_data->rx && drv_data->tx) { /* duplex */ dev_dbg(&drv_data->pdev->dev, "duplex: write_TDBR\n"); - if (drv_data->n_bytes == 2) { - *(u16 *) (drv_data->rx) = read_RDBR(drv_data); - write_TDBR(drv_data, (*(u16 *) (drv_data->tx))); - } else if (drv_data->n_bytes == 1) { - *(u8 *) (drv_data->rx) = read_RDBR(drv_data); - write_TDBR(drv_data, (*(u8 *) (drv_data->tx))); + if (n_bytes % 2) { + u16 *buf = (u16 *)drv_data->rx; + u16 *buf2 = (u16 *)drv_data->tx; + for (loop = 0; loop < n_bytes / 2; loop++) { + *buf++ = read_RDBR(drv_data); + write_TDBR(drv_data, *buf2++); + } + } else { + u8 *buf = (u8 *)drv_data->rx; + u8 *buf2 = (u8 *)drv_data->tx; + for (loop = 0; loop < n_bytes; loop++) { + *buf++ = read_RDBR(drv_data); + write_TDBR(drv_data, *buf2++); + } } } else if (drv_data->rx) { /* read */ dev_dbg(&drv_data->pdev->dev, "read: write_TDBR\n"); - if (drv_data->n_bytes == 2) - *(u16 *) (drv_data->rx) = read_RDBR(drv_data); - else if (drv_data->n_bytes == 1) - *(u8 *) (drv_data->rx) = read_RDBR(drv_data); - write_TDBR(drv_data, chip->idle_tx_val); + if (n_bytes % 2) { + u16 *buf = (u16 *)drv_data->rx; + for (loop = 0; loop < n_bytes / 2; loop++) { + *buf++ = read_RDBR(drv_data); + write_TDBR(drv_data, chip->idle_tx_val); + } + } else { + u8 *buf = (u8 *)drv_data->rx; + for (loop = 0; loop < n_bytes; loop++) { + *buf++ = read_RDBR(drv_data); + write_TDBR(drv_data, chip->idle_tx_val); + } + } } else if (drv_data->tx) { /* write */ dev_dbg(&drv_data->pdev->dev, "write: write_TDBR\n"); - bfin_spi_dummy_read(drv_data); - if (drv_data->n_bytes == 2) - write_TDBR(drv_data, (*(u16 *) (drv_data->tx))); - else if (drv_data->n_bytes == 1) - write_TDBR(drv_data, (*(u8 *) (drv_data->tx))); + if (n_bytes % 2) { + u16 *buf = (u16 *)drv_data->tx; + for (loop = 0; loop < n_bytes / 2; loop++) { + read_RDBR(drv_data); + write_TDBR(drv_data, *buf++); + } + } else { + u8 *buf = (u8 *)drv_data->tx; + for (loop = 0; loop < n_bytes; loop++) { + read_RDBR(drv_data); + write_TDBR(drv_data, *buf++); + } + } } if (drv_data->tx) @@ -623,6 +653,7 @@ static void bfin_spi_pump_transfers(unsigned long data) message->state = bfin_spi_next_transfer(drv_data); /* Schedule next transfer tasklet */ tasklet_schedule(&drv_data->pump_transfers); + return; } if (transfer->tx_buf != NULL) { @@ -651,16 +682,16 @@ static void bfin_spi_pump_transfers(unsigned long data) /* Bits per word setup */ bits_per_word = transfer->bits_per_word ? : message->spi->bits_per_word; - if (bits_per_word == 8) { - drv_data->n_bytes = 1; - drv_data->len = transfer->len; - cr_width = 0; - drv_data->ops = &bfin_bfin_spi_transfer_ops_u8; - } else if (bits_per_word == 16) { - drv_data->n_bytes = 2; + if ((bits_per_word > 0) && (bits_per_word % 16 == 0)) { + drv_data->n_bytes = bits_per_word/8; drv_data->len = (transfer->len) >> 1; cr_width = BIT_CTL_WORDSIZE; drv_data->ops = &bfin_bfin_spi_transfer_ops_u16; + } else if ((bits_per_word > 0) && (bits_per_word % 8 == 0)) { + drv_data->n_bytes = bits_per_word/8; + drv_data->len = transfer->len; + cr_width = 0; + drv_data->ops = &bfin_bfin_spi_transfer_ops_u8; } else { dev_err(&drv_data->pdev->dev, "transfer: unsupported bits_per_word\n"); message->status = -EINVAL; @@ -815,10 +846,19 @@ static void bfin_spi_pump_transfers(unsigned long data) if (drv_data->tx == NULL) write_TDBR(drv_data, chip->idle_tx_val); else { - if (bits_per_word == 8) - write_TDBR(drv_data, (*(u8 *) (drv_data->tx))); - else - write_TDBR(drv_data, (*(u16 *) (drv_data->tx))); + int loop; + if (bits_per_word % 16 == 0) { + u16 *buf = (u16 *)drv_data->tx; + for (loop = 0; loop < bits_per_word / 16; + loop++) { + write_TDBR(drv_data, *buf++); + } + } else if (bits_per_word % 8 == 0) { + u8 *buf = (u8 *)drv_data->tx; + for (loop = 0; loop < bits_per_word / 8; loop++) + write_TDBR(drv_data, *buf++); + } + drv_data->tx += drv_data->n_bytes; } @@ -1031,7 +1071,7 @@ static int bfin_spi_setup(struct spi_device *spi) chip->ctl_reg &= bfin_ctl_reg; } - if (spi->bits_per_word != 8 && spi->bits_per_word != 16) { + if (spi->bits_per_word % 8) { dev_err(&spi->dev, "%d bits_per_word is not supported\n", spi->bits_per_word); goto error; diff --git a/drivers/spi/spi_bitbang.c b/drivers/spi/spi_bitbang.c index 8b55724d5f39..14a63f6010d1 100644 --- a/drivers/spi/spi_bitbang.c +++ b/drivers/spi/spi_bitbang.c @@ -259,10 +259,6 @@ static void bitbang_work(struct work_struct *work) struct spi_bitbang *bitbang = container_of(work, struct spi_bitbang, work); unsigned long flags; - int (*setup_transfer)(struct spi_device *, - struct spi_transfer *); - - setup_transfer = bitbang->setup_transfer; spin_lock_irqsave(&bitbang->lock, flags); bitbang->busy = 1; @@ -300,11 +296,7 @@ static void bitbang_work(struct work_struct *work) /* init (-1) or override (1) transfer params */ if (do_setup != 0) { - if (!setup_transfer) { - status = -ENOPROTOOPT; - break; - } - status = setup_transfer(spi, t); + status = bitbang->setup_transfer(spi, t); if (status < 0) break; if (do_setup == -1) @@ -465,6 +457,9 @@ int spi_bitbang_start(struct spi_bitbang *bitbang) } } else if (!bitbang->master->setup) return -EINVAL; + if (bitbang->master->transfer == spi_bitbang_transfer && + !bitbang->setup_transfer) + return -EINVAL; /* this task is the only thing to touch the SPI bits */ bitbang->busy = 0; diff --git a/drivers/spi/spi_imx.c b/drivers/spi/spi_imx.c index 1cf9d5faabf4..69d6dba67c19 100644 --- a/drivers/spi/spi_imx.c +++ b/drivers/spi/spi_imx.c @@ -174,7 +174,7 @@ static unsigned int spi_imx_clkdiv_2(unsigned int fin, #define SPI_IMX2_3_CTRL 0x08 #define SPI_IMX2_3_CTRL_ENABLE (1 << 0) #define SPI_IMX2_3_CTRL_XCH (1 << 2) -#define SPI_IMX2_3_CTRL_MODE(cs) (1 << ((cs) + 4)) +#define SPI_IMX2_3_CTRL_MODE_MASK (0xf << 4) #define SPI_IMX2_3_CTRL_POSTDIV_OFFSET 8 #define SPI_IMX2_3_CTRL_PREDIV_OFFSET 12 #define SPI_IMX2_3_CTRL_CS(cs) ((cs) << 18) @@ -253,8 +253,14 @@ static int __maybe_unused spi_imx2_3_config(struct spi_imx_data *spi_imx, { u32 ctrl = SPI_IMX2_3_CTRL_ENABLE, cfg = 0; - /* set master mode */ - ctrl |= SPI_IMX2_3_CTRL_MODE(config->cs); + /* + * The hardware seems to have a race condition when changing modes. The + * current assumption is that the selection of the channel arrives + * earlier in the hardware than the mode bits when they are written at + * the same time. + * So set master mode for all channels as we do not support slave mode. + */ + ctrl |= SPI_IMX2_3_CTRL_MODE_MASK; /* set clock speed */ ctrl |= spi_imx2_3_clkdiv(spi_imx->spi_clk, config->speed_hz); diff --git a/drivers/spi/spi_oc_tiny.c b/drivers/spi/spi_oc_tiny.c new file mode 100644 index 000000000000..f1bde66cea19 --- /dev/null +++ b/drivers/spi/spi_oc_tiny.c @@ -0,0 +1,425 @@ +/* + * OpenCores tiny SPI master driver + * + * http://opencores.org/project,tiny_spi + * + * Copyright (C) 2011 Thomas Chou <thomas@wytron.com.tw> + * + * Based on spi_s3c24xx.c, which is: + * Copyright (c) 2006 Ben Dooks + * Copyright (c) 2006 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * 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/errno.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi_bitbang.h> +#include <linux/spi/spi_oc_tiny.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/of.h> + +#define DRV_NAME "spi_oc_tiny" + +#define TINY_SPI_RXDATA 0 +#define TINY_SPI_TXDATA 4 +#define TINY_SPI_STATUS 8 +#define TINY_SPI_CONTROL 12 +#define TINY_SPI_BAUD 16 + +#define TINY_SPI_STATUS_TXE 0x1 +#define TINY_SPI_STATUS_TXR 0x2 + +struct tiny_spi { + /* bitbang has to be first */ + struct spi_bitbang bitbang; + struct completion done; + + void __iomem *base; + int irq; + unsigned int freq; + unsigned int baudwidth; + unsigned int baud; + unsigned int speed_hz; + unsigned int mode; + unsigned int len; + unsigned int txc, rxc; + const u8 *txp; + u8 *rxp; + unsigned int gpio_cs_count; + int *gpio_cs; +}; + +static inline struct tiny_spi *tiny_spi_to_hw(struct spi_device *sdev) +{ + return spi_master_get_devdata(sdev->master); +} + +static unsigned int tiny_spi_baud(struct spi_device *spi, unsigned int hz) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + return min(DIV_ROUND_UP(hw->freq, hz * 2), (1U << hw->baudwidth)) - 1; +} + +static void tiny_spi_chipselect(struct spi_device *spi, int is_active) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + if (hw->gpio_cs_count) { + gpio_set_value(hw->gpio_cs[spi->chip_select], + (spi->mode & SPI_CS_HIGH) ? is_active : !is_active); + } +} + +static int tiny_spi_setup_transfer(struct spi_device *spi, + struct spi_transfer *t) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + unsigned int baud = hw->baud; + + if (t) { + if (t->speed_hz && t->speed_hz != hw->speed_hz) + baud = tiny_spi_baud(spi, t->speed_hz); + } + writel(baud, hw->base + TINY_SPI_BAUD); + writel(hw->mode, hw->base + TINY_SPI_CONTROL); + return 0; +} + +static int tiny_spi_setup(struct spi_device *spi) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + + if (spi->max_speed_hz != hw->speed_hz) { + hw->speed_hz = spi->max_speed_hz; + hw->baud = tiny_spi_baud(spi, hw->speed_hz); + } + hw->mode = spi->mode & (SPI_CPOL | SPI_CPHA); + return 0; +} + +static inline void tiny_spi_wait_txr(struct tiny_spi *hw) +{ + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXR)) + cpu_relax(); +} + +static inline void tiny_spi_wait_txe(struct tiny_spi *hw) +{ + while (!(readb(hw->base + TINY_SPI_STATUS) & + TINY_SPI_STATUS_TXE)) + cpu_relax(); +} + +static int tiny_spi_txrx_bufs(struct spi_device *spi, struct spi_transfer *t) +{ + struct tiny_spi *hw = tiny_spi_to_hw(spi); + const u8 *txp = t->tx_buf; + u8 *rxp = t->rx_buf; + unsigned int i; + + if (hw->irq >= 0) { + /* use intrrupt driven data transfer */ + hw->len = t->len; + hw->txp = t->tx_buf; + hw->rxp = t->rx_buf; + hw->txc = 0; + hw->rxc = 0; + + /* send the first byte */ + if (t->len > 1) { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXR, hw->base + TINY_SPI_STATUS); + } else { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXE, hw->base + TINY_SPI_STATUS); + } + + wait_for_completion(&hw->done); + } else if (txp && rxp) { + /* we need to tighten the transfer loop */ + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 rx, tx = *txp++; + tiny_spi_wait_txr(hw); + rx = readb(hw->base + TINY_SPI_TXDATA); + writeb(tx, hw->base + TINY_SPI_TXDATA); + *rxp++ = rx; + } + tiny_spi_wait_txr(hw); + *rxp++ = readb(hw->base + TINY_SPI_TXDATA); + } + tiny_spi_wait_txe(hw); + *rxp++ = readb(hw->base + TINY_SPI_RXDATA); + } else if (rxp) { + writeb(0, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(0, + hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 rx; + tiny_spi_wait_txr(hw); + rx = readb(hw->base + TINY_SPI_TXDATA); + writeb(0, hw->base + TINY_SPI_TXDATA); + *rxp++ = rx; + } + tiny_spi_wait_txr(hw); + *rxp++ = readb(hw->base + TINY_SPI_TXDATA); + } + tiny_spi_wait_txe(hw); + *rxp++ = readb(hw->base + TINY_SPI_RXDATA); + } else if (txp) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(*txp++, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + u8 tx = *txp++; + tiny_spi_wait_txr(hw); + writeb(tx, hw->base + TINY_SPI_TXDATA); + } + } + tiny_spi_wait_txe(hw); + } else { + writeb(0, hw->base + TINY_SPI_TXDATA); + if (t->len > 1) { + writeb(0, hw->base + TINY_SPI_TXDATA); + for (i = 2; i < t->len; i++) { + tiny_spi_wait_txr(hw); + writeb(0, hw->base + TINY_SPI_TXDATA); + } + } + tiny_spi_wait_txe(hw); + } + return t->len; +} + +static irqreturn_t tiny_spi_irq(int irq, void *dev) +{ + struct tiny_spi *hw = dev; + + writeb(0, hw->base + TINY_SPI_STATUS); + if (hw->rxc + 1 == hw->len) { + if (hw->rxp) + *hw->rxp++ = readb(hw->base + TINY_SPI_RXDATA); + hw->rxc++; + complete(&hw->done); + } else { + if (hw->rxp) + *hw->rxp++ = readb(hw->base + TINY_SPI_TXDATA); + hw->rxc++; + if (hw->txc < hw->len) { + writeb(hw->txp ? *hw->txp++ : 0, + hw->base + TINY_SPI_TXDATA); + hw->txc++; + writeb(TINY_SPI_STATUS_TXR, + hw->base + TINY_SPI_STATUS); + } else { + writeb(TINY_SPI_STATUS_TXE, + hw->base + TINY_SPI_STATUS); + } + } + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +#include <linux/of_gpio.h> + +static int __devinit tiny_spi_of_probe(struct platform_device *pdev) +{ + struct tiny_spi *hw = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + unsigned int i; + const __be32 *val; + int len; + + if (!np) + return 0; + hw->gpio_cs_count = of_gpio_count(np); + if (hw->gpio_cs_count) { + hw->gpio_cs = devm_kzalloc(&pdev->dev, + hw->gpio_cs_count * sizeof(unsigned int), + GFP_KERNEL); + if (!hw->gpio_cs) + return -ENOMEM; + } + for (i = 0; i < hw->gpio_cs_count; i++) { + hw->gpio_cs[i] = of_get_gpio_flags(np, i, NULL); + if (hw->gpio_cs[i] < 0) + return -ENODEV; + } + hw->bitbang.master->dev.of_node = pdev->dev.of_node; + val = of_get_property(pdev->dev.of_node, + "clock-frequency", &len); + if (val && len >= sizeof(__be32)) + hw->freq = be32_to_cpup(val); + val = of_get_property(pdev->dev.of_node, "baud-width", &len); + if (val && len >= sizeof(__be32)) + hw->baudwidth = be32_to_cpup(val); + return 0; +} +#else /* !CONFIG_OF */ +static int __devinit tiny_spi_of_probe(struct platform_device *pdev) +{ + return 0; +} +#endif /* CONFIG_OF */ + +static int __devinit tiny_spi_probe(struct platform_device *pdev) +{ + struct tiny_spi_platform_data *platp = pdev->dev.platform_data; + struct tiny_spi *hw; + struct spi_master *master; + struct resource *res; + unsigned int i; + int err = -ENODEV; + + master = spi_alloc_master(&pdev->dev, sizeof(struct tiny_spi)); + if (!master) + return err; + + /* setup the master state. */ + master->bus_num = pdev->id; + master->num_chipselect = 255; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->setup = tiny_spi_setup; + + hw = spi_master_get_devdata(master); + platform_set_drvdata(pdev, hw); + + /* setup the state for the bitbang driver */ + hw->bitbang.master = spi_master_get(master); + if (!hw->bitbang.master) + return err; + hw->bitbang.setup_transfer = tiny_spi_setup_transfer; + hw->bitbang.chipselect = tiny_spi_chipselect; + hw->bitbang.txrx_bufs = tiny_spi_txrx_bufs; + + /* find and map our resources */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto exit_busy; + if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), + pdev->name)) + goto exit_busy; + hw->base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!hw->base) + goto exit_busy; + /* irq is optional */ + hw->irq = platform_get_irq(pdev, 0); + if (hw->irq >= 0) { + init_completion(&hw->done); + err = devm_request_irq(&pdev->dev, hw->irq, tiny_spi_irq, 0, + pdev->name, hw); + if (err) + goto exit; + } + /* find platform data */ + if (platp) { + hw->gpio_cs_count = platp->gpio_cs_count; + hw->gpio_cs = platp->gpio_cs; + if (platp->gpio_cs_count && !platp->gpio_cs) + goto exit_busy; + hw->freq = platp->freq; + hw->baudwidth = platp->baudwidth; + } else { + err = tiny_spi_of_probe(pdev); + if (err) + goto exit; + } + for (i = 0; i < hw->gpio_cs_count; i++) { + err = gpio_request(hw->gpio_cs[i], dev_name(&pdev->dev)); + if (err) + goto exit_gpio; + gpio_direction_output(hw->gpio_cs[i], 1); + } + hw->bitbang.master->num_chipselect = max(1U, hw->gpio_cs_count); + + /* register our spi controller */ + err = spi_bitbang_start(&hw->bitbang); + if (err) + goto exit; + dev_info(&pdev->dev, "base %p, irq %d\n", hw->base, hw->irq); + + return 0; + +exit_gpio: + while (i-- > 0) + gpio_free(hw->gpio_cs[i]); +exit_busy: + err = -EBUSY; +exit: + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + return err; +} + +static int __devexit tiny_spi_remove(struct platform_device *pdev) +{ + struct tiny_spi *hw = platform_get_drvdata(pdev); + struct spi_master *master = hw->bitbang.master; + unsigned int i; + + spi_bitbang_stop(&hw->bitbang); + for (i = 0; i < hw->gpio_cs_count; i++) + gpio_free(hw->gpio_cs[i]); + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id tiny_spi_match[] = { + { .compatible = "opencores,tiny-spi-rtlsvn2", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tiny_spi_match); +#else /* CONFIG_OF */ +#define tiny_spi_match NULL +#endif /* CONFIG_OF */ + +static struct platform_driver tiny_spi_driver = { + .probe = tiny_spi_probe, + .remove = __devexit_p(tiny_spi_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .pm = NULL, + .of_match_table = tiny_spi_match, + }, +}; + +static int __init tiny_spi_init(void) +{ + return platform_driver_register(&tiny_spi_driver); +} +module_init(tiny_spi_init); + +static void __exit tiny_spi_exit(void) +{ + platform_driver_unregister(&tiny_spi_driver); +} +module_exit(tiny_spi_exit); + +MODULE_DESCRIPTION("OpenCores tiny SPI driver"); +MODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/spi/spi_sh.c b/drivers/spi/spi_sh.c new file mode 100644 index 000000000000..869a07d375d6 --- /dev/null +++ b/drivers/spi/spi_sh.c @@ -0,0 +1,543 @@ +/* + * SH SPI bus driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * + * Based on pxa2xx_spi.c: + * Copyright (C) 2005 Stephen Street / StreetFire Sound Labs + * + * 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; version 2 of the License. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/spi/spi.h> + +#define SPI_SH_TBR 0x00 +#define SPI_SH_RBR 0x00 +#define SPI_SH_CR1 0x08 +#define SPI_SH_CR2 0x10 +#define SPI_SH_CR3 0x18 +#define SPI_SH_CR4 0x20 +#define SPI_SH_CR5 0x28 + +/* CR1 */ +#define SPI_SH_TBE 0x80 +#define SPI_SH_TBF 0x40 +#define SPI_SH_RBE 0x20 +#define SPI_SH_RBF 0x10 +#define SPI_SH_PFONRD 0x08 +#define SPI_SH_SSDB 0x04 +#define SPI_SH_SSD 0x02 +#define SPI_SH_SSA 0x01 + +/* CR2 */ +#define SPI_SH_RSTF 0x80 +#define SPI_SH_LOOPBK 0x40 +#define SPI_SH_CPOL 0x20 +#define SPI_SH_CPHA 0x10 +#define SPI_SH_L1M0 0x08 + +/* CR3 */ +#define SPI_SH_MAX_BYTE 0xFF + +/* CR4 */ +#define SPI_SH_TBEI 0x80 +#define SPI_SH_TBFI 0x40 +#define SPI_SH_RBEI 0x20 +#define SPI_SH_RBFI 0x10 +#define SPI_SH_WPABRT 0x04 +#define SPI_SH_SSS 0x01 + +/* CR8 */ +#define SPI_SH_P1L0 0x80 +#define SPI_SH_PP1L0 0x40 +#define SPI_SH_MUXI 0x20 +#define SPI_SH_MUXIRQ 0x10 + +#define SPI_SH_FIFO_SIZE 32 +#define SPI_SH_SEND_TIMEOUT (3 * HZ) +#define SPI_SH_RECEIVE_TIMEOUT (HZ >> 3) + +#undef DEBUG + +struct spi_sh_data { + void __iomem *addr; + int irq; + struct spi_master *master; + struct list_head queue; + struct workqueue_struct *workqueue; + struct work_struct ws; + unsigned long cr1; + wait_queue_head_t wait; + spinlock_t lock; +}; + +static void spi_sh_write(struct spi_sh_data *ss, unsigned long data, + unsigned long offset) +{ + writel(data, ss->addr + offset); +} + +static unsigned long spi_sh_read(struct spi_sh_data *ss, unsigned long offset) +{ + return readl(ss->addr + offset); +} + +static void spi_sh_set_bit(struct spi_sh_data *ss, unsigned long val, + unsigned long offset) +{ + unsigned long tmp; + + tmp = spi_sh_read(ss, offset); + tmp |= val; + spi_sh_write(ss, tmp, offset); +} + +static void spi_sh_clear_bit(struct spi_sh_data *ss, unsigned long val, + unsigned long offset) +{ + unsigned long tmp; + + tmp = spi_sh_read(ss, offset); + tmp &= ~val; + spi_sh_write(ss, tmp, offset); +} + +static void clear_fifo(struct spi_sh_data *ss) +{ + spi_sh_set_bit(ss, SPI_SH_RSTF, SPI_SH_CR2); + spi_sh_clear_bit(ss, SPI_SH_RSTF, SPI_SH_CR2); +} + +static int spi_sh_wait_receive_buffer(struct spi_sh_data *ss) +{ + int timeout = 100000; + + while (spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_RBE) { + udelay(10); + if (timeout-- < 0) + return -ETIMEDOUT; + } + return 0; +} + +static int spi_sh_wait_write_buffer_empty(struct spi_sh_data *ss) +{ + int timeout = 100000; + + while (!(spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_TBE)) { + udelay(10); + if (timeout-- < 0) + return -ETIMEDOUT; + } + return 0; +} + +static int spi_sh_send(struct spi_sh_data *ss, struct spi_message *mesg, + struct spi_transfer *t) +{ + int i, retval = 0; + int remain = t->len; + int cur_len; + unsigned char *data; + unsigned long tmp; + long ret; + + if (t->len) + spi_sh_set_bit(ss, SPI_SH_SSA, SPI_SH_CR1); + + data = (unsigned char *)t->tx_buf; + while (remain > 0) { + cur_len = min(SPI_SH_FIFO_SIZE, remain); + for (i = 0; i < cur_len && + !(spi_sh_read(ss, SPI_SH_CR4) & + SPI_SH_WPABRT) && + !(spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_TBF); + i++) + spi_sh_write(ss, (unsigned long)data[i], SPI_SH_TBR); + + if (spi_sh_read(ss, SPI_SH_CR4) & SPI_SH_WPABRT) { + /* Abort SPI operation */ + spi_sh_set_bit(ss, SPI_SH_WPABRT, SPI_SH_CR4); + retval = -EIO; + break; + } + + cur_len = i; + + remain -= cur_len; + data += cur_len; + + if (remain > 0) { + ss->cr1 &= ~SPI_SH_TBE; + spi_sh_set_bit(ss, SPI_SH_TBE, SPI_SH_CR4); + ret = wait_event_interruptible_timeout(ss->wait, + ss->cr1 & SPI_SH_TBE, + SPI_SH_SEND_TIMEOUT); + if (ret == 0 && !(ss->cr1 & SPI_SH_TBE)) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + } + } + + if (list_is_last(&t->transfer_list, &mesg->transfers)) { + tmp = spi_sh_read(ss, SPI_SH_CR1); + tmp = tmp & ~(SPI_SH_SSD | SPI_SH_SSDB); + spi_sh_write(ss, tmp, SPI_SH_CR1); + spi_sh_set_bit(ss, SPI_SH_SSA, SPI_SH_CR1); + + ss->cr1 &= ~SPI_SH_TBE; + spi_sh_set_bit(ss, SPI_SH_TBE, SPI_SH_CR4); + ret = wait_event_interruptible_timeout(ss->wait, + ss->cr1 & SPI_SH_TBE, + SPI_SH_SEND_TIMEOUT); + if (ret == 0 && (ss->cr1 & SPI_SH_TBE)) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + } + + return retval; +} + +static int spi_sh_receive(struct spi_sh_data *ss, struct spi_message *mesg, + struct spi_transfer *t) +{ + int i; + int remain = t->len; + int cur_len; + unsigned char *data; + unsigned long tmp; + long ret; + + if (t->len > SPI_SH_MAX_BYTE) + spi_sh_write(ss, SPI_SH_MAX_BYTE, SPI_SH_CR3); + else + spi_sh_write(ss, t->len, SPI_SH_CR3); + + tmp = spi_sh_read(ss, SPI_SH_CR1); + tmp = tmp & ~(SPI_SH_SSD | SPI_SH_SSDB); + spi_sh_write(ss, tmp, SPI_SH_CR1); + spi_sh_set_bit(ss, SPI_SH_SSA, SPI_SH_CR1); + + spi_sh_wait_write_buffer_empty(ss); + + data = (unsigned char *)t->rx_buf; + while (remain > 0) { + if (remain >= SPI_SH_FIFO_SIZE) { + ss->cr1 &= ~SPI_SH_RBF; + spi_sh_set_bit(ss, SPI_SH_RBF, SPI_SH_CR4); + ret = wait_event_interruptible_timeout(ss->wait, + ss->cr1 & SPI_SH_RBF, + SPI_SH_RECEIVE_TIMEOUT); + if (ret == 0 && + spi_sh_read(ss, SPI_SH_CR1) & SPI_SH_RBE) { + printk(KERN_ERR "%s: timeout\n", __func__); + return -ETIMEDOUT; + } + } + + cur_len = min(SPI_SH_FIFO_SIZE, remain); + for (i = 0; i < cur_len; i++) { + if (spi_sh_wait_receive_buffer(ss)) + break; + data[i] = (unsigned char)spi_sh_read(ss, SPI_SH_RBR); + } + + remain -= cur_len; + data += cur_len; + } + + /* deassert CS when SPI is receiving. */ + if (t->len > SPI_SH_MAX_BYTE) { + clear_fifo(ss); + spi_sh_write(ss, 1, SPI_SH_CR3); + } else { + spi_sh_write(ss, 0, SPI_SH_CR3); + } + + return 0; +} + +static void spi_sh_work(struct work_struct *work) +{ + struct spi_sh_data *ss = container_of(work, struct spi_sh_data, ws); + struct spi_message *mesg; + struct spi_transfer *t; + unsigned long flags; + int ret; + + pr_debug("%s: enter\n", __func__); + + spin_lock_irqsave(&ss->lock, flags); + while (!list_empty(&ss->queue)) { + mesg = list_entry(ss->queue.next, struct spi_message, queue); + list_del_init(&mesg->queue); + + spin_unlock_irqrestore(&ss->lock, flags); + list_for_each_entry(t, &mesg->transfers, transfer_list) { + pr_debug("tx_buf = %p, rx_buf = %p\n", + t->tx_buf, t->rx_buf); + pr_debug("len = %d, delay_usecs = %d\n", + t->len, t->delay_usecs); + + if (t->tx_buf) { + ret = spi_sh_send(ss, mesg, t); + if (ret < 0) + goto error; + } + if (t->rx_buf) { + ret = spi_sh_receive(ss, mesg, t); + if (ret < 0) + goto error; + } + mesg->actual_length += t->len; + } + spin_lock_irqsave(&ss->lock, flags); + + mesg->status = 0; + mesg->complete(mesg->context); + } + + clear_fifo(ss); + spi_sh_set_bit(ss, SPI_SH_SSD, SPI_SH_CR1); + udelay(100); + + spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, + SPI_SH_CR1); + + clear_fifo(ss); + + spin_unlock_irqrestore(&ss->lock, flags); + + return; + + error: + mesg->status = ret; + mesg->complete(mesg->context); + + spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, + SPI_SH_CR1); + clear_fifo(ss); + +} + +static int spi_sh_setup(struct spi_device *spi) +{ + struct spi_sh_data *ss = spi_master_get_devdata(spi->master); + + if (!spi->bits_per_word) + spi->bits_per_word = 8; + + pr_debug("%s: enter\n", __func__); + + spi_sh_write(ss, 0xfe, SPI_SH_CR1); /* SPI sycle stop */ + spi_sh_write(ss, 0x00, SPI_SH_CR1); /* CR1 init */ + spi_sh_write(ss, 0x00, SPI_SH_CR3); /* CR3 init */ + + clear_fifo(ss); + + /* 1/8 clock */ + spi_sh_write(ss, spi_sh_read(ss, SPI_SH_CR2) | 0x07, SPI_SH_CR2); + udelay(10); + + return 0; +} + +static int spi_sh_transfer(struct spi_device *spi, struct spi_message *mesg) +{ + struct spi_sh_data *ss = spi_master_get_devdata(spi->master); + unsigned long flags; + + pr_debug("%s: enter\n", __func__); + pr_debug("\tmode = %02x\n", spi->mode); + + spin_lock_irqsave(&ss->lock, flags); + + mesg->actual_length = 0; + mesg->status = -EINPROGRESS; + + spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1); + + list_add_tail(&mesg->queue, &ss->queue); + queue_work(ss->workqueue, &ss->ws); + + spin_unlock_irqrestore(&ss->lock, flags); + + return 0; +} + +static void spi_sh_cleanup(struct spi_device *spi) +{ + struct spi_sh_data *ss = spi_master_get_devdata(spi->master); + + pr_debug("%s: enter\n", __func__); + + spi_sh_clear_bit(ss, SPI_SH_SSA | SPI_SH_SSDB | SPI_SH_SSD, + SPI_SH_CR1); +} + +static irqreturn_t spi_sh_irq(int irq, void *_ss) +{ + struct spi_sh_data *ss = (struct spi_sh_data *)_ss; + unsigned long cr1; + + cr1 = spi_sh_read(ss, SPI_SH_CR1); + if (cr1 & SPI_SH_TBE) + ss->cr1 |= SPI_SH_TBE; + if (cr1 & SPI_SH_TBF) + ss->cr1 |= SPI_SH_TBF; + if (cr1 & SPI_SH_RBE) + ss->cr1 |= SPI_SH_RBE; + if (cr1 & SPI_SH_RBF) + ss->cr1 |= SPI_SH_RBF; + + if (ss->cr1) { + spi_sh_clear_bit(ss, ss->cr1, SPI_SH_CR4); + wake_up(&ss->wait); + } + + return IRQ_HANDLED; +} + +static int __devexit spi_sh_remove(struct platform_device *pdev) +{ + struct spi_sh_data *ss = dev_get_drvdata(&pdev->dev); + + destroy_workqueue(ss->workqueue); + free_irq(ss->irq, ss); + iounmap(ss->addr); + spi_master_put(ss->master); + + return 0; +} + +static int __devinit spi_sh_probe(struct platform_device *pdev) +{ + struct resource *res; + struct spi_master *master; + struct spi_sh_data *ss; + int ret, irq; + + /* get base addr */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(res == NULL)) { + dev_err(&pdev->dev, "invalid resource\n"); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "platform_get_irq error\n"); + return -ENODEV; + } + + master = spi_alloc_master(&pdev->dev, sizeof(struct spi_sh_data)); + if (master == NULL) { + dev_err(&pdev->dev, "spi_alloc_master error.\n"); + return -ENOMEM; + } + + ss = spi_master_get_devdata(master); + dev_set_drvdata(&pdev->dev, ss); + + ss->irq = irq; + ss->master = master; + ss->addr = ioremap(res->start, resource_size(res)); + if (ss->addr == NULL) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto error1; + } + INIT_LIST_HEAD(&ss->queue); + spin_lock_init(&ss->lock); + INIT_WORK(&ss->ws, spi_sh_work); + init_waitqueue_head(&ss->wait); + ss->workqueue = create_singlethread_workqueue( + dev_name(master->dev.parent)); + if (ss->workqueue == NULL) { + dev_err(&pdev->dev, "create workqueue error\n"); + ret = -EBUSY; + goto error2; + } + + ret = request_irq(irq, spi_sh_irq, IRQF_DISABLED, "spi_sh", ss); + if (ret < 0) { + dev_err(&pdev->dev, "request_irq error\n"); + goto error3; + } + + master->num_chipselect = 2; + master->bus_num = pdev->id; + master->setup = spi_sh_setup; + master->transfer = spi_sh_transfer; + master->cleanup = spi_sh_cleanup; + + ret = spi_register_master(master); + if (ret < 0) { + printk(KERN_ERR "spi_register_master error.\n"); + goto error4; + } + + return 0; + + error4: + free_irq(irq, ss); + error3: + destroy_workqueue(ss->workqueue); + error2: + iounmap(ss->addr); + error1: + spi_master_put(master); + + return ret; +} + +static struct platform_driver spi_sh_driver = { + .probe = spi_sh_probe, + .remove = __devexit_p(spi_sh_remove), + .driver = { + .name = "sh_spi", + .owner = THIS_MODULE, + }, +}; + +static int __init spi_sh_init(void) +{ + return platform_driver_register(&spi_sh_driver); +} +module_init(spi_sh_init); + +static void __exit spi_sh_exit(void) +{ + platform_driver_unregister(&spi_sh_driver); +} +module_exit(spi_sh_exit); + +MODULE_DESCRIPTION("SH SPI bus driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_ALIAS("platform:sh_spi"); diff --git a/drivers/spi/spi_sh_msiof.c b/drivers/spi/spi_sh_msiof.c index 2c665fceaac7..e00d94b22250 100644 --- a/drivers/spi/spi_sh_msiof.c +++ b/drivers/spi/spi_sh_msiof.c @@ -9,22 +9,22 @@ * */ -#include <linux/kernel.h> -#include <linux/init.h> +#include <linux/bitmap.h> +#include <linux/clk.h> +#include <linux/completion.h> #include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/init.h> #include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> #include <linux/platform_device.h> -#include <linux/completion.h> #include <linux/pm_runtime.h> -#include <linux/gpio.h> -#include <linux/bitmap.h> -#include <linux/clk.h> -#include <linux/io.h> -#include <linux/err.h> +#include <linux/spi/sh_msiof.h> #include <linux/spi/spi.h> #include <linux/spi/spi_bitbang.h> -#include <linux/spi/sh_msiof.h> #include <asm/unaligned.h> @@ -67,7 +67,7 @@ struct sh_msiof_spi_priv { #define STR_TEOF (1 << 23) #define STR_REOF (1 << 7) -static unsigned long sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs) +static u32 sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs) { switch (reg_offs) { case TSCR: @@ -79,7 +79,7 @@ static unsigned long sh_msiof_read(struct sh_msiof_spi_priv *p, int reg_offs) } static void sh_msiof_write(struct sh_msiof_spi_priv *p, int reg_offs, - unsigned long value) + u32 value) { switch (reg_offs) { case TSCR: @@ -93,10 +93,10 @@ static void sh_msiof_write(struct sh_msiof_spi_priv *p, int reg_offs, } static int sh_msiof_modify_ctr_wait(struct sh_msiof_spi_priv *p, - unsigned long clr, unsigned long set) + u32 clr, u32 set) { - unsigned long mask = clr | set; - unsigned long data; + u32 mask = clr | set; + u32 data; int k; data = sh_msiof_read(p, CTR); @@ -166,10 +166,10 @@ static void sh_msiof_spi_set_clk_regs(struct sh_msiof_spi_priv *p, } static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p, - int cpol, int cpha, - int tx_hi_z, int lsb_first) + u32 cpol, u32 cpha, + u32 tx_hi_z, u32 lsb_first) { - unsigned long tmp; + u32 tmp; int edge; /* @@ -187,7 +187,7 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p, tmp |= cpol << 30; /* TSCKIZ */ tmp |= cpol << 28; /* RSCKIZ */ - edge = cpol ? cpha : !cpha; + edge = cpol ^ !cpha; tmp |= edge << 27; /* TEDG */ tmp |= edge << 26; /* REDG */ @@ -197,11 +197,9 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p, const void *tx_buf, void *rx_buf, - int bits, int words) + u32 bits, u32 words) { - unsigned long dr2; - - dr2 = ((bits - 1) << 24) | ((words - 1) << 16); + u32 dr2 = ((bits - 1) << 24) | ((words - 1) << 16); if (tx_buf) sh_msiof_write(p, TMDR2, dr2); @@ -222,7 +220,7 @@ static void sh_msiof_reset_str(struct sh_msiof_spi_priv *p) static void sh_msiof_spi_write_fifo_8(struct sh_msiof_spi_priv *p, const void *tx_buf, int words, int fs) { - const unsigned char *buf_8 = tx_buf; + const u8 *buf_8 = tx_buf; int k; for (k = 0; k < words; k++) @@ -232,7 +230,7 @@ static void sh_msiof_spi_write_fifo_8(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_write_fifo_16(struct sh_msiof_spi_priv *p, const void *tx_buf, int words, int fs) { - const unsigned short *buf_16 = tx_buf; + const u16 *buf_16 = tx_buf; int k; for (k = 0; k < words; k++) @@ -242,7 +240,7 @@ static void sh_msiof_spi_write_fifo_16(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_write_fifo_16u(struct sh_msiof_spi_priv *p, const void *tx_buf, int words, int fs) { - const unsigned short *buf_16 = tx_buf; + const u16 *buf_16 = tx_buf; int k; for (k = 0; k < words; k++) @@ -252,7 +250,7 @@ static void sh_msiof_spi_write_fifo_16u(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_write_fifo_32(struct sh_msiof_spi_priv *p, const void *tx_buf, int words, int fs) { - const unsigned int *buf_32 = tx_buf; + const u32 *buf_32 = tx_buf; int k; for (k = 0; k < words; k++) @@ -262,17 +260,37 @@ static void sh_msiof_spi_write_fifo_32(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_write_fifo_32u(struct sh_msiof_spi_priv *p, const void *tx_buf, int words, int fs) { - const unsigned int *buf_32 = tx_buf; + const u32 *buf_32 = tx_buf; int k; for (k = 0; k < words; k++) sh_msiof_write(p, TFDR, get_unaligned(&buf_32[k]) << fs); } +static void sh_msiof_spi_write_fifo_s32(struct sh_msiof_spi_priv *p, + const void *tx_buf, int words, int fs) +{ + const u32 *buf_32 = tx_buf; + int k; + + for (k = 0; k < words; k++) + sh_msiof_write(p, TFDR, swab32(buf_32[k] << fs)); +} + +static void sh_msiof_spi_write_fifo_s32u(struct sh_msiof_spi_priv *p, + const void *tx_buf, int words, int fs) +{ + const u32 *buf_32 = tx_buf; + int k; + + for (k = 0; k < words; k++) + sh_msiof_write(p, TFDR, swab32(get_unaligned(&buf_32[k]) << fs)); +} + static void sh_msiof_spi_read_fifo_8(struct sh_msiof_spi_priv *p, void *rx_buf, int words, int fs) { - unsigned char *buf_8 = rx_buf; + u8 *buf_8 = rx_buf; int k; for (k = 0; k < words; k++) @@ -282,7 +300,7 @@ static void sh_msiof_spi_read_fifo_8(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_read_fifo_16(struct sh_msiof_spi_priv *p, void *rx_buf, int words, int fs) { - unsigned short *buf_16 = rx_buf; + u16 *buf_16 = rx_buf; int k; for (k = 0; k < words; k++) @@ -292,7 +310,7 @@ static void sh_msiof_spi_read_fifo_16(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_read_fifo_16u(struct sh_msiof_spi_priv *p, void *rx_buf, int words, int fs) { - unsigned short *buf_16 = rx_buf; + u16 *buf_16 = rx_buf; int k; for (k = 0; k < words; k++) @@ -302,7 +320,7 @@ static void sh_msiof_spi_read_fifo_16u(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_read_fifo_32(struct sh_msiof_spi_priv *p, void *rx_buf, int words, int fs) { - unsigned int *buf_32 = rx_buf; + u32 *buf_32 = rx_buf; int k; for (k = 0; k < words; k++) @@ -312,19 +330,40 @@ static void sh_msiof_spi_read_fifo_32(struct sh_msiof_spi_priv *p, static void sh_msiof_spi_read_fifo_32u(struct sh_msiof_spi_priv *p, void *rx_buf, int words, int fs) { - unsigned int *buf_32 = rx_buf; + u32 *buf_32 = rx_buf; int k; for (k = 0; k < words; k++) put_unaligned(sh_msiof_read(p, RFDR) >> fs, &buf_32[k]); } +static void sh_msiof_spi_read_fifo_s32(struct sh_msiof_spi_priv *p, + void *rx_buf, int words, int fs) +{ + u32 *buf_32 = rx_buf; + int k; + + for (k = 0; k < words; k++) + buf_32[k] = swab32(sh_msiof_read(p, RFDR) >> fs); +} + +static void sh_msiof_spi_read_fifo_s32u(struct sh_msiof_spi_priv *p, + void *rx_buf, int words, int fs) +{ + u32 *buf_32 = rx_buf; + int k; + + for (k = 0; k < words; k++) + put_unaligned(swab32(sh_msiof_read(p, RFDR) >> fs), &buf_32[k]); +} + static int sh_msiof_spi_bits(struct spi_device *spi, struct spi_transfer *t) { int bits; bits = t ? t->bits_per_word : 0; - bits = bits ? bits : spi->bits_per_word; + if (!bits) + bits = spi->bits_per_word; return bits; } @@ -334,7 +373,8 @@ static unsigned long sh_msiof_spi_hz(struct spi_device *spi, unsigned long hz; hz = t ? t->speed_hz : 0; - hz = hz ? hz : spi->max_speed_hz; + if (!hz) + hz = spi->max_speed_hz; return hz; } @@ -468,9 +508,17 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t) int bytes_done; int words; int n; + bool swab; bits = sh_msiof_spi_bits(spi, t); + if (bits <= 8 && t->len > 15 && !(t->len & 3)) { + bits = 32; + swab = true; + } else { + swab = false; + } + /* setup bytes per word and fifo read/write functions */ if (bits <= 8) { bytes_per_word = 1; @@ -487,6 +535,17 @@ static int sh_msiof_spi_txrx(struct spi_device *spi, struct spi_transfer *t) rx_fifo = sh_msiof_spi_read_fifo_16u; else rx_fifo = sh_msiof_spi_read_fifo_16; + } else if (swab) { + bytes_per_word = 4; + if ((unsigned long)t->tx_buf & 0x03) + tx_fifo = sh_msiof_spi_write_fifo_s32u; + else + tx_fifo = sh_msiof_spi_write_fifo_s32; + + if ((unsigned long)t->rx_buf & 0x03) + rx_fifo = sh_msiof_spi_read_fifo_s32u; + else + rx_fifo = sh_msiof_spi_read_fifo_s32; } else { bytes_per_word = 4; if ((unsigned long)t->tx_buf & 0x03) diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 603428213d21..d9fd86211365 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -30,6 +30,7 @@ #include <linux/errno.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/compat.h> #include <linux/spi/spi.h> #include <linux/spi/spidev.h> @@ -471,6 +472,16 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return retval; } +#ifdef CONFIG_COMPAT +static long +spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); +} +#else +#define spidev_compat_ioctl NULL +#endif /* CONFIG_COMPAT */ + static int spidev_open(struct inode *inode, struct file *filp) { struct spidev_data *spidev; @@ -543,6 +554,7 @@ static const struct file_operations spidev_fops = { .write = spidev_write, .read = spidev_read, .unlocked_ioctl = spidev_ioctl, + .compat_ioctl = spidev_compat_ioctl, .open = spidev_open, .release = spidev_release, .llseek = no_llseek, diff --git a/include/linux/spi/mcp23s08.h b/include/linux/spi/mcp23s08.h index 22ef107d7704..c42cff8ca191 100644 --- a/include/linux/spi/mcp23s08.h +++ b/include/linux/spi/mcp23s08.h @@ -2,21 +2,24 @@ /* FIXME driver should be able to handle IRQs... */ struct mcp23s08_chip_info { - bool is_present; /* true iff populated */ - u8 pullups; /* BIT(x) means enable pullup x */ + bool is_present; /* true if populated */ + unsigned pullups; /* BIT(x) means enable pullup x */ }; struct mcp23s08_platform_data { - /* Four slaves (numbered 0..3) can share one SPI chipselect, and - * will provide 8..32 GPIOs using 1..4 gpio_chip instances. + /* For mcp23s08, up to 4 slaves (numbered 0..3) can share one SPI + * chipselect, each providing 1 gpio_chip instance with 8 gpios. + * For mpc23s17, up to 8 slaves (numbered 0..7) can share one SPI + * chipselect, each providing 1 gpio_chip (port A + port B) with + * 16 gpios. */ - struct mcp23s08_chip_info chip[4]; + struct mcp23s08_chip_info chip[8]; /* "base" is the number of the first GPIO. Dynamic assignment is * not currently supported, and even if there are gaps in chip * addressing the GPIO numbers are sequential .. so for example * if only slaves 0 and 3 are present, their GPIOs range from - * base to base+15. + * base to base+15 (or base+31 for s17 variant). */ unsigned base; diff --git a/include/linux/spi/spi_oc_tiny.h b/include/linux/spi/spi_oc_tiny.h new file mode 100644 index 000000000000..1ac529cf4f06 --- /dev/null +++ b/include/linux/spi/spi_oc_tiny.h @@ -0,0 +1,20 @@ +#ifndef _LINUX_SPI_SPI_OC_TINY_H +#define _LINUX_SPI_SPI_OC_TINY_H + +/** + * struct tiny_spi_platform_data - platform data of the OpenCores tiny SPI + * @freq: input clock freq to the core. + * @baudwidth: baud rate divider width of the core. + * @gpio_cs_count: number of gpio pins used for chipselect. + * @gpio_cs: array of gpio pins used for chipselect. + * + * freq and baudwidth are used only if the divider is programmable. + */ +struct tiny_spi_platform_data { + unsigned int freq; + unsigned int baudwidth; + unsigned int gpio_cs_count; + int *gpio_cs; +}; + +#endif /* _LINUX_SPI_SPI_OC_TINY_H */ |