2005-12-23 14:14:27

by Vitaly Wool

[permalink] [raw]
Subject: [PATCH] SPI: add support for PNX/SPI controller

Hi,
inlined is a pretty much _development_ version of the Philips SPI controller support we're working with on some Philips ARM targets (PNX0106 and PNX4008, namely) based on spi_bitbang library from David Brownell. It's quite a notable thing that this buggy and in some way complicated SPI controller fits well in this bitbang framework! :)
This patch also contains dumb EEPROM driver for the onboard EEPROM behind the SPI bus. This driver was used by us for controller driver testing, mainly.

Signed-off-by: Vitaly Wool <[email protected]>
Signed-off-by: Dmitry Pervusin <[email protected]>

drivers/spi/Kconfig | 8
drivers/spi/Makefile | 2
drivers/spi/eeprom.c | 127 ++++++++++++
drivers/spi/spi_pnx.c | 365 ++++++++++++++++++++++++++++++++++++
drivers/spi/spi_pnx.h | 60 ++++++
include/asm-arm/arch-pnx4008/spi.h | 370 +++++++++++++++++++++++++++++++++++++
6 files changed, 932 insertions(+)

Index: linux-2.6/drivers/spi/Kconfig
===================================================================
--- linux-2.6.orig/drivers/spi/Kconfig
+++ linux-2.6/drivers/spi/Kconfig
@@ -95,6 +95,14 @@ config SPI_BUTTERFLY
# Add new SPI master controllers in alphabetical order above this line
#

+config SPI_PNX
+ tristate "PNX SPI bus support"
+ depends on ARCH_PNX4008 && SPI_BITBANG
+
+config SPI_EEPROM
+ tristate "SPI EEPROM"
+ depends on SPI
+

#
# There are lots of SPI device types, with sensors and memory
Index: linux-2.6/drivers/spi/Makefile
===================================================================
--- linux-2.6.orig/drivers/spi/Makefile
+++ linux-2.6/drivers/spi/Makefile
@@ -13,9 +13,11 @@ obj-$(CONFIG_SPI_MASTER) += spi.o
# SPI master controller drivers (bus)
obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o
obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o
+obj-$(CONFIG_SPI_PNX) += spi_pnx.o
# ... add above this line ...

# SPI protocol drivers (device/link on bus)
+obj-$(CONFIG_SPI_EEPROM) += eeprom.o
# ... add above this line ...

# SPI slave controller drivers (upstream link)
Index: linux-2.6/drivers/spi/eeprom.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/spi/eeprom.c
@@ -0,0 +1,127 @@
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/proc_fs.h>
+#include <linux/ctype.h>
+
+#define EEPROM_SIZE 256
+#define DRIVER_NAME "EEPROM"
+#define READ_BUFF_SIZE 160
+
+static int __init spi_eeprom_init(void);
+static void __exit spi_eeprom_cleanup(void);
+
+static int spiee_read_block (struct device *d, void *block)
+{
+ struct spi_device *spi = to_spi_device(d);
+ char cmd[2];
+ struct spi_transfer t[2] = {
+ [0] = {
+ .tx_buf = cmd,
+ .len = sizeof cmd,
+ },
+ [1] = {
+ .rx_buf = block,
+ .len = 256,
+ },
+ };
+ DECLARE_SPI_MESSAGE(m);
+
+ cmd[ 0 ] = 0x03;
+ cmd[ 1 ] = 0x00;
+ list_add_tail(&t[0].link, &m.transfers);
+ list_add_tail(&t[1].link, &m.transfers);
+
+ spi_sync(spi, &m);
+ return 256;
+}
+static ssize_t blk_show (struct device *d, struct device_attribute *attr, char *text )
+{
+ char *rdbuff = kmalloc (256, SLAB_KERNEL);
+ char line1[80],line2[80];
+ char item1[5], item2[5];
+ int bytes, i, x, blen;
+
+ blen = spiee_read_block (d, rdbuff);
+
+ bytes = 0;
+
+ strcpy(text, "");
+ for (i = 0; i < blen; i += 8) {
+ strcpy(line1, "");
+ strcpy(line2, "" );
+ for (x = i; x < i + 8; x++) {
+ if (x > blen) {
+ sprintf(item1, " ");
+ sprintf(item2, " " );
+ } else {
+ sprintf(item1, "%02x ", rdbuff[x]);
+ if (isprint(rdbuff[x])) {
+ sprintf(item2, "%c", rdbuff[x]);
+ } else {
+ sprintf(item2, ".");
+ }
+ }
+ strcat(line1, item1);
+ strcat(line2, item2);
+ }
+
+ strcat(text, line1);
+ strcat(text, "| " );
+ strcat(text, line2);
+ strcat(text, "\n" );
+
+ bytes += (strlen (line1 ) + strlen(line2) + 4);
+ }
+
+ kfree (rdbuff);
+
+ return bytes + 1;
+}
+
+static DEVICE_ATTR(blk, S_IRUGO, blk_show, NULL );
+
+
+static int spiee_probe(struct spi_device *this_dev)
+{
+ device_create_file(&this_dev->dev, &dev_attr_blk);
+ return 0;
+}
+
+static int spiee_remove(struct spi_device *this_dev)
+{
+ device_remove_file(&this_dev->dev, &dev_attr_blk);
+ return 0;
+}
+
+static struct spi_driver eeprom_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = spiee_probe,
+ .remove = __devexit_p(spiee_remove),
+};
+
+static int __init spi_eeprom_init(void)
+{
+ int rc = spi_register_driver(&eeprom_driver);
+ printk("%s: %d\n", __FUNCTION__, rc);
+ return rc;
+}
+static void __exit spi_eeprom_cleanup(void)
+{
+ spi_unregister_driver(&eeprom_driver);
+}
+
+module_init(spi_eeprom_init);
+module_exit(spi_eeprom_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("dmitry pervushin <[email protected]>");
+MODULE_DESCRIPTION("SPI EEPROM driver");
Index: linux-2.6/drivers/spi/spi_pnx.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/spi/spi_pnx.c
@@ -0,0 +1,365 @@
+/*
+ * drivers/spi/spi-pnx.c
+ *
+ * SPI support for PNX 010x/4008 boards.
+ *
+ * Author: dmitry pervushin <[email protected]>
+ * Based on Dennis Kovalev's <[email protected]> bus driver for pnx010x
+ *
+ * 2004 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <asm/io.h>
+
+#include "spi_pnx.h"
+
+#define lock_device( dev ) /* down( &dev->sem ); */
+#define unlock_device( dev ) /* up( &dev->sem ); */
+
+void spipnx_dma_handler (int channel, int cause, void* context, struct pt_regs* pt_regs )
+{
+ struct spi_pnx_data *dd = context;
+
+ if (cause & DMA_TC_INT)
+ complete(&dd->threshold);
+}
+
+int spipnx_request_hardware(struct spi_pnx_data *dd,
+ struct platform_device *dev)
+{
+ int err = 0;
+ struct resource *rsrc;
+
+ lock_device(&dev->dev);
+ rsrc = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ if (rsrc) {
+ dd->iostart = rsrc->start;
+ dd->ioend = rsrc->end;
+ dd->spi_regs = ioremap(dd->iostart, SZ_4K);
+ /*
+ if (!request_mem_region
+ (dd->iostart, dd->ioend - dd->iostart + 1, dev->dev.bus_id))
+ err = -ENODEV;
+ else
+ */
+ dd->io_flags |= IORESOURCE_MEM;
+ }
+ dd->clk = spipnx_clk_init(dev);
+ if (IS_ERR(dd->clk)) {
+ err = PTR_ERR(dd->clk);
+ goto out;
+ }
+
+ spipnx_clk_start(dd->clk);
+
+ dd->irq = platform_get_irq(dev, 0);
+ if (dd->irq != NO_IRQ) {
+ dd->spi_regs->ier = 0;
+ dd->spi_regs->stat = 0;
+ }
+
+ rsrc = platform_get_resource(dev, IORESOURCE_DMA, 0);
+ if (rsrc) {
+ dd->slave_nr = rsrc->start;
+ err = spipnx_request_dma(&dd->dma_channel, dev->dev.bus_id, spipnx_dma_handler, dd);
+ if (!err)
+ dd->io_flags |= IORESOURCE_DMA;
+ }
+out:
+ unlock_device(&dev.dev);
+ return err;
+}
+
+static void spipnx_free_hardware(struct spi_pnx_data *dd,
+ struct platform_device *dev)
+{
+ lock_device(&dev->dev);
+
+ spipnx_clk_stop(dd->clk);
+
+ if (dd->io_flags & IORESOURCE_IRQ) {
+ free_irq(dd->irq, dev->dev.bus_id);
+ dd->io_flags &= ~IORESOURCE_IRQ;
+ }
+ if (dd->io_flags & IORESOURCE_MEM) {
+ /*
+ release_mem_region((unsigned long)dd->iostart,
+ dd->ioend - dd->iostart + 1);
+ */
+ dd->io_flags &= ~IORESOURCE_MEM;
+ }
+ if (dd->io_flags & IORESOURCE_DMA) {
+ spipnx_release_dma(&dd->dma_channel);
+ dd->io_flags &= ~IORESOURCE_DMA;
+ }
+
+ spipnx_clk_free(dd->clk);
+
+#ifdef DEBUG
+ if (dd->io_flags)
+ printk(KERN_ERR
+ "Oops, dd_ioflags for driver data %p is still 0x%x!\n",
+ dd, dd->io_flags);
+#endif
+ unlock_device(&dev->dev);
+}
+
+static void
+spi_pnx_chipselect(struct spi_device *spi, int is_on)
+{
+ spipnx_arch_cs(spi, is_on);
+}
+
+static int spi_pnx_xfer(struct spi_device *spidev, struct spi_transfer *t)
+{
+ u8* dat;
+ int len;
+ struct device *dev;
+ struct spi_pnx_data *dd;
+ vhblas_spiregs* regs;
+ int i;
+ u32 stat, fc, con, timeout;
+ struct spipnx_dma_transfer_t params;
+
+ dev = &spidev->dev;
+ dd = dev_get_drvdata(spidev->master->cdev.dev);
+ regs = dd->spi_regs;
+ len = t->len;
+
+ /* REVISIT */
+ con = regs->con;
+ regs->global = SPI_GLOBAL_BLRES_SPI | SPI_GLOBAL_SPI_ON;
+ udelay(10);
+ regs->global = SPI_GLOBAL_SPI_ON;
+ for (timeout = 10000; timeout >= 0; --timeout)
+ if (dd->spi_regs->global & SPIPNX_GLOBAL_SPI_ON)
+ break;
+ con |= (7<<9);
+ con &= ~(SPIPNX_CON_RXTX);
+ regs->con = con;
+
+ if (spidev->max_speed_hz)
+ spi_pnx_set_clock_rate(spidev->max_speed_hz / 1000, regs);
+
+ regs->stat |= 0x100;
+ regs->con |= SPI_CON_SPI_BIDIR | SPI_CON_SPI_BHALT | SPI_CON_SPI_BPOL |
+ SPI_CON_THR | SPI_CON_MS;
+
+ if (t->tx_buf) {
+ dat = (u8 *)t->tx_buf;
+ regs->con |= SPIPNX_CON_RXTX;
+ regs->ier |= SPIPNX_IER_EOT;
+ regs->con &= ~SPIPNX_CON_SHIFT_OFF;
+ regs->frm = len;
+
+ if (dd->dma_mode && len >= FIFO_CHUNK_SIZE) {
+ void *dmasafe = NULL;
+ if (t->tx_dma)
+ params.dma_buffer = t->tx_dma;
+ else {
+ dmasafe = kmalloc(len, SLAB_KERNEL);
+ if (!dmasafe) {
+ len = 0;
+ goto out;
+ }
+ params.dma_buffer = dma_map_single(dev->parent, dmasafe, len, DMA_TO_DEVICE);
+ memcpy(dmasafe, dat, len);
+ }
+ params.saved_ll = NULL;
+ params.saved_ll_dma = 0;
+
+ spipnx_setup_dma(dd->iostart, dd->slave_nr, dd->dma_channel,
+ DMA_MODE_WRITE, &params, len);
+ init_completion (&dd->threshold);
+ spipnx_start_dma(dd->dma_channel);
+ wait_for_completion (&dd->threshold);
+ spipnx_stop_dma(dd->dma_channel, &params);
+ if (dmasafe) {
+ dma_unmap_single(dev->parent, params.dma_buffer, len, DMA_TO_DEVICE);
+ kfree(dmasafe);
+ }
+ } else {
+ regs->con &= ~SPIPNX_CON_SHIFT_OFF;
+ for (i = 0; i < len ; i ++ ) {
+ while (regs->stat & 0x04)
+ continue;
+ regs->dat = *dat;
+ dat ++;
+ }
+ }
+ while ((regs->stat & SPIPNX_STAT_EOT) == 0)
+ continue;
+ }
+
+ if (t->rx_buf) {
+ int stopped;
+ u8 c;
+ void *dmasafe = NULL;
+
+ dat = t->rx_buf;
+ regs->con &= ~SPIPNX_CON_RXTX;
+ regs->ier |= SPIPNX_IER_EOT;
+ regs->con &= ~SPIPNX_CON_SHIFT_OFF;
+ regs->frm = len;
+
+ regs->dat = 0; /* kick off read */
+
+ if (dd->dma_mode && len > FIFO_CHUNK_SIZE - 1) {
+ if (t->rx_dma)
+ params.dma_buffer = t->rx_dma;
+ else {
+ dmasafe = kmalloc(len, SLAB_KERNEL);
+ if (!dmasafe) {
+ len = 0;
+ goto out;
+ }
+ params.dma_buffer = dma_map_single(dev->parent, dmasafe, len - (FIFO_CHUNK_SIZE - 1), DMA_FROM_DEVICE);
+ }
+ params.saved_ll = NULL;
+ params.saved_ll_dma = 0;
+
+ spipnx_setup_dma(dd->iostart, dd->slave_nr, dd->dma_channel,
+ DMA_MODE_READ, &params, len - (FIFO_CHUNK_SIZE - 1));
+ init_completion (&dd->threshold);
+ spipnx_start_dma(dd->dma_channel);
+ wait_for_completion (&dd->threshold);
+ spipnx_stop_dma(dd->dma_channel, &params);
+ if (dmasafe) {
+ memcpy(dat, dmasafe, len - (FIFO_CHUNK_SIZE - 1));
+ dma_unmap_single(dev->parent, params.dma_buffer, len - (FIFO_CHUNK_SIZE - 1), DMA_FROM_DEVICE);
+ kfree(dmasafe);
+ }
+ dat = dat + len - (FIFO_CHUNK_SIZE - 1);
+ len = FIFO_CHUNK_SIZE - 1;
+ }
+
+ stopped = 0;
+ stat = 0;
+ for(i = 0; i < len; ) {
+ unsigned long irq_flags;
+ local_irq_save(irq_flags);
+
+ stat = dd->spi_regs->stat;;
+ fc = dd->spi_regs->frm;
+ /* has hardware finished ? */
+ if(fc == 0) {
+ regs->con |= SPIPNX_CON_SHIFT_OFF;
+ stopped = 1;
+ }
+ /* FIFO not empty and hardware not about to finish */
+ if((!(stat & SPI_STAT_SPI_BE)) && ((fc > 4) || (stopped ))) {
+ *dat++= c = dd->spi_regs->dat;
+ i++;
+ }
+ if((stat & SPI_STAT_SPI_BF) && (!stopped)) {
+ *dat++= c = dd->spi_regs->dat;
+ i++;
+ }
+ local_irq_restore(irq_flags);
+ }
+ }
+out:
+ return len;
+}
+
+static int spi_pnx_setup(struct spi_device *spi)
+{
+ return 0;
+}
+
+static int spi_pnx_probe(struct device *device)
+{
+ struct spi_master *master;
+ struct spi_pnx_data *data;
+ struct spi_bitbang *pnx;
+ int rc = 0;
+
+ printk("spi probe called\n");
+ master = spi_alloc_master(device, sizeof *data);
+ if (!master) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ master->setup = &spi_pnx_setup;
+ master->bus_num = to_platform_device(device)->id;
+
+ pnx = spi_master_get_devdata(master);
+ pnx->master = master;
+ pnx->chipselect = &spi_pnx_chipselect;
+ pnx->txrx_bufs = &spi_pnx_xfer;
+ rc = spi_bitbang_start(pnx);
+ if (rc < 0)
+ goto out;
+
+ data = kzalloc(sizeof *data, GFP_KERNEL);
+ if (!data) {
+ spi_bitbang_stop(pnx);
+ kfree(master);
+ rc = -ENOMEM;
+ goto out;
+ }
+ data->dma_channel = -1;
+ data->slave_nr = -1;
+ data->irq = NO_IRQ;
+ data->master = master;
+ data->dma_mode = 1;
+ dev_set_drvdata(device, data);
+
+ rc = spipnx_request_hardware(data, to_platform_device(device));
+ if (rc < 0) {
+ spi_bitbang_stop(pnx);
+ kfree(master);
+ kfree(data);
+ goto out;
+ }
+out:
+ return rc;
+}
+
+static int spi_pnx_remove(struct device *device)
+{
+ struct spi_pnx_data *data = dev_get_drvdata(device);
+ struct spi_bitbang *pnx;
+
+ pnx = spi_master_get_devdata(data->master);
+
+ spipnx_free_hardware(data, to_platform_device(device));
+ spi_bitbang_stop(pnx);
+ kfree(data->master);
+ kfree(data);
+ return 0;
+}
+
+
+static struct device_driver spi_pnx_driver = {
+ .name = "spipnx",
+ .bus = &platform_bus_type,
+ .probe = spi_pnx_probe,
+ .remove = spi_pnx_remove,
+};
+
+static int __init spi_pnx_init(void)
+{
+ return driver_register(&spi_pnx_driver);
+}
+
+static void __exit spi_pnx_cleanup(void)
+{
+ driver_unregister(&spi_pnx_driver);
+}
+
+module_init(spi_pnx_init);
+module_exit(spi_pnx_cleanup);
Index: linux-2.6/include/asm-arm/arch-pnx4008/spi.h
===================================================================
--- /dev/null
+++ linux-2.6/include/asm-arm/arch-pnx4008/spi.h
@@ -0,0 +1,370 @@
+/*
+ * include/asm-arm/arch-pnx4008/spi.h
+ *
+ * Author: Vitaly Wool <[email protected]>
+ *
+ * PNX4008-specific tweaks for SPI block
+ *
+ * 2005 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+#ifndef __ASM_ARCH_SPI_H__
+#define __ASM_ARCH_SPI_H__
+
+#include <linux/spi/spi.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include <asm/dma.h>
+
+#include <asm/arch/dma.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/pm.h>
+#include <asm/arch/gpio.h>
+
+#include <asm/hardware/clock.h>
+
+#define SPI_ADR_OFFSET_GLOBAL 0x0000 /* R/W - Global control register */
+#define SPI_ADR_OFFSET_CON 0x0004 /* R/W - Control register */
+#define SPI_ADR_OFFSET_FRM 0x0008 /* R/W - Frame count register */
+#define SPI_ADR_OFFSET_IER 0x000C /* R/W - Interrupt enable register */
+#define SPI_ADR_OFFSET_STAT 0x0010 /* R/W - Status register */
+#define SPI_ADR_OFFSET_DAT 0x0014 /* R/W - Data register */
+#define SPI_ADR_OFFSET_DAT_MSK 0x0018 /* W - Data register for using mask mode */
+#define SPI_ADR_OFFSET_MASK 0x001C /* R/W - Mask register */
+#define SPI_ADR_OFFSET_ADDR 0x0020 /* R/W - Address register */
+#define SPI_ADR_OFFSET_TIMER_CTRL_REG 0x0400 /* R/W - IRQ timer control register */
+#define SPI_ADR_OFFSET_TIMER_COUNT_REG 0x0404 /* R/W - Timed interrupt period register */
+#define SPI_ADR_OFFSET_TIMER_STATUS_REG 0x0408 /* R;R/C - RxDepth and interrupt status register */
+
+/* Bit definitions for SPI_GLOBAL register */
+#define SPI_GLOBAL_BLRES_SPI 0x00000002 /* R/W - Software reset, active high */
+#define SPI_GLOBAL_SPI_ON 0x00000001 /* R/W - SPI interface on */
+
+/* Bit definitions for SPI_CON register */
+#define SPI_CON_SPI_BIDIR 0x00800000 /* R/W - SPI data in bidir. mux */
+#define SPI_CON_SPI_BHALT 0x00400000 /* R/W - Halt control */
+#define SPI_CON_SPI_BPOL 0x00200000 /* R/W - Busy signal active polarity */
+#define SPI_CON_SPI_SHFT 0x00100000 /* R/W - Data shifting enable in mask mode */
+#define SPI_CON_SPI_MSB 0x00080000 /* R/W - Transfer LSB or MSB first */
+#define SPI_CON_SPI_EN 0x00040000 /* R/W - SPULSE signal enable */
+#define SPI_CON_SPI_MODE 0x00030000 /* R/W - SCK polarity and phase modes */
+#define SPI_CON_SPI_MODE3 0x00030000 /* R/W - SCK polarity and phase mode 3 */
+#define SPI_CON_SPI_MODE2 0x00020000 /* R/W - SCK polarity and phase mode 2 */
+#define SPI_CON_SPI_MODE1 0x00010000 /* R/W - SCK polarity and phase mode 1 */
+#define SPI_CON_SPI_MODE0 0x00000000 /* R/W - SCK polarity and phase mode 0 */
+#define SPI_CON_RxTx 0x00008000 /* R/W - Transfer direction */
+#define SPI_CON_THR 0x00004000 /* R/W - Threshold selection */
+#define SPI_CON_SHIFT_OFF 0x00002000 /* R/W - Inhibits generation of clock pulses on sck_spi pin */
+#define SPI_CON_BITNUM 0x00001E00 /* R/W - No of bits to tx or rx in one block transfer */
+#define SPI_CON_CS_EN 0x00000100 /* R/W - Disable use of CS in slave mode */
+#define SPI_CON_MS 0x00000080 /* R/W - Selection of master or slave mode */
+#define SPI_CON_RATE 0x0000007F /* R/W - Transmit/receive rate */
+
+#define SPI_CON_RATE_0 0x00
+#define SPI_CON_RATE_1 0x01
+#define SPI_CON_RATE_2 0x02
+#define SPI_CON_RATE_3 0x03
+#define SPI_CON_RATE_4 0x04
+#define SPI_CON_RATE_13 0x13
+
+/* Bit definitions for SPI_FRM register */
+#define SPI_FRM_SPIF 0x0000FFFF /* R/W - Number of frames to be transfered */
+
+/* Bit definitions for SPI_IER register */
+#define SPI_IER_SPI_INTCS 0x00000004 /* R/W - SPI CS level change interrupt enable */
+#define SPI_IER_SPI_INTEOT 0x00000002 /* R/W - End of transfer interrupt enable */
+#define SPI_IER_SPI_INTTHR 0x00000001 /* R/W - FIFO threshold interrupt enable */
+
+/* Bit definitions for SPI_STAT register */
+#define SPI_STAT_SPI_INTCLR 0x00000100 /* R/WC - Clear interrupt */
+#define SPI_STAT_SPI_EOT 0x00000080 /* R/W - End of transfer */
+#define SPI_STAT_SPI_BSY_SL 0x00000040 /* R/W - Status of the input pin spi_busy */
+#define SPI_STAT_SPI_CSL 0x00000020 /* R/W - Indication of the edge on SPI_CS that caused an int. */
+#define SPI_STAT_SPI_CSS 0x00000010 /* R/W - Level of SPI_CS has changed in slave mode */
+#define SPI_STAT_SPI_BUSY 0x00000008 /* R/W - A data transfer is ongoing */
+#define SPI_STAT_SPI_BF 0x00000004 /* R/W - FIFO is full */
+#define SPI_STAT_SPI_THR 0x00000002 /* R/W - No of entries in Tx/Rx FIFO */
+#define SPI_STAT_SPI_BE 0x00000001 /* R/W - FIFO is empty */
+
+/* Bit definitions for SPI_DAT register */
+#define SPI_DAT_SPID 0x0000FFFF /* R/W - SPI data bits */
+
+/* Bit definitions for SPI_DAT_MSK register */
+#define SPI_DAT_MSK_SPIDM 0x0000FFFF /* W - SPI data bits to send using the masking mode */
+
+/* Bit definitions for SPI_MASK register */
+#define SPI_MASK_SPIMSK 0x0000FFFF /* R/W - Masking bits used for validating data bits */
+
+/* Bit definitions for SPI_ADDR register */
+#define SPI_ADDR_SPIAD 0x0000FFFF /* R/W - Address bits to add to the data bits */
+
+/* Bit definitions for SPI_TIMER_CTRL_REG register */
+#define SPI_TIMER_CTRL_REG_TIRQE 0x00000004 /* R/W - Timer interrupt enable */
+#define SPI_TIMER_CTRL_REG_PIRQE 0x00000002 /* R/W - Peripheral interrupt enable */
+#define SPI_TIMER_CTRL_REG_MODE 0x00000001 /* R/W - Mode */
+
+/* Bit definitions for SPI_TIMER_COUNT_REG register */
+#define SPI_TIMER_COUNT_REG 0x0000FFFF /* R/W - Timed interrupt period */
+
+/* Bit definitions for SPI_TIMER_STATUS_REG register */
+#define SPI_TIMER_STATUS_REG_INT_STAT 0x00008000 /* R/C - Interrupt status */
+#define SPI_TIMER_STATUS_REG_FIFO_DEPTH 0x0000007F /* R - FIFO depth value (for debug purpose) */
+
+#define SPIPNX_STAT_EOT SPI_STAT_SPI_EOT
+#define SPIPNX_STAT_THR SPI_STAT_SPI_THR
+#define SPIPNX_IER_EOT SPI_IER_SPI_INTEOT
+#define SPIPNX_IER_THR SPI_IER_SPI_INTTHR
+#define SPIPNX_STAT_CLRINT SPI_STAT_SPI_INTCLR
+
+#define SPIPNX_GLOBAL_RESET_SPI SPI_GLOBAL_BLRES_SPI
+#define SPIPNX_GLOBAL_SPI_ON SPI_GLOBAL_SPI_ON
+#define SPIPNX_CON_MS SPI_CON_MS
+#define SPIPNX_CON_BIDIR SPI_CON_SPI_BIDIR
+#define SPIPNX_CON_RXTX SPI_CON_RxTx
+#define SPIPNX_CON_THR SPI_CON_THR
+#define SPIPNX_CON_SHIFT_OFF SPI_CON_SHIFT_OFF
+#define SPIPNX_DATA SPI_ADR_OFFSET_DAT
+#define SPI_PNX4008_CLOCK_IN 104000000
+#define SPI_PNX4008_CLOCK 2670000
+#define SPIPNX_CLOCK ((((SPI_PNX4008_CLOCK_IN / SPI_PNX4008_CLOCK) - 2) / 2) & SPI_CON_RATE_MASK )
+#define SPIPNX_DATA_REG(s) ( (u32)(s) + SPIPNX_DATA )
+
+typedef volatile struct {
+ u32 global; /* 0x000 */
+ u32 con; /* 0x004 */
+ u32 frm; /* 0x008 */
+ u32 ier; /* 0x00C */
+ u32 stat; /* 0x010 */
+ u32 dat; /* 0x014 */
+ u32 dat_msk; /* 0x018 */
+ u32 mask; /* 0x01C */
+ u32 addr; /* 0x020 */
+ u32 _d0[(SPI_ADR_OFFSET_TIMER_CTRL_REG -
+ (SPI_ADR_OFFSET_ADDR + sizeof(u32))) / sizeof(u32)];
+ u32 timer_ctrl; /* 0x400 */
+ u32 timer_count; /* 0x404 */
+ u32 timer_status; /* 0x408 */
+}
+vhblas_spiregs, *pvhblas_spiregs;
+
+#define SPI_CON_RATE_MASK 0x7f
+
+static void spipnx_arch_set_clock_rate( int clock_khz, int ref_clock_khz, vhblas_spiregs* regs )
+{
+ u32 spi_clock;
+
+ printk( "ref clock = %d, requested clock = %d\n", ref_clock_khz, clock_khz);
+ if(!clock_khz)
+ spi_clock = ref_clock_khz < 104000 ? 0 : 1;
+ else
+ spi_clock = (ref_clock_khz/1000) / (2*(clock_khz/1000) + 2);
+ regs->con &= ~SPI_CON_RATE_MASK;
+ printk("SPI clock = %x\n", spi_clock);
+ regs->con |= spi_clock;
+}
+
+static inline void spi_pnx_set_clock_rate(int saved_clock_khz, vhblas_spiregs *regs)
+{
+ struct clk *div_clk= clk_get(0, "hclk_ck");
+ struct clk *arm_clk = clk_get(0, "ck_pll4");
+ if (!IS_ERR(arm_clk) && !IS_ERR(div_clk))
+ spipnx_arch_set_clock_rate(
+ saved_clock_khz,
+ clk_get_rate(arm_clk) / clk_get_rate(div_clk),
+ regs);
+ if (!IS_ERR(arm_clk))
+ clk_put(arm_clk);
+ if (!IS_ERR(div_clk))
+ clk_put(div_clk);
+}
+
+static inline void spipnx_clk_stop(void *clk_data)
+{
+ struct clk *clk = (struct clk *)clk_data;
+ if (clk && !IS_ERR(clk)) {
+ clk_set_rate(clk, 0);
+ clk_put(clk);
+ }
+}
+
+static inline void spipnx_clk_start(void *clk_data)
+{
+ struct clk *clk = (struct clk *)clk_data;
+ if (clk && !IS_ERR(clk)) {
+ clk_set_rate(clk, 1);
+ clk_put(clk);
+ }
+}
+
+static inline void *spipnx_clk_init(struct platform_device *dev)
+{
+ struct clk *clk = clk_get(0, dev->id == 1 ? "spi0_ck" : "spi1_ck");
+ if (IS_ERR(clk))
+ printk("spi%d_ck pointer err\n", dev->id);
+ return clk;
+}
+
+static inline void spipnx_clk_free( void* clk )
+{
+ clk_put( clk );
+}
+
+static inline void spipnx_arch_cs(struct spi_device *spi, int is_on)
+{
+ switch (spi->chip_select)
+ {
+ case 0:
+ printk("setting pin %x to %d\n", GPO_02, !is_on);
+ pnx4008_gpio_write_pin(GPO_02, !is_on);
+ break;
+ case 1:
+ printk( "setting pin %x to %d\n", GPIO_03, !is_on);
+ pnx4008_gpio_write_pin(GPIO_03, !is_on);
+ break;
+ }
+}
+
+static inline int spipnx_request_dma(
+ int *dma_channel, char *name,
+ void (*handler)( int, int, void*, struct pt_regs*),
+ void *dma_context)
+{
+ int err;
+
+ if (*dma_channel != -1) {
+ err = *dma_channel;
+ goto out;
+ }
+ err = pnx4008_request_channel(name, -1, handler, dma_context);
+ if (err >= 0) {
+ *dma_channel = err;
+ }
+out:
+ return err < 0 ? err : 0;
+}
+
+static inline void spipnx_release_dma(int *dma_channel)
+{
+ if (*dma_channel >= 0) {
+ pnx4008_free_channel(*dma_channel);
+ }
+ *dma_channel = -1;
+}
+
+static inline void spipnx_start_dma(int dma_channel)
+{
+ pnx4008_dma_ch_enable(dma_channel);
+}
+
+struct spipnx_dma_transfer_t
+{
+ dma_addr_t dma_buffer;
+ void *saved_ll;
+ u32 saved_ll_dma;
+};
+
+static inline void spipnx_stop_dma(int dma_channel, void* dev_buf)
+{
+ struct spipnx_dma_transfer_t *buf = dev_buf;
+
+ pnx4008_dma_ch_disable(dma_channel);
+ if (buf->saved_ll) {
+ pnx4008_free_ll(buf->saved_ll_dma, buf->saved_ll);
+ buf->saved_ll = NULL;
+ }
+}
+
+static inline int spipnx_setup_dma(u32 iostart, int slave_nr, int dma_channel,
+ int mode, void* params, int len)
+{
+ int err = 0;
+ struct spipnx_dma_transfer_t *buff = params;
+
+ pnx4008_dma_config_t cfg;
+ pnx4008_dma_ch_config_t ch_cfg;
+ pnx4008_dma_ch_ctrl_t ch_ctrl;
+
+ memset(&cfg, 0, sizeof(cfg));
+
+ if (mode == DMA_MODE_READ) {
+ cfg.dest_addr = buff->dma_buffer; /* buff->dma_buffer; */
+ cfg.src_addr = (u32) SPIPNX_DATA_REG(iostart);
+ ch_cfg.flow_cntrl = FC_PER2MEM_DMA;
+ ch_cfg.src_per = slave_nr;
+ ch_cfg.dest_per = 0;
+ ch_ctrl.di = 1;
+ ch_ctrl.si = 0;
+ } else if (mode == DMA_MODE_WRITE) {
+ cfg.src_addr = buff->dma_buffer; /* buff->dma_buffer; */
+ cfg.dest_addr = (u32) SPIPNX_DATA_REG(iostart);
+ ch_cfg.flow_cntrl = FC_MEM2PER_DMA;
+ ch_cfg.dest_per = slave_nr;
+ ch_cfg.src_per = 0;
+ ch_ctrl.di = 0;
+ ch_ctrl.si = 1;
+ } else {
+ err = -EINVAL;
+ }
+
+ ch_cfg.halt = 0;
+ ch_cfg.active = 1;
+ ch_cfg.lock = 0;
+ ch_cfg.itc = 1;
+ ch_cfg.ie = 1;
+ ch_ctrl.tc_mask = 1;
+ ch_ctrl.cacheable = 0;
+ ch_ctrl.bufferable = 0;
+ ch_ctrl.priv_mode = 1;
+ ch_ctrl.dest_ahb1 = 0;
+ ch_ctrl.src_ahb1 = 0;
+ ch_ctrl.dwidth = WIDTH_BYTE;
+ ch_ctrl.swidth = WIDTH_BYTE;
+ ch_ctrl.dbsize = 1;
+ ch_ctrl.sbsize = 1;
+ ch_ctrl.tr_size = len;
+ if (0 > (err = pnx4008_dma_pack_config(&ch_cfg, &cfg.ch_cfg))) {
+ goto out;
+ }
+ if (0 > (err = pnx4008_dma_pack_control(&ch_ctrl, &cfg.ch_ctrl))) {
+ if ( err == -E2BIG ) {
+ printk( KERN_DEBUG"buffer is too large, splitting\n" );
+ pnx4008_dma_split_head_entry(&cfg, &ch_ctrl);
+ buff->saved_ll = cfg.ll;
+ buff->saved_ll_dma = cfg.ll_dma;
+ err = 0;
+ } else {
+ goto out;
+ }
+ }
+ err = pnx4008_config_channel(dma_channel, &cfg);
+out:
+ return err;
+}
+
+static inline void spipnx_start_event_init(int id)
+{
+ int se = id==0 ? SE_SPI1_DATAIN_INT : SE_SPI2_DATAIN_INT;
+
+ /*setup wakeup interrupt*/
+ start_int_set_rising_edge(se);
+ start_int_ack(se);
+ start_int_umask(se);
+}
+
+static inline void spipnx_start_event_deinit(int id)
+{
+ int se = id==0 ? SE_SPI1_DATAIN_INT : SE_SPI2_DATAIN_INT;
+
+ start_int_mask(se);
+}
+
+struct spipnx_wifi_params {
+ int gpio_cs_line;
+};
+
+#endif /* __ASM_ARCH_SPI_H___ */
+
Index: linux-2.6/drivers/spi/spi_pnx.h
===================================================================
--- /dev/null
+++ linux-2.6/drivers/spi/spi_pnx.h
@@ -0,0 +1,60 @@
+/*
+ * SPI support for Philips SPI bus on PNX's.
+ *
+ * Author: Dennis Kovalev <[email protected]>
+ *
+ * 2004 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#ifndef _SPI_PNX_BUS_DRIVER
+#define _SPI_PNX_BUS_DRIVER
+
+#include <linux/spi/spi.h>
+#include <asm/arch/platform.h>
+#include <asm/arch/dma.h>
+#include <asm/dma.h>
+#include <asm/arch/spi.h>
+
+/* structures */
+struct spi_pnx_data {
+ unsigned io_flags;
+
+ int irq;
+
+ int dma_mode;
+ int dma_channel;
+ int slave_nr;
+
+ u32 iostart, ioend;
+ vhblas_spiregs *spi_regs;
+
+ struct clk *clk;
+ int clk_id[4];
+
+ int state;
+ u32 saved_clock;
+
+ struct completion op_complete;
+ struct completion threshold;
+
+ int progress;
+ struct spi_master *master;
+
+ void (*control) (struct spi_device * dev, int code, u32 ctl);
+};
+
+#define SPIPNX_STATE_UNINITIALIZED 0
+#define SPIPNX_STATE_READY 1
+#define SPIPNX_STATE_SUSPENDED 2
+
+#define SPIPNX_IS_READY( bus ) ( (bus)->state == SPIPNX_STATE_READY )
+
+#define FIFO_CHUNK_SIZE 56
+
+#define SPI_ENDIAN_SWAP_NO 0
+#define SPI_ENDIAN_SWAP_YES 1
+
+#endif // __SPI_PNX_BUS_DRIVER


2005-12-27 17:28:31

by David Brownell

[permalink] [raw]
Subject: Re: [PATCH] SPI: add support for PNX/SPI controller

On Friday 23 December 2005 6:15 am, Vitaly Wool wrote:
> (PNX0106 and PNX4008, namely) based on spi_bitbang library

Good! Does need work yet though...

> It's quite a notable thing that this buggy and in some way
> complicated SPI controller fits well in this bitbang framework! :)

So the transfer-level hooks worked for you ... I'm glad.


> This patch also contains dumb EEPROM driver for the onboard EEPROM
> behind the SPI bus. This driver was used by us for controller driver testing, mainly.

It should still be a separate patch though.


> # Add new SPI master controllers in alphabetical order above this line

Note how it says "above", yet you've added yours ... below! :(


> #
>
> +config SPI_PNX
> + tristate "PNX SPI bus support"
> + depends on ARCH_PNX4008 && SPI_BITBANG

... also added the eeprom so it shows in the list of controller drivers!!


> +config SPI_EEPROM
> + tristate "SPI EEPROM"
> + depends on SPI
> +
>

> +#define lock_device( dev ) /* down( &dev->sem ); */
> +#define unlock_device( dev ) /* up( &dev->sem ); */

Not clear why you'd want such stuff ... better to just remove
NOPs like that.



> +static void
> +spi_pnx_chipselect(struct spi_device *spi, int is_on)
> +{
> + spipnx_arch_cs(spi, is_on);
> +}

I see that "arch" hook is actually just toggling a DMA line.
Did you notice how Stephen's PXA code handled that? The
"controller_data" was a board-specific GPIO toggle function,
as provided in the board_info that implicitly defines the
relevant device.

Plus, activating the chip does more than just toggling some
arch-specific GPIO. It has to set the right SPI mode, of
which CPOL must be set _before_ chipselect goes active.
And it has to set the transfer clock, for chips like yours
which are multiplexing devices using software.


> +static int spi_pnx_xfer(struct spi_device *spidev, struct spi_transfer *t)
> +{
...
> + if (spidev->max_speed_hz)
> + spi_pnx_set_clock_rate(spidev->max_speed_hz / 1000, regs);

Just do it the one time, when activating that chip's selection.


...
> + if (t->tx_buf) {
> + dat = (u8 *)t->tx_buf;
> + regs->con |= SPIPNX_CON_RXTX;
> + regs->ier |= SPIPNX_IER_EOT;
> + regs->con &= ~SPIPNX_CON_SHIFT_OFF;
> + regs->frm = len;
> +
> + if (dd->dma_mode && len >= FIFO_CHUNK_SIZE) {
> + void *dmasafe = NULL;
> + if (t->tx_dma)

That's not actually correct, since zero might be a legal DMA address.
Unfortunately we have no DMA_ADDR_INVALID to test against, so this
will need to suffice for now. (And I updated the spi_bitbang code
to ensure that address _is_ zeroed when the message didn't set up
the DMA addresses already, matching the default behavior.)


> + params.dma_buffer = t->tx_dma;
> + else {
> + dmasafe = kmalloc(len, SLAB_KERNEL);
> + if (!dmasafe) {
> + len = 0;
> + goto out;
> + }
> + params.dma_buffer = dma_map_single(dev->parent, dmasafe, len, DMA_TO_DEVICE);
> + memcpy(dmasafe, dat, len);

You do know this is incorrect don't you? Call dma_map_single()
if you need a dma address. The caller already guaranteed that the
input address is dma-safe.

...
> + }
> +
> + if (t->rx_buf) {
...
> + }

That RX bit sure looks wrong to me ... as if, given an spi_transfer
with both TX and RX buffers, it will execute them in sequence (half
duplex) rather than concurrently (full duplex). SPI is full duplex,
and this code should be too. If it can't implement that, then it
should be reporting an error if both rx and tx are requested, rather
than doing the wrong thing.


> +static int spi_pnx_probe(struct device *device)

That should probably be marked __init ..

> +{
> + struct spi_master *master;
> + struct spi_pnx_data *data;
> + struct spi_bitbang *pnx;
> + int rc = 0;
> +
> + printk("spi probe called\n");
> + master = spi_alloc_master(device, sizeof *data);
> + if (!master) {
> + rc = -ENOMEM;
> + goto out;
> + }
> + master->setup = &spi_pnx_setup;
> + master->bus_num = to_platform_device(device)->id;
> +
> + pnx = spi_master_get_devdata(master);
> + pnx->master = master;
> + pnx->chipselect = &spi_pnx_chipselect;
> + pnx->txrx_bufs = &spi_pnx_xfer;
> + rc = spi_bitbang_start(pnx);

You shouldn't start() it until it's fully set up, including
hardware being ready to go.


> + if (rc < 0)
> + goto out;
> +
> + data = kzalloc(sizeof *data, GFP_KERNEL);

You don't need that kzalloc, it was already done for you by virtue of
your telling spi_alloc_master() to do so. But then you saved that
memory into "pnx" not "data" (bug! wrong size!). What you should be
doing is nesting your bitbang structure inside the "data" thing.


> +static int spi_pnx_remove(struct device *device)

Likewise, mark this __exit. And I suspect you're using some kernel
older than 2.6.15-rc6 ... ;)

I notice your header file was mostly inline function declarations,
even for basic parts like clocking and DMA. That's best as part
of the driver itself, if it's not following some framework like
the <asm/hardware/clock.h> API on ARM.

- Dave



2005-12-27 20:21:47

by Vitaly Wool

[permalink] [raw]
Subject: Re: [PATCH] SPI: add support for PNX/SPI controller

David Brownell wrote:

>On Friday 23 December 2005 6:15 am, Vitaly Wool wrote:
>
>
>>(PNX0106 and PNX4008, namely) based on spi_bitbang library
>>
>>
>
>Good! Does need work yet though...
>
>
Yup...

>> # Add new SPI master controllers in alphabetical order above this line
>>
>>
>
>Note how it says "above", yet you've added yours ... below! :(
>
>
>
>
>> #
>>
>>+config SPI_PNX
>>+ tristate "PNX SPI bus support"
>>+ depends on ARCH_PNX4008 && SPI_BITBANG
>>
>>
>
>... also added the eeprom so it shows in the list of controller drivers!!
>
>
>
Oh sorry. Stupid me... Not that it matters really since this driver's
not yet mature enough anyway.

>
>
>>+config SPI_EEPROM
>>+ tristate "SPI EEPROM"
>>+ depends on SPI
>>+
>>
>>
>>
>
>
>
>>+#define lock_device( dev ) /* down( &dev->sem ); */
>>+#define unlock_device( dev ) /* up( &dev->sem ); */
>>
>>
>
>Not clear why you'd want such stuff ... better to just remove
>NOPs like that.
>
>
>
They caused problems on 2.6.10... I might have uncommented that but I
saw no reason. This driver is now is in FYI state :)

>
>
>
>>+static void
>>+spi_pnx_chipselect(struct spi_device *spi, int is_on)
>>+{
>>+ spipnx_arch_cs(spi, is_on);
>>+}
>>
>>
>
>I see that "arch" hook is actually just toggling a DMA line.
>Did you notice how Stephen's PXA code handled that? The
>"controller_data" was a board-specific GPIO toggle function,
>as provided in the board_info that implicitly defines the
>relevant device.
>
>Plus, activating the chip does more than just toggling some
>arch-specific GPIO. It has to set the right SPI mode, of
>which CPOL must be set _before_ chipselect goes active.
>And it has to set the transfer clock, for chips like yours
>which are multiplexing devices using software.
>
>
Yep, the current way of doing stuff is a sort of abuse to the driver's
model.
It's just "put #ifdefs out of the C dode" thing. We're gonna rewrite
that using controller_data but in our case that's gonna be a structure
with several functions and data needed. We have more arch-specific stuff
to care about than Stephen's PXA code, so we just didn't bother at the
moment; but anyway you're right.

>
>
>
>>+static int spi_pnx_xfer(struct spi_device *spidev, struct spi_transfer *t)
>>+{
>>
>>
>...
>
>
>>+ if (spidev->max_speed_hz)
>>+ spi_pnx_set_clock_rate(spidev->max_speed_hz / 1000, regs);
>>
>>
>
>Just do it the one time, when activating that chip's selection.
>
>
Nm really in our case, we have to reset the chip at each direction
change due to hardware bug.

>
>...
>
>
>>+ if (t->tx_buf) {
>>+ dat = (u8 *)t->tx_buf;
>>+ regs->con |= SPIPNX_CON_RXTX;
>>+ regs->ier |= SPIPNX_IER_EOT;
>>+ regs->con &= ~SPIPNX_CON_SHIFT_OFF;
>>+ regs->frm = len;
>>+
>>+ if (dd->dma_mode && len >= FIFO_CHUNK_SIZE) {
>>+ void *dmasafe = NULL;
>>+ if (t->tx_dma)
>>
>>
>
>That's not actually correct, since zero might be a legal DMA address.
>Unfortunately we have no DMA_ADDR_INVALID to test against, so this
>will need to suffice for now. (And I updated the spi_bitbang code
>to ensure that address _is_ zeroed when the message didn't set up
>the DMA addresses already, matching the default behavior.)
>
>
Hmm, DMA_ADDR_INVALID... (u_long)-1 ?

>
>
>
>>+ params.dma_buffer = t->tx_dma;
>>+ else {
>>+ dmasafe = kmalloc(len, SLAB_KERNEL);
>>+ if (!dmasafe) {
>>+ len = 0;
>>+ goto out;
>>+ }
>>+ params.dma_buffer = dma_map_single(dev->parent, dmasafe, len, DMA_TO_DEVICE);
>>+ memcpy(dmasafe, dat, len);
>>
>>
>
>You do know this is incorrect don't you? Call dma_map_single()
>if you need a dma address. The caller already guaranteed that the
>input address is dma-safe.
>
>
Well it didn't :) See eeprom.c.
It's not the way to go however, but it's nice to have ability to add DMA
safety in some controller drivers working with weird device drivers :/

>...
>
>
>>+ }
>>+
>>+ if (t->rx_buf) {
>>
>>
>...
>
>
>>+ }
>>
>>
>
>That RX bit sure looks wrong to me ... as if, given an spi_transfer
>with both TX and RX buffers, it will execute them in sequence (half
>duplex) rather than concurrently (full duplex). SPI is full duplex,
>and this code should be too. If it can't implement that, then it
>should be reporting an error if both rx and tx are requested, rather
>than doing the wrong thing.
>
>
This particular controller is full duplex but it has some hw problems
working in FD mode.
This is a workaround for FD which looks ok to me.

>
>
>>+ if (rc < 0)
>>+ goto out;
>>+
>>+ data = kzalloc(sizeof *data, GFP_KERNEL);
>>
>>
>
>You don't need that kzalloc, it was already done for you by virtue of
>your telling spi_alloc_master() to do so. But then you saved that
>memory into "pnx" not "data" (bug! wrong size!). What you should be
>doing is nesting your bitbang structure inside the "data" thing.
>
>
>
Agreed.

>Likewise, mark this __exit. And I suspect you're using some kernel
>older than 2.6.15-rc6 ... ;)
>
>
Definitely, it was quickly ported from 2.6.10.

>I notice your header file was mostly inline function declarations,
>even for basic parts like clocking and DMA. That's best as part
>of the driver itself, if it's not following some framework like
>the <asm/hardware/clock.h> API on ARM.
>
>
Well, these all platform-specific functions are gonna be in
controller_data some day... hopefully soon.

Vitaly