This is a refresh of my initial "SPI controller" implementation running on
David Brownell's "simple SPI framework". The controller should run on any
PXA2xx SSP port and has been tested on the PXA255 NSSP port. Complete
board setup and description facilities per the SPI framework are
supported.
Summary of changes:
- Added non-descriptor DMA support for aligned buffers.
- Modifications to support transfers from interrupt context.
- Addition of "loopback" demo "SPI protocol" driver.
David's SPI patch can be found at
http://marc.theaimsgroup.com/?l=linux-kernel&m=112844925715957&w=2
Your comments and suggestions encouraged! You can e-mail me directly if you
have any question regarding running SPI controller on your board.
Stephen Street
---snip---
This is a prototype dma/interrupt driven SPI "controller" for Intel's PXA2xx
series SOC. The driver plugs into the lightweight SPI framework developed by
David Brownell. Hardwired configuration information is provided via
spi_board_info structures initialized in arch/arm/mach_pxa board
initialization code (see include/linux/spi.h for details).
The driver is built around a spi_message fifo serviced by two tasklets. The
first tasklet (pump_messages) is responsible for queuing SPI transactions
and scheduling SPI transfers. The second tasklet (pump_transfers) is
responsible to setting up and launching the dma/interrupt driven transfers.
DMA transfers maybe enabled by setting the "enable_dma" flag in the
"pxa2xx_spi_master" structure. The driver will select DMA transfers if the
"spi_message" transfer buffers are aligned on a 8-byte boundary. Unaligned
buffers will be sent using interrupt driven PIO. The driver user "Streaming
DMA mappings" to program two PXA2xx dma channels aquired during the device
probing. Per transfer chip select and delay control is available.
This is a prototype driver, so you mileage will vary. It has only been
tested on the NSSP port.
Known Limitations:
Does not handle invert chip select polarity.
Heavy loaded systems may see transaction failures.
Wordsize support is untested.
Module hangs during unload.
drivers/spi/Kconfig | 11
drivers/spi/Makefile | 3
drivers/spi/pxa2xx_loopback.c | 403 +++++++++
drivers/spi/pxa2xx_spi_ssp.c | 1286 ++++++++++++++++++++++++++++++
include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h | 42
5 files changed, 1745 insertions(+)
--- linux-2.6.12-spi/drivers/spi/Kconfig 2005-10-25 16:17:54.101062361 -0700
+++ linux-2.6.12-spi-pxa/drivers/spi/Kconfig 2005-10-25 16:13:14.434103295 -0700
@@ -65,6 +65,12 @@
comment "SPI Master Controller Drivers"
+config SPI_PXA_SSP
+ tristate "PXA SSP controller as SPI master"
+ depends on ARCH_PXA
+ help
+ This implements SPI master mode using an SSP controller.
+
#
# Add new SPI master controllers in alphabetical order above this line
#
@@ -77,6 +83,11 @@
comment "SPI Protocol Masters"
+config SPI_PXA_LOOPBACK
+ tristate "PXA2xx Loopback device"
+ help
+ This is a test and development loopback driver.
+
#
# Add new SPI protocol masters in alphabetical order above this line
#
--- linux-2.6.12-spi/drivers/spi/Makefile 2005-10-25 16:17:54.123061965 -0700
+++ linux-2.6.12-spi-pxa/drivers/spi/Makefile 2005-10-25 16:13:44.128574038 -0700
@@ -11,9 +11,12 @@
obj-$(CONFIG_SPI_MASTER) += spi.o
# SPI master controller drivers (bus)
+obj-$(CONFIG_SPI_PXA_SSP) += pxa2xx_spi_ssp.o
# ... add above this line ...
# SPI protocol drivers (device/link on bus)
+obj-$(CONFIG_SPI_PXA_LOOPBACK) += pxa2xx_loopback.o
+
# ... add above this line ...
# SPI slave controller drivers (upstream link)
--- linux-2.6.12-spi/drivers/spi/pxa2xx_spi_ssp.c 1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_spi_ssp.c 2005-10-25 15:58:06.325046000 -0700
@@ -0,0 +1,1286 @@
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This is a prototype dma/interrupt driven SPI "controller" for Intel's PXA2xx
+ * series SOC. The driver plugs into the lightweight SPI framework developed by
+ * David Brownell. Hardwired configuration information is provided via
+ * spi_board_info structures initialized in arch/arm/mach_pxa board
+ * initialization code (see include/linux/spi.h for details).
+ *
+ * This follow code snippet demostrates a sample board configuration using
+ * the PXA255 NSSP port connect to a "loopback" pseudo-chip.
+ *
+ *
+ * static struct pxa2xx_spi_chip loopback_chip_info = {
+ * .mode = SPI_MODE_3,
+ * .tx_threshold = 12,
+ * .rx_threshold = 4,
+ * .dma_burst_size = 8,
+ * .bits_per_word = 8,
+ * .timeout_microsecs = 64,
+ * .enable_loopback = 1,
+ * };
+ *
+ * static struct spi_board_info board_info[] __initdata = {
+ * {
+ * .modalias = "loopback",
+ * .max_speed_hz = 3686400,
+ * .bus_num = 2,
+ * .chip_select = 0,
+ * .controller_data = &loopback_chip_info,
+ * },
+ * };
+ *
+ * static struct resource pxa_spi_resources[] = {
+ * [0] = {
+ * .start = __PREG(SSCR0_P(2)),
+ * .end = __PREG(SSCR0_P(2)) + 0x2c,
+ * .flags = IORESOURCE_MEM,
+ * },
+ * [1] = {
+ * .start = IRQ_NSSP,
+ * .end = IRQ_NSSP,
+ * .flags = IORESOURCE_IRQ,
+ * },
+ * };
+ *
+ * static struct pxa2xx_spi_master pxa_nssp_master_info = {
+ * .bus_num = 2,
+ * .clock_enable = CKEN9_NSSP,
+ * .num_chipselect = 3,
+ * .enable_dma = 1,
+ * };
+ *
+ * static struct platform_device pxa_spi_ssp = {
+ * .name = "pxa2xx-spi-ssp",
+ * .id = -1,
+ * .resource = pxa_spi_resources,
+ * .num_resources = ARRAY_SIZE(pxa_spi_resources),
+ * .dev = {
+ * .platform_data = &pxa_nssp_master_info,
+ * },
+ * };
+ *
+ * static void __init streetracer_init(void)
+ * {
+ * platform_device_register(&pxa_spi_ssp);
+ * spi_register_board_info(board_info, ARRAY_SIZE(board_info));
+ * }
+ *
+ * The driver is built around a spi_message fifo serviced by two tasklets. The
+ * first tasklet (pump_messages) is responsible for queuing SPI transactions
+ * and scheduling SPI transfers. The second tasklet (pump_transfers) is
+ * responsible to setting up and launching the dma/interrupt driven transfers.
+ * DMA transfers maybe enabled by setting the "enable_dma" flag in the
+ * "pxa2xx_spi_master" structure. The driver will select DMA transfers if the
+ * "spi_message" transfer buffers are aligned on a 8-byte boundary. Unaligned
+ * buffers will be sent using interrupt driven PIO. The driver user "Streaming
+ * DMA mappings" to program two PXA2xx dma channels aquired during the device
+ * probing. Per transfer chip select and delay control is available.
+ *
+ * This is a prototype driver, so you mileage will vary. It has only been
+ * tested on the NSSP port.
+ *
+ * Known Limitations:
+ * Does not handle invert chip select polarity.
+ * Heavy loaded systems may see transaction failures.
+ * Wordsize support is untested.
+ * Module hangs during unload.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/spi.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/hardware.h>
+#include <asm/delay.h>
+#include <asm/dma.h>
+
+#include <asm/arch/hardware.h>
+#include <asm/arch/pxa-regs.h>
+#include <asm/arch/pxa2xx_spi_ssp.h>
+
+MODULE_AUTHOR("Stephen Street");
+MODULE_DESCRIPTION("PXA2xx SSP SPI Contoller");
+MODULE_LICENSE("GPL");
+
+#define MAX_SPEED_HZ 3686400
+#define MAX_BUSES 3
+
+#define DMA_INT_MASK (DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR)
+#define RESET_DMA_CHANNEL (DCSR_NODESC | DMA_INT_MASK)
+
+#define IS_DMA_ALIGNED(x,y) ((((u32)(x)& 0x07)==0) && (((u32)(y)& 0x07)==0))
+#define GET_IRQ_STATUS(x) (__REG(x)&(SSSR_TINT|SSSR_RFS|SSSR_TFS|SSSR_ROR))
+
+struct master_data;
+
+struct transfer_state {
+ int index;
+ int len;
+ void *tx;
+ void *tx_end;
+ void *rx;
+ void *rx_end;
+ void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state);
+ void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state);
+ irqreturn_t (*transfer_handler)(struct master_data *drv_data,
+ struct spi_message *msg,
+ struct transfer_state *state);
+ void (*cs_control)(u32 command);
+};
+
+struct master_data {
+ spinlock_t lock;
+ struct spi_master *master;
+ int rx_channel;
+ int tx_channel;
+ struct list_head queue;
+ struct tasklet_struct pump_messages;
+ struct tasklet_struct pump_transfers;
+ struct spi_message* cur_msg;
+ struct transfer_state cur_state;
+ void *null_dma_buf;
+ u32 trans_cnt;
+ u32 dma_trans_cnt;
+ u32 int_trans_cnt;
+ u32 bigunalign_cnt;
+ u32 buserr_cnt;
+ u32 ror_cnt;
+ u32 sscr0;
+ u32 sscr1;
+ u32 sssr;
+ u32 ssitr;
+ u32 ssdr;
+ u32 ssto;
+ u32 sspsp;
+};
+
+struct chip_data {
+ u32 cr0;
+ u32 cr1;
+ u32 to;
+ u32 psp;
+ u32 timeout;
+ u8 n_bytes;
+ u32 dma_width;
+ u32 dma_burst_size;
+ u32 threshold;
+ u32 dma_threshold;
+ u8 enable_dma;
+ void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state);
+ void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state);
+ void (*cs_control)(u32 command);
+};
+
+static inline void flush(struct master_data *drv_data)
+{
+ u32 sssr = drv_data->sssr;
+ u32 ssdr = drv_data->ssdr;
+
+ do {
+ while (__REG(sssr) & SSSR_RNE) {
+ (void)__REG(ssdr);
+ }
+ } while (__REG(sssr) & SSSR_BSY);
+ __REG(sssr) = SSSR_ROR ;
+}
+
+static inline void save_state(struct master_data *drv_data,
+ struct chip_data *chip)
+{
+ /* Save critical register */
+ chip->cr0 = __REG(drv_data->sscr0);
+ chip->cr1 = __REG(drv_data->sscr1);
+ chip->to = __REG(drv_data->ssto);
+ chip->psp = __REG(drv_data->sspsp);
+
+ /* Disable clock */
+ __REG(drv_data->sscr0) &= ~SSCR0_SSE;
+}
+
+static inline void restore_state(struct master_data *drv_data,
+ struct chip_data *chip)
+{
+ /* Clear status and disable clock*/
+ __REG(drv_data->sssr) = SSSR_ROR | SSSR_TUR | SSSR_BCE;
+ __REG(drv_data->sscr0) = chip->cr0 & ~SSCR0_SSE;
+
+ /* Load the registers */
+ __REG(drv_data->sscr1) = chip->cr1;
+ __REG(drv_data->ssto) = chip->to;
+ __REG(drv_data->sspsp) = chip->psp;
+ __REG(drv_data->sscr0) = chip->cr0;
+}
+
+static inline void dump_state(struct master_data* drv_data)
+{
+ u32 sscr0 = drv_data->sscr0;
+ u32 sscr1 = drv_data->sscr1;
+ u32 sssr = drv_data->sssr;
+ u32 ssto = drv_data->ssto;
+ u32 sspsp = drv_data->sspsp;
+
+ printk("SSP dump: sscr0=0x%08x, sscr1=0x%08x, "
+ "ssto=0x%08x, sspsp=0x%08x, sssr=0x%08x\n",
+ __REG(sscr0), __REG(sscr1), __REG(ssto),
+ __REG(sspsp), __REG(sssr));
+}
+
+static inline void dump_message(struct spi_message *msg)
+{
+ int i;
+ struct device *dev = &msg->dev->dev;
+
+ dev_dbg(dev, "message = %p\n", msg);
+ dev_dbg(dev, " complete = %p\n", msg->complete);
+ dev_dbg(dev, " context = %p\n", msg->context);
+ dev_dbg(dev, " actual_length = %u\n", msg->actual_length);
+ dev_dbg(dev, " status = %d\n", msg->status);
+ dev_dbg(dev, " state = %p\n", msg->state);
+ dev_dbg(dev, " n_transfers = %u\n", msg->n_transfer);
+
+ for (i = 0; i < msg->n_transfer; i++) {
+ dev_dbg(dev, " %d, tx_buf = %p\n",
+ i, msg->transfers[i].tx_buf);
+ dev_dbg(dev, " %d, rx_buf = %p\n",
+ i, msg->transfers[i].rx_buf);
+ dev_dbg(dev, " %d, tx_dma = %p\n",
+ i, (void *)msg->transfers[i].tx_dma);
+ dev_dbg(dev, " %d, rx_dma = %p\n",
+ i, (void *)msg->transfers[i].rx_dma);
+ dev_dbg(dev, " %d, len = %u\n",
+ i, msg->transfers[i].len);
+ dev_dbg(dev, " %d, cs_change = %u\n",
+ i, msg->transfers[i].cs_change);
+ dev_dbg(dev, " %d, delay_usecs = %u\n",
+ i, msg->transfers[i].delay_usecs);
+ }
+}
+
+static void null_cs_control(u32 command)
+{
+}
+
+static void null_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
+ __REG(ssdr) = 0;
+ ++state->tx;
+ }
+}
+
+static void null_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
+ (void)(__REG(ssdr));
+ ++state->rx;
+ }
+}
+
+static void u8_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
+ __REG(ssdr) = *(u8 *)(state->tx);
+ ++state->tx;
+ }
+}
+
+static void u8_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
+ *(u8 *)(state->rx) = __REG(ssdr);
+ ++state->rx;
+ }
+}
+
+static void u16_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
+ __REG(ssdr) = *(u16 *)(state->tx);
+ state->tx += 2;
+ }
+}
+
+static void u16_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
+ *(u16 *)(state->rx) = __REG(ssdr);
+ state->rx += 2;
+ }
+}
+static void u32_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
+ __REG(ssdr) = *(u32 *)(state->tx);
+ state->tx += 4;
+ }
+}
+
+static void u32_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
+{
+ while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
+ *(u32 *)(state->rx) = __REG(ssdr);
+ state->rx += 4;
+ }
+}
+
+static void dma_handler(int channel, void *data, struct pt_regs *regs)
+{
+ struct master_data *drv_data = (struct master_data *)data;
+ u32 sssr = drv_data->sssr;
+ u32 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->sscr1;
+ u32 irq_status = DCSR(drv_data->tx_channel) & DMA_INT_MASK;
+ struct spi_message *msg = drv_data->cur_msg;
+ struct transfer_state *state = (struct transfer_state *)msg->context;
+
+ if (irq_status & DCSR_BUSERR) {
+
+ __REG(ssto) = 0;
+ __REG(sssr) = SSSR_TINT | SSSR_ROR;
+ __REG(sscr1) &= ~(SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE);
+ DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
+ DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
+
+ flush(drv_data);
+
+ printk(KERN_ERR "dma_handler: bad bus address on channel %d\n",
+ channel);
+
+ drv_data->buserr_cnt++;
+ drv_data->cur_state.index = -2;
+ tasklet_schedule(&drv_data->pump_transfers);
+ }
+
+ if (channel == drv_data->rx_channel && irq_status & DCSR_ENDINTR) {
+ /* Forward to transfer handler */
+ state->rx = state->rx_end;
+ state->transfer_handler(drv_data, msg, state);
+ }
+
+
+}
+
+static irqreturn_t dma_transfer(struct master_data *drv_data,
+ struct spi_message *msg,
+ struct transfer_state *state)
+{
+ u32 sssr = drv_data->sssr;
+ u32 ssdr = drv_data->ssdr;
+ u32 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->ssto;
+ u32 irq_status = GET_IRQ_STATUS(sssr);
+ u32 trailing_sssr = 0;
+
+ if (irq_status & SSSR_ROR) {
+ /* Clear and disable interrupts on SSP and DMA channels*/
+ __REG(ssto) = 0;
+ __REG(sssr) = SSSR_TINT | SSSR_ROR;
+ __REG(sscr1) &= ~(SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE);
+ DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
+ DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
+
+ flush(drv_data);
+
+ printk(KERN_WARNING "dma_transfer: fifo overun"
+ "tx dcmd = 0x%08x rx dcmd = 0x%08x\n",
+ DCMD(drv_data->tx_channel),
+ DCMD(drv_data->rx_channel));
+
+ drv_data->ror_cnt++;
+
+ state->index = -2;
+ tasklet_schedule(&drv_data->pump_transfers);
+
+ return IRQ_HANDLED;
+ }
+
+ if (irq_status & SSSR_TINT || state->rx == state->rx_end) {
+
+ /* Check for false positive */
+ if (DCSR(drv_data->tx_channel) & DCSR_RUN) {
+ __REG(sssr) = SSSR_TINT;
+ return IRQ_HANDLED;
+ }
+
+ /* Clear and disable interrupts on SSP and DMA channels*/
+ __REG(ssto) = 0;
+ __REG(sssr) = SSSR_TINT | SSSR_ROR ;
+ __REG(sscr1) &= ~(SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE);
+ DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
+ DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
+ while (!(DCSR(drv_data->rx_channel) & DCSR_STOPSTATE)
+ || (__REG(sssr) & SSSR_BSY));
+
+ /* Handle trailing byte, must unmap the dma buffers */
+ dma_unmap_single(&msg->dev->dev,
+ msg->transfers[state->index].tx_dma,
+ msg->transfers[state->index].len,
+ DMA_TO_DEVICE);
+ dma_unmap_single(&msg->dev->dev,
+ msg->transfers[state->index].rx_dma,
+ msg->transfers[state->index].len,
+ DMA_FROM_DEVICE);
+
+ /* Calculate number of trailing bytes */
+ trailing_sssr = __REG(sssr);
+ if ((trailing_sssr & 0xf008) != 0xf000) {
+ state->rx = state->rx_end -
+ (((trailing_sssr >> 12) & 0x0f) + 1);
+ state->read(sssr, ssdr, state);
+ }
+
+ if (msg->transfers[state->index].cs_change)
+ state->cs_control(PXA2XX_CS_DEASSERT);
+
+ /* Schedule transfer tasklet */
+ msg->actual_length += msg->transfers[state->index].len;
+ ++state->index;
+ tasklet_schedule(&drv_data->pump_transfers);
+
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t interrupt_transfer(struct master_data *drv_data,
+ struct spi_message *msg,
+ struct transfer_state *state)
+{
+ u32 sssr = drv_data->sssr;
+ u32 ssdr = drv_data->ssdr;
+ u32 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->ssto;
+ u32 irq_status;
+
+ while ((irq_status = GET_IRQ_STATUS(sssr))) {
+
+ if (irq_status & SSSR_ROR) {
+
+ /* Clear and disable interrupts */
+ __REG(ssto) = 0;
+ __REG(sssr) = SSSR_TINT | SSSR_ROR;
+ __REG(sscr1) &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
+
+ flush(drv_data);
+
+ printk(KERN_WARNING "interrupt_transfer: fifo overun, "
+ "index=%d tx_len=%d rx_len=%d\n",
+ state->index,
+ (state->tx_end - state->tx),
+ (state->rx_end - state->rx));
+
+ drv_data->ror_cnt++;
+ state->index = -2;
+ tasklet_schedule(&drv_data->pump_transfers);
+
+ return IRQ_HANDLED;
+ }
+
+ /* Look for false positive timeout */
+ if ((irq_status & SSSR_TINT) && (state->rx < state->rx_end))
+ __REG(sssr) = SSSR_TINT;
+
+ /* Pump data */
+ state->read(sssr, ssdr, state);
+ state->write(sssr, ssdr, state);
+
+ /* Disable tx interrupt enable rx timeout */
+ if (state->tx == state->tx_end) {
+ __REG(sscr1) &= ~SSCR1_TIE;
+ }
+
+ if ((irq_status & SSSR_TINT) || (state->rx == state->rx_end)) {
+
+ /* Clear timeout */
+ __REG(ssto) = 0;
+ __REG(sssr) = SSSR_TINT | SSSR_ROR ;
+ __REG(sscr1) &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
+
+ msg->actual_length += msg->transfers[state->index].len;
+
+ if (msg->transfers[state->index].cs_change)
+ state->cs_control(PXA2XX_CS_DEASSERT);
+
+ /* Schedule transfer tasklet */
+ ++state->index;
+ tasklet_schedule(&drv_data->pump_transfers);
+
+ return IRQ_HANDLED;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ssp_int(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct master_data *drv_data = (struct master_data *)dev_id;
+ struct transfer_state *state;
+ struct spi_message *msg;
+
+ if (!drv_data->cur_msg || !drv_data->cur_msg->state) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad message or transfer "
+ "state in interrupt handler\n");
+ return IRQ_NONE;
+ }
+ state = (struct transfer_state *)drv_data->cur_msg->state;
+ msg = drv_data->cur_msg;
+
+ return state->transfer_handler(drv_data, msg, state);
+}
+
+static void pump_dma_transfers(unsigned long data)
+{
+ struct master_data *drv_data = (struct master_data *)data;
+ struct spi_message *message = drv_data->cur_msg;
+ struct chip_data *chip;
+ struct transfer_state * state;
+ struct spi_transfer *transfer;
+ u32 sssr = drv_data->sssr;
+ u32 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->ssto;
+ u32 tx_dcd = DCMD_INCSRCADDR | DCMD_FLOWTRG;
+ u32 rx_dcd = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_ENDIRQEN;
+ u32 tx_map_len;
+ u32 rx_map_len;
+ unsigned long flags;
+
+ if (!message) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers "
+ "schedule\n");
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ state = (struct transfer_state *)message->state;
+ if (!state) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n");
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ chip = spi_get_ctldata(message->dev);
+ if (!chip) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n");
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ /* Handle for abort */
+ if (state->index == -2) {
+ message->status = -EIO;
+ if (message->complete) {
+ message->complete(message->context);
+ }
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ /* Handle end of message */
+ if (state->index == message->n_transfer) {
+ if (!message->transfers[state->index].cs_change)
+ state->cs_control(PXA2XX_CS_DEASSERT);
+
+ message->status = 0;
+ if (message->complete)
+ message->complete(message->context);
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ /* Handle start of message */
+ if (state->index == -1) {
+ restore_state(drv_data, chip);
+ flush(drv_data);
+ ++state->index;
+ }
+
+ /* Delay if requested at end of transfer*/
+ if (state->index > 1) {
+ transfer = message->transfers + (state->index - 1);
+ if (transfer->delay_usecs)
+ udelay(transfer->delay_usecs);
+ }
+
+ /* Setup the transfer state based on the type of transfer */
+ flush(drv_data);
+ drv_data->trans_cnt++;
+ transfer = message->transfers + state->index;
+ state->cs_control = !chip->cs_control ?
+ null_cs_control : chip->cs_control;
+ state->tx = (void *)transfer->tx_buf;
+ state->tx_end = state->tx + (transfer->len * chip->n_bytes);
+ state->rx = transfer->rx_buf;
+ state->rx_end = state->rx + (transfer->len * chip->n_bytes);
+ state->write = state->tx ? chip->write : null_writer;
+ state->read = state->rx ? chip->read : null_reader;
+
+ /* Make sure DMA is enable and buffer are aligned on 8-byte boundary */
+ if (chip->enable_dma && IS_DMA_ALIGNED(state->tx, state->rx)) {
+
+ /* Ensure we have the correct interrupt handler */
+ state->transfer_handler = dma_transfer;
+ drv_data->dma_trans_cnt++;
+
+ /* Use local DMA if no rx buf specified */
+ if (state->rx == NULL) {
+ *(u32 *)(drv_data->null_dma_buf) = 0;
+ state->rx = drv_data->null_dma_buf;
+ rx_map_len = 4;
+ rx_dcd &= ~DCMD_INCTRGADDR;
+ }
+ else
+ rx_map_len = transfer->len & DCMD_LENGTH;
+
+
+ /* Disable cache on rx buffer */
+ transfer->rx_dma =
+ dma_map_single(&message->dev->dev,
+ state->rx,
+ rx_map_len,
+ DMA_FROM_DEVICE);
+
+ if (dma_mapping_error(transfer->rx_dma))
+ goto out_mapping_error;
+
+ /* Setup rx DMA Channel */
+ DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
+ DSADR(drv_data->rx_channel) = drv_data->ssdr;
+ DTADR(drv_data->rx_channel) = transfer->rx_dma;
+ DCMD(drv_data->rx_channel) = rx_dcd
+ | chip->dma_width
+ | chip->dma_burst_size
+ | transfer->len;
+
+ /* Use local DMA if no tx buf specified */
+ if (transfer->tx_buf == NULL) {
+ *(u32 *)(drv_data->null_dma_buf) = 0;
+ state->tx = drv_data->null_dma_buf;
+ tx_map_len = 4;
+ tx_dcd &= ~DCMD_INCSRCADDR;
+ }
+ else
+ tx_map_len = transfer->len & DCMD_LENGTH;
+
+ /* Disable cache on tx buffer */
+ transfer->tx_dma =
+ dma_map_single(&message->dev->dev,
+ state->tx,
+ tx_map_len,
+ DMA_TO_DEVICE);
+
+ if (dma_mapping_error(transfer->tx_dma))
+ goto out_mapping_error;
+
+ /* Setup tx DMA Channel */
+ DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
+ DSADR(drv_data->tx_channel) = transfer->tx_dma;
+ DTADR(drv_data->tx_channel) = drv_data->ssdr;
+ DCMD(drv_data->tx_channel) = tx_dcd
+ | chip->dma_width
+ | chip->dma_burst_size
+ | transfer->len;
+
+ /* Fix me, need to handle cs polarity */
+ state->cs_control(PXA2XX_CS_ASSERT);
+
+ /* Go baby, go */
+ local_irq_save(flags);
+ __REG(sssr) = SSSR_TINT | SSSR_ROR;
+ DCSR(drv_data->rx_channel) |= DCSR_RUN;
+ DCSR(drv_data->tx_channel) |= DCSR_RUN;
+ __REG(ssto) = chip->timeout;
+ __REG(sscr1) = chip->cr1
+ | chip->dma_threshold
+ | SSCR1_TSRE
+ | SSCR1_RSRE
+ | SSCR1_TINTE;
+ local_irq_restore(flags);
+ }
+ else {
+ if (transfer->len > 16)
+ drv_data->bigunalign_cnt++;
+
+ /* Ensure we have the correct interrupt handler */
+ state->transfer_handler = interrupt_transfer;
+ drv_data->int_trans_cnt++;
+
+ /* Fix me, need to handle cs polarity */
+ state->cs_control(PXA2XX_CS_ASSERT);
+
+ /* Go baby, go */
+ local_irq_save(flags);
+ __REG(sssr) = SSSR_TINT | SSSR_ROR;
+ __REG(ssto) = chip->timeout;
+ __REG(sscr1) = chip->cr1
+ | chip->threshold
+ | SSCR1_TIE
+ | SSCR1_RIE
+ | SSCR1_TINTE;
+ local_irq_restore(flags);
+ }
+
+ return;
+
+out_mapping_error:
+ printk(KERN_ERR "problem mapping dma buf\n");
+ message->status = -EIO;
+ if (message->complete) {
+ message->complete(message->context);
+ }
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+}
+
+static void pump_int_transfers(unsigned long data)
+{
+ struct master_data *drv_data = (struct master_data *)data;
+ struct spi_message *message = drv_data->cur_msg;
+ struct chip_data *chip;
+ struct transfer_state * state;
+ struct spi_transfer *transfer;
+ u32 sssr = drv_data->sssr;
+ u32 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->ssto;
+ unsigned long flags;
+
+ if (!message) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers "
+ "schedule\n");
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ state = (struct transfer_state *)message->state;
+ if (!state) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n");
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ chip = spi_get_ctldata(message->dev);
+ if (!chip) {
+ printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n");
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ /* Handle for abort */
+ if (state->index == -2) {
+ message->status = -EIO;
+ if (message->complete) {
+ message->complete(message->context);
+ }
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ /* Handle end of message */
+ if (state->index == message->n_transfer) {
+ if (!message->transfers[state->index].cs_change)
+ state->cs_control(PXA2XX_CS_DEASSERT);
+
+ message->status = 0;
+ if (message->complete)
+ message->complete(message->context);
+ drv_data->cur_msg = NULL;
+ tasklet_schedule(&drv_data->pump_messages);
+ return;
+ }
+
+ /* Handle start of message */
+ if (state->index == -1) {
+ restore_state(drv_data, chip);
+ flush(drv_data);
+ ++state->index;
+ }
+
+ /* Delay if requested at end of transfer*/
+ if (state->index > 1) {
+ transfer = message->transfers + (state->index - 1);
+ if (transfer->delay_usecs)
+ udelay(transfer->delay_usecs);
+ }
+
+ /* Setup the transfer state based on the type of transfer */
+ flush(drv_data);
+ drv_data->trans_cnt++;
+ transfer = message->transfers + state->index;
+ state->cs_control = chip->cs_control;
+ state->tx = (void *)transfer->tx_buf;
+ state->tx_end = state->tx + (transfer->len * chip->n_bytes);
+ state->rx = transfer->rx_buf;
+ state->rx_end = state->rx + (transfer->len * chip->n_bytes);
+ state->write = state->tx ? chip->write : null_writer;
+ state->read = state->rx ? chip->read : null_reader;
+ state->transfer_handler = interrupt_transfer;
+ drv_data->int_trans_cnt++;
+
+ /* Fix me, need to handle cs polarity */
+ state->cs_control(PXA2XX_CS_ASSERT);
+
+ /* Go baby, go */
+ local_irq_save(flags);
+ __REG(sssr) = SSSR_TINT | SSSR_ROR;
+ __REG(ssto) = chip->timeout;
+ __REG(sscr1) = chip->cr1
+ | chip->threshold
+ | SSCR1_TIE
+ | SSCR1_RIE
+ | SSCR1_TINTE;
+ local_irq_restore(flags);
+}
+
+static void pump_messages(unsigned long data)
+{
+ struct master_data *drv_data = (struct master_data *)data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&drv_data->lock, flags);
+
+ /* Check for list empty */
+ if (list_empty(&drv_data->queue)) {
+ spin_unlock(&drv_data->lock);
+ return;
+ }
+
+ /* Check to see if we are already running */
+ if (drv_data->cur_msg) {
+ spin_unlock(&drv_data->lock);
+ return;
+ }
+
+ /* Extract head of queue and check for tasklet reschedule */
+ drv_data->cur_msg = list_entry(drv_data->queue.next,
+ struct spi_message, queue);
+ list_del_init(&drv_data->cur_msg->queue);
+
+ /* Setup message transfer and schedule transfer pump */
+ drv_data->cur_msg->state = &drv_data->cur_state;
+ drv_data->cur_state.index = -1;
+ drv_data->cur_state.len = 0;
+ tasklet_schedule(&drv_data->pump_transfers);
+
+ spin_unlock_irqrestore(&drv_data->lock, flags);
+}
+
+static int transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct master_data *drv_data = class_get_devdata(&spi->master->cdev);
+ unsigned long flags;
+
+ msg->actual_length = 0;
+ msg->status = 0;
+
+ spin_lock_irqsave(&drv_data->lock, flags);
+ list_add_tail(&msg->queue, &drv_data->queue);
+ spin_unlock_irqrestore(&drv_data->lock, flags);
+
+ tasklet_schedule(&drv_data->pump_messages);
+
+ return 0;
+}
+
+static int setup(struct spi_device *spi)
+{
+ struct pxa2xx_spi_chip *chip_info;
+ struct chip_data *chip;
+
+ chip_info = (struct pxa2xx_spi_chip *)spi->platform_data;
+
+ /* Only alloc on first setup */
+ chip = spi_get_ctldata(spi);
+ if (chip == NULL) {
+ chip = kcalloc(1, sizeof(struct chip_data), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ spi->mode = chip_info->mode;
+ spi->bits_per_word = chip_info->bits_per_word;
+ }
+
+ chip->cs_control = chip_info->cs_control;
+ chip->cr0 = SSCR0_SerClkDiv((MAX_SPEED_HZ / spi->max_speed_hz) + 2)
+ | SSCR0_Motorola
+ | SSCR0_DataSize(spi->bits_per_word)
+ | SSCR0_SSE
+ | (spi->bits_per_word > 16 ? SSCR0_EDSS : 0);
+ chip->cr1 = (((spi->mode & SPI_CPHA) != 0) << 4)
+ | (((spi->mode & SPI_CPOL) != 0) << 3)
+ | (chip_info->enable_loopback ? SSCR1_LBM : 0);
+ chip->to = 0;
+ chip->psp = 0;
+ chip->timeout = (chip_info->timeout_microsecs * 10000) / 2712;
+ chip->threshold = SSCR1_RxTresh(chip_info->rx_threshold)
+ | SSCR1_TxTresh(chip_info->tx_threshold);
+
+ chip->enable_dma = chip_info->dma_burst_size != 0;
+ if (chip_info->dma_burst_size <= 8) {
+ chip->dma_threshold = SSCR1_RxTresh(8) | SSCR1_TxTresh(8);
+ chip->dma_burst_size = DCMD_BURST8;
+ }
+ else if (chip_info->dma_burst_size <= 16) {
+ chip->dma_threshold = SSCR1_RxTresh(16) | SSCR1_TxTresh(16);
+ chip->dma_burst_size = DCMD_BURST16;
+ }
+ else {
+ chip->dma_threshold = SSCR1_RxTresh(32) | SSCR1_TxTresh(32);
+ chip->dma_burst_size = DCMD_BURST32;
+ }
+
+ if (spi->bits_per_word <= 8) {
+ chip->n_bytes = 1;
+ chip->dma_width = DCMD_WIDTH1;
+ chip->read = u8_reader;
+ chip->write = u8_writer;
+ }
+ else if (spi->bits_per_word <= 16) {
+ chip->n_bytes = 2;
+ chip->dma_width = DCMD_WIDTH2;
+ chip->read = u16_reader;
+ chip->write = u16_writer;
+ }
+ else if (spi->bits_per_word <= 32) {
+ chip->n_bytes = 4;
+ chip->dma_width = DCMD_WIDTH4;
+ chip->read = u32_reader;
+ chip->write = u32_writer;
+ }
+ else {
+ printk(KERN_ERR "pxa2xx_spi_ssp: invalid wordsize\n");
+ kfree(chip);
+ return -ENODEV;
+ }
+
+ spi_set_ctldata(spi, chip);
+
+ return 0;
+}
+
+static void cleanup(const struct spi_device *spi)
+{
+ struct chip_data *chip = spi_get_ctldata((struct spi_device *)spi);
+
+ if (chip)
+ kfree(chip);
+
+ dev_dbg(&spi->dev, "spi_device %u.%u cleanup\n",
+ spi->master->bus_num, spi->chip_select);
+}
+
+static ssize_t trans_cnt_show(struct device *dev, char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->trans_cnt);
+}
+DEVICE_ATTR(transfer_count, 0444, trans_cnt_show, NULL);
+
+static ssize_t dma_trans_cnt_show(struct device *dev, char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->dma_trans_cnt);
+}
+DEVICE_ATTR(dma_transfer_count, 0444, dma_trans_cnt_show, NULL);
+
+static ssize_t int_trans_cnt_show(struct device *dev, char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->int_trans_cnt);
+}
+DEVICE_ATTR(interrupt_transfer_count, 0444, int_trans_cnt_show, NULL);
+
+static ssize_t bigunalign_cnt_show(struct device *dev, char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->bigunalign_cnt);
+}
+DEVICE_ATTR(big_unaligned_count, 0444, bigunalign_cnt_show, NULL);
+
+static ssize_t buserr_cnt_show(struct device *dev, char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->buserr_cnt);
+}
+DEVICE_ATTR(bus_error_count, 0444, buserr_cnt_show, NULL);
+
+static ssize_t ror_cnt_show(struct device *dev, char *buf)
+{
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->ror_cnt);
+}
+DEVICE_ATTR(overrun_count, 0444, ror_cnt_show, NULL);
+
+static int probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pxa2xx_spi_master *platform_info;
+ struct spi_master *master;
+ struct master_data *drv_data = 0;
+ struct resource *memory_resource;
+ int irq;
+ int status = 0;
+
+ platform_info = (struct pxa2xx_spi_master *)pdev->dev.platform_data;
+
+ master = spi_alloc_master(dev, sizeof(struct master_data) + 16);
+ if (!master)
+ return -ENOMEM;
+ drv_data = class_get_devdata(&master->cdev);
+ drv_data->master = master;
+
+ INIT_LIST_HEAD(&drv_data->queue);
+ spin_lock_init(&drv_data->lock);
+
+ tasklet_init(&drv_data->pump_messages,
+ pump_messages,
+ (unsigned long)drv_data);
+
+ master->bus_num = platform_info->bus_num;
+ master->num_chipselect = platform_info->num_chipselect;
+ master->cleanup = cleanup;
+ master->setup = setup;
+ master->transfer = transfer;
+ drv_data->null_dma_buf = drv_data + sizeof(struct master_data);
+ drv_data->null_dma_buf = (void *)(((u32)(drv_data->null_dma_buf)
+ & 0xfffffff8) | 8 );
+ drv_data->trans_cnt = 0;
+ drv_data->dma_trans_cnt = 0;
+ drv_data->int_trans_cnt = 0;
+ drv_data->bigunalign_cnt = 0;
+ drv_data->buserr_cnt = 0;
+ drv_data->ror_cnt = 0;
+
+ /* Setup register addresses */
+ memory_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!memory_resource) {
+ dev_dbg(dev, "can not find platform io memory\n");
+ status = -ENODEV;
+ goto out_error_memory;
+ }
+
+ drv_data->sscr0 = memory_resource->start + 0x00000000;
+ drv_data->sscr1 = memory_resource->start + 0x00000004;
+ drv_data->sssr = memory_resource->start + 0x00000008;
+ drv_data->ssitr = memory_resource->start + 0x0000000c;
+ drv_data->ssdr = memory_resource->start + 0x00000010;
+ drv_data->ssto = memory_resource->start + 0x00000028;
+ drv_data->sspsp = memory_resource->start + 0x0000002c;
+
+ /* Attach to IRQ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq == 0) {
+ dev_dbg(dev, "problem getting IORESOURCE_IRQ[0]\n");
+ status = -ENODEV;
+ goto out_error_memory;
+ }
+
+ status = request_irq(irq, ssp_int, SA_INTERRUPT, dev->bus_id, drv_data);
+ if (status < 0) {
+ dev_dbg(dev, "problem requesting IORESOURCE_IRQ %u\n", irq);
+ goto out_error_memory;
+ }
+
+ /* Setup DMA is requested */
+ if (platform_info->enable_dma) {
+
+ tasklet_init(&drv_data->pump_transfers,
+ pump_dma_transfers,
+ (unsigned long)drv_data);
+
+ /* Get two DMA channels (rx and tx) */
+ drv_data->rx_channel = pxa_request_dma("pxa2xx_spi_ssp_rx",
+ DMA_PRIO_HIGH,
+ dma_handler,
+ drv_data);
+ if (drv_data->rx_channel < 0) {
+ dev_err(dev, "problem (%d) requesting rx channel\n",
+ drv_data->rx_channel);
+ status = -ENODEV;
+ goto out_error_irq;
+ }
+ DRCMRRXSS2DR = DRCMR_MAPVLD
+ | (drv_data->rx_channel & DRCMR_CHLNUM);
+
+ drv_data->tx_channel = pxa_request_dma("pxa2xx_spi_ssp_tx",
+ DMA_PRIO_MEDIUM,
+ dma_handler,
+ drv_data);
+ if (drv_data->tx_channel < 0) {
+ dev_err(dev, "problem (%d) requesting tx channel\n",
+ drv_data->tx_channel);
+ status = -ENODEV;
+ goto out_error_rx_dma;
+ }
+ DRCMRTXSS2DR = DRCMR_MAPVLD
+ | (drv_data->tx_channel & DRCMR_CHLNUM);
+ }
+ else
+ /* No DMA use interrupt transfers */
+ tasklet_init(&drv_data->pump_transfers,
+ pump_int_transfers,
+ (unsigned long)drv_data);
+
+ /* Enable SOC clock */
+ pxa_set_cken(platform_info->clock_enable, 1);
+
+ /* Load default SSP configuration */
+ __REG(drv_data->sscr0) = 0;
+ __REG(drv_data->sscr1) = SSCR1_RxTresh(4) | SSCR1_TxTresh(12);
+ __REG(drv_data->sscr0) = SSCR0_SerClkDiv(2)
+ | SSCR0_Motorola
+ | SSCR0_DataSize(8);
+ __REG(drv_data->ssto) = 0;
+ __REG(drv_data->sspsp) = 0;
+
+ status = device_create_file(dev, &dev_attr_transfer_count);
+ status += device_create_file(dev, &dev_attr_dma_transfer_count);
+ status += device_create_file(dev, &dev_attr_interrupt_transfer_count);
+ status += device_create_file(dev, &dev_attr_big_unaligned_count);
+ status += device_create_file(dev, &dev_attr_bus_error_count);
+ status += device_create_file(dev, &dev_attr_overrun_count);
+ if (status != 0) {
+ dev_err(dev, "problem creating attribute\n");
+ status = -EIO;
+ goto out_error_attr;
+ }
+
+
+ /* Register with the SPI framework */
+ dev_set_drvdata(dev, master);
+ status = spi_register_master(master);
+ if (status != 0) {
+ goto out_error_attr;
+ }
+
+ return status;
+
+out_error_attr:
+
+ device_remove_file(dev, &dev_attr_transfer_count);
+ device_remove_file(dev, &dev_attr_dma_transfer_count);
+ device_remove_file(dev, &dev_attr_interrupt_transfer_count);
+ device_remove_file(dev, &dev_attr_big_unaligned_count);
+ device_remove_file(dev, &dev_attr_bus_error_count);
+ device_remove_file(dev, &dev_attr_overrun_count);
+
+ pxa_free_dma(drv_data->tx_channel);
+
+out_error_rx_dma:
+ pxa_free_dma(drv_data->rx_channel);
+
+out_error_irq:
+ free_irq(irq, drv_data);
+
+out_error_memory:
+
+ return status;
+}
+
+static int remove(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct spi_master *master = dev_get_drvdata(dev);
+ struct master_data *drv_data = class_get_devdata(&master->cdev);
+ struct pxa2xx_spi_master *platform_info;
+
+ int irq;
+ unsigned long flags;
+
+ device_remove_file(dev, &dev_attr_transfer_count);
+ device_remove_file(dev, &dev_attr_dma_transfer_count);
+ device_remove_file(dev, &dev_attr_interrupt_transfer_count);
+ device_remove_file(dev, &dev_attr_big_unaligned_count);
+ device_remove_file(dev, &dev_attr_bus_error_count);
+ device_remove_file(dev, &dev_attr_overrun_count);
+
+ platform_info = (struct pxa2xx_spi_master *)pdev->dev.platform_data;
+
+ spin_lock_irqsave(&drv_data->lock, flags);
+
+ __REG(drv_data->sscr0) = 0;
+ pxa_set_cken(platform_info->clock_enable, 0);
+
+ DRCMRRXSS2DR = 0;
+ DRCMRTXSS2DR = 0;
+ pxa_free_dma(drv_data->tx_channel);
+ pxa_free_dma(drv_data->rx_channel);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq != 0)
+ free_irq(irq, drv_data);
+
+ spin_unlock_irqrestore(&drv_data->lock, flags);
+
+ spi_unregister_master(master);
+
+ return 0;
+}
+
+static struct device_driver driver = {
+ .name = "pxa2xx-spi-ssp",
+ .bus = &platform_bus_type,
+ .owner = THIS_MODULE,
+ .probe = probe,
+ .remove = remove,
+};
+
+static int pxa2xx_spi_ssp_init(void)
+{
+ driver_register(&driver);
+
+ return 0;
+}
+module_init(pxa2xx_spi_ssp_init);
+
+static void pxa2xx_spi_ssp_exit(void)
+{
+ driver_unregister(&driver);
+}
+module_exit(pxa2xx_spi_ssp_exit);
--- linux-2.6.12-spi/drivers/spi/pxa2xx_loopback.c 1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_loopback.c 2005-10-25 15:58:06.325046000 -0700
@@ -0,0 +1,403 @@
+/*
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/spi.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/kfifo.h>
+
+#include <asm/semaphore.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Stephen Street");
+MODULE_DESCRIPTION("PXA Loopback SPI Protocol Driver");
+MODULE_LICENSE("GPL");
+
+#define BUF_SIZE (1024)
+
+struct device_data {
+ char name[BUS_ID_SIZE];
+ struct miscdevice misc_device;
+ struct semaphore driver_lock;
+ struct list_head device_entry;
+ atomic_t busy;
+ spinlock_t read_lock;
+ spinlock_t write_lock;
+ struct kfifo *read_fifo;
+ struct kfifo *write_fifo;
+ wait_queue_head_t read_available;
+ wait_queue_head_t write_available;
+ struct spi_message msg;
+ struct spi_transfer transfer;
+ void *dma_buf;
+};
+
+static void post(void *context);
+
+static LIST_HEAD(device_list);
+static DECLARE_MUTEX(device_list_lock);
+
+static inline struct device_data *find_dev_data(int minor)
+{
+ struct list_head *ptr;
+ struct device_data *entry;
+
+ down(&device_list_lock);
+
+ list_for_each(ptr, &device_list) {
+ entry = list_entry(ptr, struct device_data, device_entry);
+ if (minor == entry->misc_device.minor) {
+ up(&device_list_lock);
+ return entry;
+ }
+ }
+
+ up(&device_list_lock);
+ return NULL;
+}
+
+static inline void add_device_data(struct device_data *dev_data)
+{
+ down(&device_list_lock);
+
+ list_add(&dev_data->device_entry, &device_list);
+
+ up(&device_list_lock);
+}
+
+static inline void remove_device_data(struct device_data *dev_data)
+{
+ down(&device_list_lock);
+
+ list_del(&dev_data->device_entry);
+
+ up(&device_list_lock);
+}
+
+static void kick(struct device_data *dev_data)
+{
+ unsigned int size;
+ int status = 0;
+
+ /* Make sure we are not busy */
+ if (!atomic_inc_and_test(&dev_data->busy)) {
+ atomic_dec(&dev_data->busy);
+ return;
+ }
+
+ /* Check for available data */
+ size = kfifo_len(dev_data->write_fifo);
+ if (!size) {
+ atomic_dec(&dev_data->busy);
+ return;
+ }
+
+ /* Read the data to send */
+ if (kfifo_get(dev_data->write_fifo, dev_data->dma_buf, size) != size) {
+ dev_err(dev_data->misc_device.dev, "write fifo failure\n");
+ atomic_dec(&dev_data->busy);
+ return;
+ }
+
+ /* Send async spi message */
+ memset(&dev_data->msg, 0, sizeof(struct spi_message));
+ memset(&dev_data->transfer, 0, sizeof(struct spi_transfer));
+ dev_data->transfer.tx_buf = dev_data->dma_buf;
+ dev_data->transfer.rx_buf = dev_data->dma_buf;
+ dev_data->transfer.len = size;
+ dev_data->msg.transfers = &dev_data->transfer;
+ dev_data->msg.n_transfer = 1;
+ dev_data->msg.complete = post;
+ dev_data->msg.context = dev_data;
+ status = spi_async(to_spi_device(dev_data->misc_device.dev),
+ &dev_data->msg);
+ if (status < 0) {
+ dev_err(dev_data->misc_device.dev,
+ "spi failure (%d)\n",
+ status);
+ atomic_dec(&dev_data->busy);
+ }
+
+ wake_up_interruptible(&dev_data->write_available);
+}
+
+static void post(void *context)
+{
+ struct device_data *dev_data = (struct device_data *)context;
+ unsigned int space;
+ unsigned length;
+ unsigned char local_buf[BUF_SIZE];
+
+ /* Check spi status */
+ if (dev_data->msg.status == 0) {
+
+ /* Make room for new data */
+ length = dev_data->msg.actual_length;
+ space = BUF_SIZE - kfifo_len(dev_data->read_fifo);
+ if (space < length)
+ kfifo_get(dev_data->read_fifo,
+ local_buf,
+ length - space);
+
+ /* Add the new data */
+ if (kfifo_put(dev_data->read_fifo, dev_data->dma_buf, length)
+ != length) {
+ dev_err(dev_data->misc_device.dev,
+ "read fifo post failure\n");
+ }
+
+ /* Let readers know */
+ wake_up_interruptible(&dev_data->read_available);
+ }
+ else
+ dev_err(dev_data->misc_device.dev,
+ "spi failure (%d)\n",
+ dev_data->msg.status);
+
+ atomic_dec(&dev_data->busy);
+ kick(dev_data);
+}
+
+static ssize_t read(struct file *filp,
+ char __user *buf,
+ size_t count,
+ loff_t *f_pos)
+{
+ struct device_data *dev_data = (struct device_data *)filp->private_data;
+ char local_buf[BUF_SIZE];
+ size_t read_size;
+
+ if (down_interruptible(&dev_data->driver_lock))
+ return -ERESTARTSYS;
+
+ while (kfifo_len(dev_data->read_fifo) == 0) {
+ up(&dev_data->driver_lock);
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(dev_data->read_available,
+ (kfifo_len(dev_data->read_fifo) != 0)))
+ return -ERESTARTSYS;
+ if (down_interruptible(&dev_data->driver_lock))
+ return -ERESTARTSYS;
+ }
+
+ read_size = min(count, (size_t)BUF_SIZE);
+ read_size = min(read_size, kfifo_len(dev_data->read_fifo));
+
+ if (kfifo_get(dev_data->read_fifo, local_buf, read_size) != read_size) {
+ dev_err(dev_data->misc_device.dev, "read fifo failure");
+ up(&dev_data->driver_lock);
+ return -EIO;
+ }
+
+ if (copy_to_user(buf, local_buf, read_size)) {
+ dev_err(dev_data->misc_device.dev, "user space failure");
+ up(&dev_data->driver_lock);
+ return -EFAULT;
+ }
+
+ up(&dev_data->driver_lock);
+
+ return read_size;
+}
+
+static ssize_t write(struct file *filp,
+ const char __user *buf,
+ size_t count,
+ loff_t *f_pos)
+{
+ struct device_data *dev_data = (struct device_data *)filp->private_data;
+ char local_buf[BUF_SIZE];
+ size_t write_size;
+
+ if (down_interruptible(&dev_data->driver_lock))
+ return -ERESTARTSYS;
+
+ while (kfifo_len(dev_data->write_fifo) == BUF_SIZE) {
+ up(&dev_data->driver_lock);
+ if (filp->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ if (wait_event_interruptible(dev_data->read_available,
+ (kfifo_len(dev_data->write_fifo) < BUF_SIZE)))
+ return -ERESTARTSYS;
+ if (down_interruptible(&dev_data->driver_lock))
+ return -ERESTARTSYS;
+ }
+
+ write_size = min(count, (size_t)BUF_SIZE);
+ write_size = min(write_size,
+ BUF_SIZE - kfifo_len(dev_data->write_fifo));
+
+ if (copy_from_user(local_buf, buf, write_size)) {
+ dev_err(dev_data->misc_device.dev, "user space failure");
+ up(&dev_data->driver_lock);
+ return -EFAULT;
+ }
+
+ if (kfifo_put(dev_data->write_fifo, local_buf, write_size)
+ != write_size) {
+ dev_err(dev_data->misc_device.dev, "write fifo failure");
+ up(&dev_data->driver_lock);
+ return -EIO;
+ }
+
+ kick(dev_data);
+
+ up(&dev_data->driver_lock);
+
+ return write_size;
+}
+
+static int open(struct inode *inode, struct file *filp)
+{
+ struct device_data *dev_data;
+
+ dev_data = find_dev_data(iminor(inode));
+ if (dev_data == NULL) {
+ printk(KERN_ERR "no device data for minor %d\n",
+ iminor(inode));
+ return ENODEV;
+ }
+ filp->private_data = dev_data;
+
+ return 0;
+}
+
+static struct file_operations loopback_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = write,
+ .read = read,
+ .open = open,
+};
+
+static int probe(struct device *dev)
+{
+ struct device_data *dev_data;
+ struct spi_device *sdev;
+ int status;
+
+ /* Allocate driver data */
+ dev_data = kcalloc(1, sizeof(struct device_data) + BUF_SIZE + 16,
+ GFP_KERNEL);
+ if (!dev_data) {
+ dev_err(dev, "problem allocating driver memory\n");
+ status = -ENOMEM;
+ goto out_error;
+ }
+
+ /* Allocated input/output fifos */
+ dev_data->read_fifo = kfifo_alloc(BUF_SIZE,
+ GFP_KERNEL,
+ &dev_data->read_lock);
+ if (IS_ERR(dev_data->read_fifo)) {
+ status = -ENOMEM;
+ goto out_memalloc;
+ }
+
+ dev_data->write_fifo = kfifo_alloc(BUF_SIZE,
+ GFP_KERNEL,
+ &dev_data->write_lock);
+ if (IS_ERR(dev_data->write_fifo)) {
+ status = -ENOMEM;
+ goto out_readalloc;
+ }
+
+ /* Initialize the driver data */
+ init_MUTEX(&dev_data->driver_lock);
+ spin_lock_init(&dev_data->read_lock);
+ spin_lock_init(&dev_data->write_lock);
+ atomic_set(&dev_data->busy, -1);
+ init_waitqueue_head(&dev_data->read_available);
+ init_waitqueue_head(&dev_data->write_available);
+ dev_data->dma_buf = dev_data + sizeof(struct device_data);
+ dev_data->dma_buf = (void *)(((u32)(dev_data->dma_buf)&0xfffffff8)|8);
+ INIT_LIST_HEAD(&dev_data->device_entry);
+ dev_set_drvdata(dev, dev_data);
+
+ /* Build misc device name from bus number and chip select */
+ sdev = to_spi_device(dev);
+ snprintf(dev_data->name, BUS_ID_SIZE, "slp%u%u",
+ sdev->master->bus_num,
+ sdev->chip_select);
+
+ /* Register as a misc device with dynamic minor number */
+ dev_data->misc_device.name = dev_data->name;
+ dev_data->misc_device.dev = dev;
+ dev_data->misc_device.fops = &loopback_fops;
+ dev_data->misc_device.minor = MISC_DYNAMIC_MINOR;
+ status = misc_register(&dev_data->misc_device);
+ if (status != 0) {
+ dev_err(dev, "problem (%d) registering misc device\n", status);
+ goto out_writealloc;
+ }
+
+ /* Add device data to internal device list */
+ add_device_data(dev_data);
+
+ return 0;
+
+out_writealloc:
+ kfifo_free(dev_data->write_fifo);
+
+out_readalloc:
+ kfifo_free(dev_data->read_fifo);
+
+out_memalloc:
+ kfree(dev_data);
+
+out_error:
+ return status;
+}
+
+static int remove(struct device *dev)
+{
+ struct device_data *dev_data = dev_get_drvdata(dev);
+
+ misc_deregister(&dev_data->misc_device);
+ remove_device_data(dev_data);
+ kfree(dev_data);
+
+ return 0;
+}
+
+struct device_driver loopback_spi = {
+ .name = "loopback",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .probe = probe,
+ .remove = remove,
+};
+
+static int __init loopback_init(void)
+{
+ return driver_register(&loopback_spi);
+}
+module_init(loopback_init);
+
+static void __exit loopback_exit(void)
+{
+ driver_unregister(&loopback_spi);
+}
+module_exit(loopback_exit);
--- linux-2.6.12-spi/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h 1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.12-spi-pxa/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h 2005-10-25 15:58:06.000000000 -0700
@@ -0,0 +1,42 @@
+/* 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef PXA2XX_SPI_SSP_H_
+#define PXA2XX_SPI_SSP_H_
+
+#define PXA2XX_CS_ASSERT (0x01)
+#define PXA2XX_CS_DEASSERT (0x02)
+
+struct pxa2xx_spi_master {
+ u16 bus_num;
+ u32 clock_enable;
+ u16 num_chipselect;
+ u8 enable_dma;
+};
+
+struct pxa2xx_spi_chip {
+ u8 mode;
+ u8 tx_threshold;
+ u8 rx_threshold;
+ u8 bits_per_word;
+ u8 dma_burst_size;
+ u32 timeout_microsecs;
+ u8 enable_loopback;
+ void (*cs_control)(u32 command);
+};
+
+#endif /*PXA2XX_SPI_SSP_H_*/
Dear Stepen
I am now writing another controller driver by david's framework.
But leak of debuging layer, could your loopback driver serve for this
purpose and how could i use it?
Mike,Lee
On 10/26/05, [email protected] <[email protected]> wrote:
> This is a refresh of my initial "SPI controller" implementation running on
> David Brownell's "simple SPI framework". The controller should run on any
> PXA2xx SSP port and has been tested on the PXA255 NSSP port. Complete
> board setup and description facilities per the SPI framework are
> supported.
>
> Summary of changes:
> - Added non-descriptor DMA support for aligned buffers.
> - Modifications to support transfers from interrupt context.
> - Addition of "loopback" demo "SPI protocol" driver.
>
> David's SPI patch can be found at
>
> http://marc.theaimsgroup.com/?l=linux-kernel&m=112844925715957&w=2
>
> Your comments and suggestions encouraged! You can e-mail me directly if you
> have any question regarding running SPI controller on your board.
>
> Stephen Street
>
> ---snip---
>
> This is a prototype dma/interrupt driven SPI "controller" for Intel's PXA2xx
> series SOC. The driver plugs into the lightweight SPI framework developed by
> David Brownell. Hardwired configuration information is provided via
> spi_board_info structures initialized in arch/arm/mach_pxa board
> initialization code (see include/linux/spi.h for details).
>
> The driver is built around a spi_message fifo serviced by two tasklets. The
> first tasklet (pump_messages) is responsible for queuing SPI transactions
> and scheduling SPI transfers. The second tasklet (pump_transfers) is
> responsible to setting up and launching the dma/interrupt driven transfers.
> DMA transfers maybe enabled by setting the "enable_dma" flag in the
> "pxa2xx_spi_master" structure. The driver will select DMA transfers if the
> "spi_message" transfer buffers are aligned on a 8-byte boundary. Unaligned
> buffers will be sent using interrupt driven PIO. The driver user "Streaming
> DMA mappings" to program two PXA2xx dma channels aquired during the device
> probing. Per transfer chip select and delay control is available.
>
> This is a prototype driver, so you mileage will vary. It has only been
> tested on the NSSP port.
>
> Known Limitations:
> Does not handle invert chip select polarity.
> Heavy loaded systems may see transaction failures.
> Wordsize support is untested.
> Module hangs during unload.
>
> drivers/spi/Kconfig | 11
> drivers/spi/Makefile | 3
> drivers/spi/pxa2xx_loopback.c | 403 +++++++++
> drivers/spi/pxa2xx_spi_ssp.c | 1286 ++++++++++++++++++++++++++++++
> include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h | 42
> 5 files changed, 1745 insertions(+)
>
> --- linux-2.6.12-spi/drivers/spi/Kconfig 2005-10-25 16:17:54.101062361 -0700
> +++ linux-2.6.12-spi-pxa/drivers/spi/Kconfig 2005-10-25 16:13:14.434103295 -0700
> @@ -65,6 +65,12 @@
> comment "SPI Master Controller Drivers"
>
>
> +config SPI_PXA_SSP
> + tristate "PXA SSP controller as SPI master"
> + depends on ARCH_PXA
> + help
> + This implements SPI master mode using an SSP controller.
> +
> #
> # Add new SPI master controllers in alphabetical order above this line
> #
> @@ -77,6 +83,11 @@
> comment "SPI Protocol Masters"
>
>
> +config SPI_PXA_LOOPBACK
> + tristate "PXA2xx Loopback device"
> + help
> + This is a test and development loopback driver.
> +
> #
> # Add new SPI protocol masters in alphabetical order above this line
> #
> --- linux-2.6.12-spi/drivers/spi/Makefile 2005-10-25 16:17:54.123061965 -0700
> +++ linux-2.6.12-spi-pxa/drivers/spi/Makefile 2005-10-25 16:13:44.128574038 -0700
> @@ -11,9 +11,12 @@
> obj-$(CONFIG_SPI_MASTER) += spi.o
>
> # SPI master controller drivers (bus)
> +obj-$(CONFIG_SPI_PXA_SSP) += pxa2xx_spi_ssp.o
> # ... add above this line ...
>
> # SPI protocol drivers (device/link on bus)
> +obj-$(CONFIG_SPI_PXA_LOOPBACK) += pxa2xx_loopback.o
> +
> # ... add above this line ...
>
> # SPI slave controller drivers (upstream link)
> --- linux-2.6.12-spi/drivers/spi/pxa2xx_spi_ssp.c 1969-12-31 16:00:00.000000000 -0800
> +++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_spi_ssp.c 2005-10-25 15:58:06.325046000 -0700
> @@ -0,0 +1,1286 @@
> +/*
> + * 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +/*
> + * This is a prototype dma/interrupt driven SPI "controller" for Intel's PXA2xx
> + * series SOC. The driver plugs into the lightweight SPI framework developed by
> + * David Brownell. Hardwired configuration information is provided via
> + * spi_board_info structures initialized in arch/arm/mach_pxa board
> + * initialization code (see include/linux/spi.h for details).
> + *
> + * This follow code snippet demostrates a sample board configuration using
> + * the PXA255 NSSP port connect to a "loopback" pseudo-chip.
> + *
> + *
> + * static struct pxa2xx_spi_chip loopback_chip_info = {
> + * .mode = SPI_MODE_3,
> + * .tx_threshold = 12,
> + * .rx_threshold = 4,
> + * .dma_burst_size = 8,
> + * .bits_per_word = 8,
> + * .timeout_microsecs = 64,
> + * .enable_loopback = 1,
> + * };
> + *
> + * static struct spi_board_info board_info[] __initdata = {
> + * {
> + * .modalias = "loopback",
> + * .max_speed_hz = 3686400,
> + * .bus_num = 2,
> + * .chip_select = 0,
> + * .controller_data = &loopback_chip_info,
> + * },
> + * };
> + *
> + * static struct resource pxa_spi_resources[] = {
> + * [0] = {
> + * .start = __PREG(SSCR0_P(2)),
> + * .end = __PREG(SSCR0_P(2)) + 0x2c,
> + * .flags = IORESOURCE_MEM,
> + * },
> + * [1] = {
> + * .start = IRQ_NSSP,
> + * .end = IRQ_NSSP,
> + * .flags = IORESOURCE_IRQ,
> + * },
> + * };
> + *
> + * static struct pxa2xx_spi_master pxa_nssp_master_info = {
> + * .bus_num = 2,
> + * .clock_enable = CKEN9_NSSP,
> + * .num_chipselect = 3,
> + * .enable_dma = 1,
> + * };
> + *
> + * static struct platform_device pxa_spi_ssp = {
> + * .name = "pxa2xx-spi-ssp",
> + * .id = -1,
> + * .resource = pxa_spi_resources,
> + * .num_resources = ARRAY_SIZE(pxa_spi_resources),
> + * .dev = {
> + * .platform_data = &pxa_nssp_master_info,
> + * },
> + * };
> + *
> + * static void __init streetracer_init(void)
> + * {
> + * platform_device_register(&pxa_spi_ssp);
> + * spi_register_board_info(board_info, ARRAY_SIZE(board_info));
> + * }
> + *
> + * The driver is built around a spi_message fifo serviced by two tasklets. The
> + * first tasklet (pump_messages) is responsible for queuing SPI transactions
> + * and scheduling SPI transfers. The second tasklet (pump_transfers) is
> + * responsible to setting up and launching the dma/interrupt driven transfers.
> + * DMA transfers maybe enabled by setting the "enable_dma" flag in the
> + * "pxa2xx_spi_master" structure. The driver will select DMA transfers if the
> + * "spi_message" transfer buffers are aligned on a 8-byte boundary. Unaligned
> + * buffers will be sent using interrupt driven PIO. The driver user "Streaming
> + * DMA mappings" to program two PXA2xx dma channels aquired during the device
> + * probing. Per transfer chip select and delay control is available.
> + *
> + * This is a prototype driver, so you mileage will vary. It has only been
> + * tested on the NSSP port.
> + *
> + * Known Limitations:
> + * Does not handle invert chip select polarity.
> + * Heavy loaded systems may see transaction failures.
> + * Wordsize support is untested.
> + * Module hangs during unload.
> + *
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/spi.h>
> +#include <linux/ioport.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/dma-mapping.h>
> +
> +#include <asm/io.h>
> +#include <asm/irq.h>
> +#include <asm/hardware.h>
> +#include <asm/delay.h>
> +#include <asm/dma.h>
> +
> +#include <asm/arch/hardware.h>
> +#include <asm/arch/pxa-regs.h>
> +#include <asm/arch/pxa2xx_spi_ssp.h>
> +
> +MODULE_AUTHOR("Stephen Street");
> +MODULE_DESCRIPTION("PXA2xx SSP SPI Contoller");
> +MODULE_LICENSE("GPL");
> +
> +#define MAX_SPEED_HZ 3686400
> +#define MAX_BUSES 3
> +
> +#define DMA_INT_MASK (DCSR_ENDINTR | DCSR_STARTINTR | DCSR_BUSERR)
> +#define RESET_DMA_CHANNEL (DCSR_NODESC | DMA_INT_MASK)
> +
> +#define IS_DMA_ALIGNED(x,y) ((((u32)(x)& 0x07)==0) && (((u32)(y)& 0x07)==0))
> +#define GET_IRQ_STATUS(x) (__REG(x)&(SSSR_TINT|SSSR_RFS|SSSR_TFS|SSSR_ROR))
> +
> +struct master_data;
> +
> +struct transfer_state {
> + int index;
> + int len;
> + void *tx;
> + void *tx_end;
> + void *rx;
> + void *rx_end;
> + void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state);
> + void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state);
> + irqreturn_t (*transfer_handler)(struct master_data *drv_data,
> + struct spi_message *msg,
> + struct transfer_state *state);
> + void (*cs_control)(u32 command);
> +};
> +
> +struct master_data {
> + spinlock_t lock;
> + struct spi_master *master;
> + int rx_channel;
> + int tx_channel;
> + struct list_head queue;
> + struct tasklet_struct pump_messages;
> + struct tasklet_struct pump_transfers;
> + struct spi_message* cur_msg;
> + struct transfer_state cur_state;
> + void *null_dma_buf;
> + u32 trans_cnt;
> + u32 dma_trans_cnt;
> + u32 int_trans_cnt;
> + u32 bigunalign_cnt;
> + u32 buserr_cnt;
> + u32 ror_cnt;
> + u32 sscr0;
> + u32 sscr1;
> + u32 sssr;
> + u32 ssitr;
> + u32 ssdr;
> + u32 ssto;
> + u32 sspsp;
> +};
> +
> +struct chip_data {
> + u32 cr0;
> + u32 cr1;
> + u32 to;
> + u32 psp;
> + u32 timeout;
> + u8 n_bytes;
> + u32 dma_width;
> + u32 dma_burst_size;
> + u32 threshold;
> + u32 dma_threshold;
> + u8 enable_dma;
> + void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state);
> + void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state);
> + void (*cs_control)(u32 command);
> +};
> +
> +static inline void flush(struct master_data *drv_data)
> +{
> + u32 sssr = drv_data->sssr;
> + u32 ssdr = drv_data->ssdr;
> +
> + do {
> + while (__REG(sssr) & SSSR_RNE) {
> + (void)__REG(ssdr);
> + }
> + } while (__REG(sssr) & SSSR_BSY);
> + __REG(sssr) = SSSR_ROR ;
> +}
> +
> +static inline void save_state(struct master_data *drv_data,
> + struct chip_data *chip)
> +{
> + /* Save critical register */
> + chip->cr0 = __REG(drv_data->sscr0);
> + chip->cr1 = __REG(drv_data->sscr1);
> + chip->to = __REG(drv_data->ssto);
> + chip->psp = __REG(drv_data->sspsp);
> +
> + /* Disable clock */
> + __REG(drv_data->sscr0) &= ~SSCR0_SSE;
> +}
> +
> +static inline void restore_state(struct master_data *drv_data,
> + struct chip_data *chip)
> +{
> + /* Clear status and disable clock*/
> + __REG(drv_data->sssr) = SSSR_ROR | SSSR_TUR | SSSR_BCE;
> + __REG(drv_data->sscr0) = chip->cr0 & ~SSCR0_SSE;
> +
> + /* Load the registers */
> + __REG(drv_data->sscr1) = chip->cr1;
> + __REG(drv_data->ssto) = chip->to;
> + __REG(drv_data->sspsp) = chip->psp;
> + __REG(drv_data->sscr0) = chip->cr0;
> +}
> +
> +static inline void dump_state(struct master_data* drv_data)
> +{
> + u32 sscr0 = drv_data->sscr0;
> + u32 sscr1 = drv_data->sscr1;
> + u32 sssr = drv_data->sssr;
> + u32 ssto = drv_data->ssto;
> + u32 sspsp = drv_data->sspsp;
> +
> + printk("SSP dump: sscr0=0x%08x, sscr1=0x%08x, "
> + "ssto=0x%08x, sspsp=0x%08x, sssr=0x%08x\n",
> + __REG(sscr0), __REG(sscr1), __REG(ssto),
> + __REG(sspsp), __REG(sssr));
> +}
> +
> +static inline void dump_message(struct spi_message *msg)
> +{
> + int i;
> + struct device *dev = &msg->dev->dev;
> +
> + dev_dbg(dev, "message = %p\n", msg);
> + dev_dbg(dev, " complete = %p\n", msg->complete);
> + dev_dbg(dev, " context = %p\n", msg->context);
> + dev_dbg(dev, " actual_length = %u\n", msg->actual_length);
> + dev_dbg(dev, " status = %d\n", msg->status);
> + dev_dbg(dev, " state = %p\n", msg->state);
> + dev_dbg(dev, " n_transfers = %u\n", msg->n_transfer);
> +
> + for (i = 0; i < msg->n_transfer; i++) {
> + dev_dbg(dev, " %d, tx_buf = %p\n",
> + i, msg->transfers[i].tx_buf);
> + dev_dbg(dev, " %d, rx_buf = %p\n",
> + i, msg->transfers[i].rx_buf);
> + dev_dbg(dev, " %d, tx_dma = %p\n",
> + i, (void *)msg->transfers[i].tx_dma);
> + dev_dbg(dev, " %d, rx_dma = %p\n",
> + i, (void *)msg->transfers[i].rx_dma);
> + dev_dbg(dev, " %d, len = %u\n",
> + i, msg->transfers[i].len);
> + dev_dbg(dev, " %d, cs_change = %u\n",
> + i, msg->transfers[i].cs_change);
> + dev_dbg(dev, " %d, delay_usecs = %u\n",
> + i, msg->transfers[i].delay_usecs);
> + }
> +}
> +
> +static void null_cs_control(u32 command)
> +{
> +}
> +
> +static void null_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
> + __REG(ssdr) = 0;
> + ++state->tx;
> + }
> +}
> +
> +static void null_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
> + (void)(__REG(ssdr));
> + ++state->rx;
> + }
> +}
> +
> +static void u8_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
> + __REG(ssdr) = *(u8 *)(state->tx);
> + ++state->tx;
> + }
> +}
> +
> +static void u8_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
> + *(u8 *)(state->rx) = __REG(ssdr);
> + ++state->rx;
> + }
> +}
> +
> +static void u16_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
> + __REG(ssdr) = *(u16 *)(state->tx);
> + state->tx += 2;
> + }
> +}
> +
> +static void u16_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
> + *(u16 *)(state->rx) = __REG(ssdr);
> + state->rx += 2;
> + }
> +}
> +static void u32_writer(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) {
> + __REG(ssdr) = *(u32 *)(state->tx);
> + state->tx += 4;
> + }
> +}
> +
> +static void u32_reader(u32 sssr, u32 ssdr, struct transfer_state *state)
> +{
> + while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) {
> + *(u32 *)(state->rx) = __REG(ssdr);
> + state->rx += 4;
> + }
> +}
> +
> +static void dma_handler(int channel, void *data, struct pt_regs *regs)
> +{
> + struct master_data *drv_data = (struct master_data *)data;
> + u32 sssr = drv_data->sssr;
> + u32 sscr1 = drv_data->sscr1;
> + u32 ssto = drv_data->sscr1;
> + u32 irq_status = DCSR(drv_data->tx_channel) & DMA_INT_MASK;
> + struct spi_message *msg = drv_data->cur_msg;
> + struct transfer_state *state = (struct transfer_state *)msg->context;
> +
> + if (irq_status & DCSR_BUSERR) {
> +
> + __REG(ssto) = 0;
> + __REG(sssr) = SSSR_TINT | SSSR_ROR;
> + __REG(sscr1) &= ~(SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE);
> + DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
> + DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
> +
> + flush(drv_data);
> +
> + printk(KERN_ERR "dma_handler: bad bus address on channel %d\n",
> + channel);
> +
> + drv_data->buserr_cnt++;
> + drv_data->cur_state.index = -2;
> + tasklet_schedule(&drv_data->pump_transfers);
> + }
> +
> + if (channel == drv_data->rx_channel && irq_status & DCSR_ENDINTR) {
> + /* Forward to transfer handler */
> + state->rx = state->rx_end;
> + state->transfer_handler(drv_data, msg, state);
> + }
> +
> +
> +}
> +
> +static irqreturn_t dma_transfer(struct master_data *drv_data,
> + struct spi_message *msg,
> + struct transfer_state *state)
> +{
> + u32 sssr = drv_data->sssr;
> + u32 ssdr = drv_data->ssdr;
> + u32 sscr1 = drv_data->sscr1;
> + u32 ssto = drv_data->ssto;
> + u32 irq_status = GET_IRQ_STATUS(sssr);
> + u32 trailing_sssr = 0;
> +
> + if (irq_status & SSSR_ROR) {
> + /* Clear and disable interrupts on SSP and DMA channels*/
> + __REG(ssto) = 0;
> + __REG(sssr) = SSSR_TINT | SSSR_ROR;
> + __REG(sscr1) &= ~(SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE);
> + DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
> + DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
> +
> + flush(drv_data);
> +
> + printk(KERN_WARNING "dma_transfer: fifo overun"
> + "tx dcmd = 0x%08x rx dcmd = 0x%08x\n",
> + DCMD(drv_data->tx_channel),
> + DCMD(drv_data->rx_channel));
> +
> + drv_data->ror_cnt++;
> +
> + state->index = -2;
> + tasklet_schedule(&drv_data->pump_transfers);
> +
> + return IRQ_HANDLED;
> + }
> +
> + if (irq_status & SSSR_TINT || state->rx == state->rx_end) {
> +
> + /* Check for false positive */
> + if (DCSR(drv_data->tx_channel) & DCSR_RUN) {
> + __REG(sssr) = SSSR_TINT;
> + return IRQ_HANDLED;
> + }
> +
> + /* Clear and disable interrupts on SSP and DMA channels*/
> + __REG(ssto) = 0;
> + __REG(sssr) = SSSR_TINT | SSSR_ROR ;
> + __REG(sscr1) &= ~(SSCR1_TSRE | SSCR1_RSRE | SSCR1_TINTE);
> + DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
> + DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
> + while (!(DCSR(drv_data->rx_channel) & DCSR_STOPSTATE)
> + || (__REG(sssr) & SSSR_BSY));
> +
> + /* Handle trailing byte, must unmap the dma buffers */
> + dma_unmap_single(&msg->dev->dev,
> + msg->transfers[state->index].tx_dma,
> + msg->transfers[state->index].len,
> + DMA_TO_DEVICE);
> + dma_unmap_single(&msg->dev->dev,
> + msg->transfers[state->index].rx_dma,
> + msg->transfers[state->index].len,
> + DMA_FROM_DEVICE);
> +
> + /* Calculate number of trailing bytes */
> + trailing_sssr = __REG(sssr);
> + if ((trailing_sssr & 0xf008) != 0xf000) {
> + state->rx = state->rx_end -
> + (((trailing_sssr >> 12) & 0x0f) + 1);
> + state->read(sssr, ssdr, state);
> + }
> +
> + if (msg->transfers[state->index].cs_change)
> + state->cs_control(PXA2XX_CS_DEASSERT);
> +
> + /* Schedule transfer tasklet */
> + msg->actual_length += msg->transfers[state->index].len;
> + ++state->index;
> + tasklet_schedule(&drv_data->pump_transfers);
> +
> + return IRQ_HANDLED;
> + }
> +
> + return IRQ_NONE;
> +}
> +
> +static irqreturn_t interrupt_transfer(struct master_data *drv_data,
> + struct spi_message *msg,
> + struct transfer_state *state)
> +{
> + u32 sssr = drv_data->sssr;
> + u32 ssdr = drv_data->ssdr;
> + u32 sscr1 = drv_data->sscr1;
> + u32 ssto = drv_data->ssto;
> + u32 irq_status;
> +
> + while ((irq_status = GET_IRQ_STATUS(sssr))) {
> +
> + if (irq_status & SSSR_ROR) {
> +
> + /* Clear and disable interrupts */
> + __REG(ssto) = 0;
> + __REG(sssr) = SSSR_TINT | SSSR_ROR;
> + __REG(sscr1) &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
> +
> + flush(drv_data);
> +
> + printk(KERN_WARNING "interrupt_transfer: fifo overun, "
> + "index=%d tx_len=%d rx_len=%d\n",
> + state->index,
> + (state->tx_end - state->tx),
> + (state->rx_end - state->rx));
> +
> + drv_data->ror_cnt++;
> + state->index = -2;
> + tasklet_schedule(&drv_data->pump_transfers);
> +
> + return IRQ_HANDLED;
> + }
> +
> + /* Look for false positive timeout */
> + if ((irq_status & SSSR_TINT) && (state->rx < state->rx_end))
> + __REG(sssr) = SSSR_TINT;
> +
> + /* Pump data */
> + state->read(sssr, ssdr, state);
> + state->write(sssr, ssdr, state);
> +
> + /* Disable tx interrupt enable rx timeout */
> + if (state->tx == state->tx_end) {
> + __REG(sscr1) &= ~SSCR1_TIE;
> + }
> +
> + if ((irq_status & SSSR_TINT) || (state->rx == state->rx_end)) {
> +
> + /* Clear timeout */
> + __REG(ssto) = 0;
> + __REG(sssr) = SSSR_TINT | SSSR_ROR ;
> + __REG(sscr1) &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
> +
> + msg->actual_length += msg->transfers[state->index].len;
> +
> + if (msg->transfers[state->index].cs_change)
> + state->cs_control(PXA2XX_CS_DEASSERT);
> +
> + /* Schedule transfer tasklet */
> + ++state->index;
> + tasklet_schedule(&drv_data->pump_transfers);
> +
> + return IRQ_HANDLED;
> + }
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t ssp_int(int irq, void *dev_id, struct pt_regs *regs)
> +{
> + struct master_data *drv_data = (struct master_data *)dev_id;
> + struct transfer_state *state;
> + struct spi_message *msg;
> +
> + if (!drv_data->cur_msg || !drv_data->cur_msg->state) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad message or transfer "
> + "state in interrupt handler\n");
> + return IRQ_NONE;
> + }
> + state = (struct transfer_state *)drv_data->cur_msg->state;
> + msg = drv_data->cur_msg;
> +
> + return state->transfer_handler(drv_data, msg, state);
> +}
> +
> +static void pump_dma_transfers(unsigned long data)
> +{
> + struct master_data *drv_data = (struct master_data *)data;
> + struct spi_message *message = drv_data->cur_msg;
> + struct chip_data *chip;
> + struct transfer_state * state;
> + struct spi_transfer *transfer;
> + u32 sssr = drv_data->sssr;
> + u32 sscr1 = drv_data->sscr1;
> + u32 ssto = drv_data->ssto;
> + u32 tx_dcd = DCMD_INCSRCADDR | DCMD_FLOWTRG;
> + u32 rx_dcd = DCMD_INCTRGADDR | DCMD_FLOWSRC | DCMD_ENDIRQEN;
> + u32 tx_map_len;
> + u32 rx_map_len;
> + unsigned long flags;
> +
> + if (!message) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers "
> + "schedule\n");
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + state = (struct transfer_state *)message->state;
> + if (!state) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n");
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + chip = spi_get_ctldata(message->dev);
> + if (!chip) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n");
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + /* Handle for abort */
> + if (state->index == -2) {
> + message->status = -EIO;
> + if (message->complete) {
> + message->complete(message->context);
> + }
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + /* Handle end of message */
> + if (state->index == message->n_transfer) {
> + if (!message->transfers[state->index].cs_change)
> + state->cs_control(PXA2XX_CS_DEASSERT);
> +
> + message->status = 0;
> + if (message->complete)
> + message->complete(message->context);
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + /* Handle start of message */
> + if (state->index == -1) {
> + restore_state(drv_data, chip);
> + flush(drv_data);
> + ++state->index;
> + }
> +
> + /* Delay if requested at end of transfer*/
> + if (state->index > 1) {
> + transfer = message->transfers + (state->index - 1);
> + if (transfer->delay_usecs)
> + udelay(transfer->delay_usecs);
> + }
> +
> + /* Setup the transfer state based on the type of transfer */
> + flush(drv_data);
> + drv_data->trans_cnt++;
> + transfer = message->transfers + state->index;
> + state->cs_control = !chip->cs_control ?
> + null_cs_control : chip->cs_control;
> + state->tx = (void *)transfer->tx_buf;
> + state->tx_end = state->tx + (transfer->len * chip->n_bytes);
> + state->rx = transfer->rx_buf;
> + state->rx_end = state->rx + (transfer->len * chip->n_bytes);
> + state->write = state->tx ? chip->write : null_writer;
> + state->read = state->rx ? chip->read : null_reader;
> +
> + /* Make sure DMA is enable and buffer are aligned on 8-byte boundary */
> + if (chip->enable_dma && IS_DMA_ALIGNED(state->tx, state->rx)) {
> +
> + /* Ensure we have the correct interrupt handler */
> + state->transfer_handler = dma_transfer;
> + drv_data->dma_trans_cnt++;
> +
> + /* Use local DMA if no rx buf specified */
> + if (state->rx == NULL) {
> + *(u32 *)(drv_data->null_dma_buf) = 0;
> + state->rx = drv_data->null_dma_buf;
> + rx_map_len = 4;
> + rx_dcd &= ~DCMD_INCTRGADDR;
> + }
> + else
> + rx_map_len = transfer->len & DCMD_LENGTH;
> +
> +
> + /* Disable cache on rx buffer */
> + transfer->rx_dma =
> + dma_map_single(&message->dev->dev,
> + state->rx,
> + rx_map_len,
> + DMA_FROM_DEVICE);
> +
> + if (dma_mapping_error(transfer->rx_dma))
> + goto out_mapping_error;
> +
> + /* Setup rx DMA Channel */
> + DCSR(drv_data->rx_channel) = RESET_DMA_CHANNEL;
> + DSADR(drv_data->rx_channel) = drv_data->ssdr;
> + DTADR(drv_data->rx_channel) = transfer->rx_dma;
> + DCMD(drv_data->rx_channel) = rx_dcd
> + | chip->dma_width
> + | chip->dma_burst_size
> + | transfer->len;
> +
> + /* Use local DMA if no tx buf specified */
> + if (transfer->tx_buf == NULL) {
> + *(u32 *)(drv_data->null_dma_buf) = 0;
> + state->tx = drv_data->null_dma_buf;
> + tx_map_len = 4;
> + tx_dcd &= ~DCMD_INCSRCADDR;
> + }
> + else
> + tx_map_len = transfer->len & DCMD_LENGTH;
> +
> + /* Disable cache on tx buffer */
> + transfer->tx_dma =
> + dma_map_single(&message->dev->dev,
> + state->tx,
> + tx_map_len,
> + DMA_TO_DEVICE);
> +
> + if (dma_mapping_error(transfer->tx_dma))
> + goto out_mapping_error;
> +
> + /* Setup tx DMA Channel */
> + DCSR(drv_data->tx_channel) = RESET_DMA_CHANNEL;
> + DSADR(drv_data->tx_channel) = transfer->tx_dma;
> + DTADR(drv_data->tx_channel) = drv_data->ssdr;
> + DCMD(drv_data->tx_channel) = tx_dcd
> + | chip->dma_width
> + | chip->dma_burst_size
> + | transfer->len;
> +
> + /* Fix me, need to handle cs polarity */
> + state->cs_control(PXA2XX_CS_ASSERT);
> +
> + /* Go baby, go */
> + local_irq_save(flags);
> + __REG(sssr) = SSSR_TINT | SSSR_ROR;
> + DCSR(drv_data->rx_channel) |= DCSR_RUN;
> + DCSR(drv_data->tx_channel) |= DCSR_RUN;
> + __REG(ssto) = chip->timeout;
> + __REG(sscr1) = chip->cr1
> + | chip->dma_threshold
> + | SSCR1_TSRE
> + | SSCR1_RSRE
> + | SSCR1_TINTE;
> + local_irq_restore(flags);
> + }
> + else {
> + if (transfer->len > 16)
> + drv_data->bigunalign_cnt++;
> +
> + /* Ensure we have the correct interrupt handler */
> + state->transfer_handler = interrupt_transfer;
> + drv_data->int_trans_cnt++;
> +
> + /* Fix me, need to handle cs polarity */
> + state->cs_control(PXA2XX_CS_ASSERT);
> +
> + /* Go baby, go */
> + local_irq_save(flags);
> + __REG(sssr) = SSSR_TINT | SSSR_ROR;
> + __REG(ssto) = chip->timeout;
> + __REG(sscr1) = chip->cr1
> + | chip->threshold
> + | SSCR1_TIE
> + | SSCR1_RIE
> + | SSCR1_TINTE;
> + local_irq_restore(flags);
> + }
> +
> + return;
> +
> +out_mapping_error:
> + printk(KERN_ERR "problem mapping dma buf\n");
> + message->status = -EIO;
> + if (message->complete) {
> + message->complete(message->context);
> + }
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> +}
> +
> +static void pump_int_transfers(unsigned long data)
> +{
> + struct master_data *drv_data = (struct master_data *)data;
> + struct spi_message *message = drv_data->cur_msg;
> + struct chip_data *chip;
> + struct transfer_state * state;
> + struct spi_transfer *transfer;
> + u32 sssr = drv_data->sssr;
> + u32 sscr1 = drv_data->sscr1;
> + u32 ssto = drv_data->ssto;
> + unsigned long flags;
> +
> + if (!message) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers "
> + "schedule\n");
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + state = (struct transfer_state *)message->state;
> + if (!state) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n");
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + chip = spi_get_ctldata(message->dev);
> + if (!chip) {
> + printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n");
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + /* Handle for abort */
> + if (state->index == -2) {
> + message->status = -EIO;
> + if (message->complete) {
> + message->complete(message->context);
> + }
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + /* Handle end of message */
> + if (state->index == message->n_transfer) {
> + if (!message->transfers[state->index].cs_change)
> + state->cs_control(PXA2XX_CS_DEASSERT);
> +
> + message->status = 0;
> + if (message->complete)
> + message->complete(message->context);
> + drv_data->cur_msg = NULL;
> + tasklet_schedule(&drv_data->pump_messages);
> + return;
> + }
> +
> + /* Handle start of message */
> + if (state->index == -1) {
> + restore_state(drv_data, chip);
> + flush(drv_data);
> + ++state->index;
> + }
> +
> + /* Delay if requested at end of transfer*/
> + if (state->index > 1) {
> + transfer = message->transfers + (state->index - 1);
> + if (transfer->delay_usecs)
> + udelay(transfer->delay_usecs);
> + }
> +
> + /* Setup the transfer state based on the type of transfer */
> + flush(drv_data);
> + drv_data->trans_cnt++;
> + transfer = message->transfers + state->index;
> + state->cs_control = chip->cs_control;
> + state->tx = (void *)transfer->tx_buf;
> + state->tx_end = state->tx + (transfer->len * chip->n_bytes);
> + state->rx = transfer->rx_buf;
> + state->rx_end = state->rx + (transfer->len * chip->n_bytes);
> + state->write = state->tx ? chip->write : null_writer;
> + state->read = state->rx ? chip->read : null_reader;
> + state->transfer_handler = interrupt_transfer;
> + drv_data->int_trans_cnt++;
> +
> + /* Fix me, need to handle cs polarity */
> + state->cs_control(PXA2XX_CS_ASSERT);
> +
> + /* Go baby, go */
> + local_irq_save(flags);
> + __REG(sssr) = SSSR_TINT | SSSR_ROR;
> + __REG(ssto) = chip->timeout;
> + __REG(sscr1) = chip->cr1
> + | chip->threshold
> + | SSCR1_TIE
> + | SSCR1_RIE
> + | SSCR1_TINTE;
> + local_irq_restore(flags);
> +}
> +
> +static void pump_messages(unsigned long data)
> +{
> + struct master_data *drv_data = (struct master_data *)data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&drv_data->lock, flags);
> +
> + /* Check for list empty */
> + if (list_empty(&drv_data->queue)) {
> + spin_unlock(&drv_data->lock);
> + return;
> + }
> +
> + /* Check to see if we are already running */
> + if (drv_data->cur_msg) {
> + spin_unlock(&drv_data->lock);
> + return;
> + }
> +
> + /* Extract head of queue and check for tasklet reschedule */
> + drv_data->cur_msg = list_entry(drv_data->queue.next,
> + struct spi_message, queue);
> + list_del_init(&drv_data->cur_msg->queue);
> +
> + /* Setup message transfer and schedule transfer pump */
> + drv_data->cur_msg->state = &drv_data->cur_state;
> + drv_data->cur_state.index = -1;
> + drv_data->cur_state.len = 0;
> + tasklet_schedule(&drv_data->pump_transfers);
> +
> + spin_unlock_irqrestore(&drv_data->lock, flags);
> +}
> +
> +static int transfer(struct spi_device *spi, struct spi_message *msg)
> +{
> + struct master_data *drv_data = class_get_devdata(&spi->master->cdev);
> + unsigned long flags;
> +
> + msg->actual_length = 0;
> + msg->status = 0;
> +
> + spin_lock_irqsave(&drv_data->lock, flags);
> + list_add_tail(&msg->queue, &drv_data->queue);
> + spin_unlock_irqrestore(&drv_data->lock, flags);
> +
> + tasklet_schedule(&drv_data->pump_messages);
> +
> + return 0;
> +}
> +
> +static int setup(struct spi_device *spi)
> +{
> + struct pxa2xx_spi_chip *chip_info;
> + struct chip_data *chip;
> +
> + chip_info = (struct pxa2xx_spi_chip *)spi->platform_data;
> +
> + /* Only alloc on first setup */
> + chip = spi_get_ctldata(spi);
> + if (chip == NULL) {
> + chip = kcalloc(1, sizeof(struct chip_data), GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + spi->mode = chip_info->mode;
> + spi->bits_per_word = chip_info->bits_per_word;
> + }
> +
> + chip->cs_control = chip_info->cs_control;
> + chip->cr0 = SSCR0_SerClkDiv((MAX_SPEED_HZ / spi->max_speed_hz) + 2)
> + | SSCR0_Motorola
> + | SSCR0_DataSize(spi->bits_per_word)
> + | SSCR0_SSE
> + | (spi->bits_per_word > 16 ? SSCR0_EDSS : 0);
> + chip->cr1 = (((spi->mode & SPI_CPHA) != 0) << 4)
> + | (((spi->mode & SPI_CPOL) != 0) << 3)
> + | (chip_info->enable_loopback ? SSCR1_LBM : 0);
> + chip->to = 0;
> + chip->psp = 0;
> + chip->timeout = (chip_info->timeout_microsecs * 10000) / 2712;
> + chip->threshold = SSCR1_RxTresh(chip_info->rx_threshold)
> + | SSCR1_TxTresh(chip_info->tx_threshold);
> +
> + chip->enable_dma = chip_info->dma_burst_size != 0;
> + if (chip_info->dma_burst_size <= 8) {
> + chip->dma_threshold = SSCR1_RxTresh(8) | SSCR1_TxTresh(8);
> + chip->dma_burst_size = DCMD_BURST8;
> + }
> + else if (chip_info->dma_burst_size <= 16) {
> + chip->dma_threshold = SSCR1_RxTresh(16) | SSCR1_TxTresh(16);
> + chip->dma_burst_size = DCMD_BURST16;
> + }
> + else {
> + chip->dma_threshold = SSCR1_RxTresh(32) | SSCR1_TxTresh(32);
> + chip->dma_burst_size = DCMD_BURST32;
> + }
> +
> + if (spi->bits_per_word <= 8) {
> + chip->n_bytes = 1;
> + chip->dma_width = DCMD_WIDTH1;
> + chip->read = u8_reader;
> + chip->write = u8_writer;
> + }
> + else if (spi->bits_per_word <= 16) {
> + chip->n_bytes = 2;
> + chip->dma_width = DCMD_WIDTH2;
> + chip->read = u16_reader;
> + chip->write = u16_writer;
> + }
> + else if (spi->bits_per_word <= 32) {
> + chip->n_bytes = 4;
> + chip->dma_width = DCMD_WIDTH4;
> + chip->read = u32_reader;
> + chip->write = u32_writer;
> + }
> + else {
> + printk(KERN_ERR "pxa2xx_spi_ssp: invalid wordsize\n");
> + kfree(chip);
> + return -ENODEV;
> + }
> +
> + spi_set_ctldata(spi, chip);
> +
> + return 0;
> +}
> +
> +static void cleanup(const struct spi_device *spi)
> +{
> + struct chip_data *chip = spi_get_ctldata((struct spi_device *)spi);
> +
> + if (chip)
> + kfree(chip);
> +
> + dev_dbg(&spi->dev, "spi_device %u.%u cleanup\n",
> + spi->master->bus_num, spi->chip_select);
> +}
> +
> +static ssize_t trans_cnt_show(struct device *dev, char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->trans_cnt);
> +}
> +DEVICE_ATTR(transfer_count, 0444, trans_cnt_show, NULL);
> +
> +static ssize_t dma_trans_cnt_show(struct device *dev, char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->dma_trans_cnt);
> +}
> +DEVICE_ATTR(dma_transfer_count, 0444, dma_trans_cnt_show, NULL);
> +
> +static ssize_t int_trans_cnt_show(struct device *dev, char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->int_trans_cnt);
> +}
> +DEVICE_ATTR(interrupt_transfer_count, 0444, int_trans_cnt_show, NULL);
> +
> +static ssize_t bigunalign_cnt_show(struct device *dev, char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->bigunalign_cnt);
> +}
> +DEVICE_ATTR(big_unaligned_count, 0444, bigunalign_cnt_show, NULL);
> +
> +static ssize_t buserr_cnt_show(struct device *dev, char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->buserr_cnt);
> +}
> +DEVICE_ATTR(bus_error_count, 0444, buserr_cnt_show, NULL);
> +
> +static ssize_t ror_cnt_show(struct device *dev, char *buf)
> +{
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> +
> + return snprintf(buf, PAGE_SIZE, "%u\n", drv_data->ror_cnt);
> +}
> +DEVICE_ATTR(overrun_count, 0444, ror_cnt_show, NULL);
> +
> +static int probe(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct pxa2xx_spi_master *platform_info;
> + struct spi_master *master;
> + struct master_data *drv_data = 0;
> + struct resource *memory_resource;
> + int irq;
> + int status = 0;
> +
> + platform_info = (struct pxa2xx_spi_master *)pdev->dev.platform_data;
> +
> + master = spi_alloc_master(dev, sizeof(struct master_data) + 16);
> + if (!master)
> + return -ENOMEM;
> + drv_data = class_get_devdata(&master->cdev);
> + drv_data->master = master;
> +
> + INIT_LIST_HEAD(&drv_data->queue);
> + spin_lock_init(&drv_data->lock);
> +
> + tasklet_init(&drv_data->pump_messages,
> + pump_messages,
> + (unsigned long)drv_data);
> +
> + master->bus_num = platform_info->bus_num;
> + master->num_chipselect = platform_info->num_chipselect;
> + master->cleanup = cleanup;
> + master->setup = setup;
> + master->transfer = transfer;
> + drv_data->null_dma_buf = drv_data + sizeof(struct master_data);
> + drv_data->null_dma_buf = (void *)(((u32)(drv_data->null_dma_buf)
> + & 0xfffffff8) | 8 );
> + drv_data->trans_cnt = 0;
> + drv_data->dma_trans_cnt = 0;
> + drv_data->int_trans_cnt = 0;
> + drv_data->bigunalign_cnt = 0;
> + drv_data->buserr_cnt = 0;
> + drv_data->ror_cnt = 0;
> +
> + /* Setup register addresses */
> + memory_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!memory_resource) {
> + dev_dbg(dev, "can not find platform io memory\n");
> + status = -ENODEV;
> + goto out_error_memory;
> + }
> +
> + drv_data->sscr0 = memory_resource->start + 0x00000000;
> + drv_data->sscr1 = memory_resource->start + 0x00000004;
> + drv_data->sssr = memory_resource->start + 0x00000008;
> + drv_data->ssitr = memory_resource->start + 0x0000000c;
> + drv_data->ssdr = memory_resource->start + 0x00000010;
> + drv_data->ssto = memory_resource->start + 0x00000028;
> + drv_data->sspsp = memory_resource->start + 0x0000002c;
> +
> + /* Attach to IRQ */
> + irq = platform_get_irq(pdev, 0);
> + if (irq == 0) {
> + dev_dbg(dev, "problem getting IORESOURCE_IRQ[0]\n");
> + status = -ENODEV;
> + goto out_error_memory;
> + }
> +
> + status = request_irq(irq, ssp_int, SA_INTERRUPT, dev->bus_id, drv_data);
> + if (status < 0) {
> + dev_dbg(dev, "problem requesting IORESOURCE_IRQ %u\n", irq);
> + goto out_error_memory;
> + }
> +
> + /* Setup DMA is requested */
> + if (platform_info->enable_dma) {
> +
> + tasklet_init(&drv_data->pump_transfers,
> + pump_dma_transfers,
> + (unsigned long)drv_data);
> +
> + /* Get two DMA channels (rx and tx) */
> + drv_data->rx_channel = pxa_request_dma("pxa2xx_spi_ssp_rx",
> + DMA_PRIO_HIGH,
> + dma_handler,
> + drv_data);
> + if (drv_data->rx_channel < 0) {
> + dev_err(dev, "problem (%d) requesting rx channel\n",
> + drv_data->rx_channel);
> + status = -ENODEV;
> + goto out_error_irq;
> + }
> + DRCMRRXSS2DR = DRCMR_MAPVLD
> + | (drv_data->rx_channel & DRCMR_CHLNUM);
> +
> + drv_data->tx_channel = pxa_request_dma("pxa2xx_spi_ssp_tx",
> + DMA_PRIO_MEDIUM,
> + dma_handler,
> + drv_data);
> + if (drv_data->tx_channel < 0) {
> + dev_err(dev, "problem (%d) requesting tx channel\n",
> + drv_data->tx_channel);
> + status = -ENODEV;
> + goto out_error_rx_dma;
> + }
> + DRCMRTXSS2DR = DRCMR_MAPVLD
> + | (drv_data->tx_channel & DRCMR_CHLNUM);
> + }
> + else
> + /* No DMA use interrupt transfers */
> + tasklet_init(&drv_data->pump_transfers,
> + pump_int_transfers,
> + (unsigned long)drv_data);
> +
> + /* Enable SOC clock */
> + pxa_set_cken(platform_info->clock_enable, 1);
> +
> + /* Load default SSP configuration */
> + __REG(drv_data->sscr0) = 0;
> + __REG(drv_data->sscr1) = SSCR1_RxTresh(4) | SSCR1_TxTresh(12);
> + __REG(drv_data->sscr0) = SSCR0_SerClkDiv(2)
> + | SSCR0_Motorola
> + | SSCR0_DataSize(8);
> + __REG(drv_data->ssto) = 0;
> + __REG(drv_data->sspsp) = 0;
> +
> + status = device_create_file(dev, &dev_attr_transfer_count);
> + status += device_create_file(dev, &dev_attr_dma_transfer_count);
> + status += device_create_file(dev, &dev_attr_interrupt_transfer_count);
> + status += device_create_file(dev, &dev_attr_big_unaligned_count);
> + status += device_create_file(dev, &dev_attr_bus_error_count);
> + status += device_create_file(dev, &dev_attr_overrun_count);
> + if (status != 0) {
> + dev_err(dev, "problem creating attribute\n");
> + status = -EIO;
> + goto out_error_attr;
> + }
> +
> +
> + /* Register with the SPI framework */
> + dev_set_drvdata(dev, master);
> + status = spi_register_master(master);
> + if (status != 0) {
> + goto out_error_attr;
> + }
> +
> + return status;
> +
> +out_error_attr:
> +
> + device_remove_file(dev, &dev_attr_transfer_count);
> + device_remove_file(dev, &dev_attr_dma_transfer_count);
> + device_remove_file(dev, &dev_attr_interrupt_transfer_count);
> + device_remove_file(dev, &dev_attr_big_unaligned_count);
> + device_remove_file(dev, &dev_attr_bus_error_count);
> + device_remove_file(dev, &dev_attr_overrun_count);
> +
> + pxa_free_dma(drv_data->tx_channel);
> +
> +out_error_rx_dma:
> + pxa_free_dma(drv_data->rx_channel);
> +
> +out_error_irq:
> + free_irq(irq, drv_data);
> +
> +out_error_memory:
> +
> + return status;
> +}
> +
> +static int remove(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct spi_master *master = dev_get_drvdata(dev);
> + struct master_data *drv_data = class_get_devdata(&master->cdev);
> + struct pxa2xx_spi_master *platform_info;
> +
> + int irq;
> + unsigned long flags;
> +
> + device_remove_file(dev, &dev_attr_transfer_count);
> + device_remove_file(dev, &dev_attr_dma_transfer_count);
> + device_remove_file(dev, &dev_attr_interrupt_transfer_count);
> + device_remove_file(dev, &dev_attr_big_unaligned_count);
> + device_remove_file(dev, &dev_attr_bus_error_count);
> + device_remove_file(dev, &dev_attr_overrun_count);
> +
> + platform_info = (struct pxa2xx_spi_master *)pdev->dev.platform_data;
> +
> + spin_lock_irqsave(&drv_data->lock, flags);
> +
> + __REG(drv_data->sscr0) = 0;
> + pxa_set_cken(platform_info->clock_enable, 0);
> +
> + DRCMRRXSS2DR = 0;
> + DRCMRTXSS2DR = 0;
> + pxa_free_dma(drv_data->tx_channel);
> + pxa_free_dma(drv_data->rx_channel);
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq != 0)
> + free_irq(irq, drv_data);
> +
> + spin_unlock_irqrestore(&drv_data->lock, flags);
> +
> + spi_unregister_master(master);
> +
> + return 0;
> +}
> +
> +static struct device_driver driver = {
> + .name = "pxa2xx-spi-ssp",
> + .bus = &platform_bus_type,
> + .owner = THIS_MODULE,
> + .probe = probe,
> + .remove = remove,
> +};
> +
> +static int pxa2xx_spi_ssp_init(void)
> +{
> + driver_register(&driver);
> +
> + return 0;
> +}
> +module_init(pxa2xx_spi_ssp_init);
> +
> +static void pxa2xx_spi_ssp_exit(void)
> +{
> + driver_unregister(&driver);
> +}
> +module_exit(pxa2xx_spi_ssp_exit);
> --- linux-2.6.12-spi/drivers/spi/pxa2xx_loopback.c 1969-12-31 16:00:00.000000000 -0800
> +++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_loopback.c 2005-10-25 15:58:06.325046000 -0700
> @@ -0,0 +1,403 @@
> +/*
> + * 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/device.h>
> +#include <linux/spi.h>
> +#include <linux/list.h>
> +#include <linux/errno.h>
> +#include <linux/miscdevice.h>
> +#include <linux/kfifo.h>
> +
> +#include <asm/semaphore.h>
> +#include <asm/atomic.h>
> +#include <asm/uaccess.h>
> +
> +MODULE_AUTHOR("Stephen Street");
> +MODULE_DESCRIPTION("PXA Loopback SPI Protocol Driver");
> +MODULE_LICENSE("GPL");
> +
> +#define BUF_SIZE (1024)
> +
> +struct device_data {
> + char name[BUS_ID_SIZE];
> + struct miscdevice misc_device;
> + struct semaphore driver_lock;
> + struct list_head device_entry;
> + atomic_t busy;
> + spinlock_t read_lock;
> + spinlock_t write_lock;
> + struct kfifo *read_fifo;
> + struct kfifo *write_fifo;
> + wait_queue_head_t read_available;
> + wait_queue_head_t write_available;
> + struct spi_message msg;
> + struct spi_transfer transfer;
> + void *dma_buf;
> +};
> +
> +static void post(void *context);
> +
> +static LIST_HEAD(device_list);
> +static DECLARE_MUTEX(device_list_lock);
> +
> +static inline struct device_data *find_dev_data(int minor)
> +{
> + struct list_head *ptr;
> + struct device_data *entry;
> +
> + down(&device_list_lock);
> +
> + list_for_each(ptr, &device_list) {
> + entry = list_entry(ptr, struct device_data, device_entry);
> + if (minor == entry->misc_device.minor) {
> + up(&device_list_lock);
> + return entry;
> + }
> + }
> +
> + up(&device_list_lock);
> + return NULL;
> +}
> +
> +static inline void add_device_data(struct device_data *dev_data)
> +{
> + down(&device_list_lock);
> +
> + list_add(&dev_data->device_entry, &device_list);
> +
> + up(&device_list_lock);
> +}
> +
> +static inline void remove_device_data(struct device_data *dev_data)
> +{
> + down(&device_list_lock);
> +
> + list_del(&dev_data->device_entry);
> +
> + up(&device_list_lock);
> +}
> +
> +static void kick(struct device_data *dev_data)
> +{
> + unsigned int size;
> + int status = 0;
> +
> + /* Make sure we are not busy */
> + if (!atomic_inc_and_test(&dev_data->busy)) {
> + atomic_dec(&dev_data->busy);
> + return;
> + }
> +
> + /* Check for available data */
> + size = kfifo_len(dev_data->write_fifo);
> + if (!size) {
> + atomic_dec(&dev_data->busy);
> + return;
> + }
> +
> + /* Read the data to send */
> + if (kfifo_get(dev_data->write_fifo, dev_data->dma_buf, size) != size) {
> + dev_err(dev_data->misc_device.dev, "write fifo failure\n");
> + atomic_dec(&dev_data->busy);
> + return;
> + }
> +
> + /* Send async spi message */
> + memset(&dev_data->msg, 0, sizeof(struct spi_message));
> + memset(&dev_data->transfer, 0, sizeof(struct spi_transfer));
> + dev_data->transfer.tx_buf = dev_data->dma_buf;
> + dev_data->transfer.rx_buf = dev_data->dma_buf;
> + dev_data->transfer.len = size;
> + dev_data->msg.transfers = &dev_data->transfer;
> + dev_data->msg.n_transfer = 1;
> + dev_data->msg.complete = post;
> + dev_data->msg.context = dev_data;
> + status = spi_async(to_spi_device(dev_data->misc_device.dev),
> + &dev_data->msg);
> + if (status < 0) {
> + dev_err(dev_data->misc_device.dev,
> + "spi failure (%d)\n",
> + status);
> + atomic_dec(&dev_data->busy);
> + }
> +
> + wake_up_interruptible(&dev_data->write_available);
> +}
> +
> +static void post(void *context)
> +{
> + struct device_data *dev_data = (struct device_data *)context;
> + unsigned int space;
> + unsigned length;
> + unsigned char local_buf[BUF_SIZE];
> +
> + /* Check spi status */
> + if (dev_data->msg.status == 0) {
> +
> + /* Make room for new data */
> + length = dev_data->msg.actual_length;
> + space = BUF_SIZE - kfifo_len(dev_data->read_fifo);
> + if (space < length)
> + kfifo_get(dev_data->read_fifo,
> + local_buf,
> + length - space);
> +
> + /* Add the new data */
> + if (kfifo_put(dev_data->read_fifo, dev_data->dma_buf, length)
> + != length) {
> + dev_err(dev_data->misc_device.dev,
> + "read fifo post failure\n");
> + }
> +
> + /* Let readers know */
> + wake_up_interruptible(&dev_data->read_available);
> + }
> + else
> + dev_err(dev_data->misc_device.dev,
> + "spi failure (%d)\n",
> + dev_data->msg.status);
> +
> + atomic_dec(&dev_data->busy);
> + kick(dev_data);
> +}
> +
> +static ssize_t read(struct file *filp,
> + char __user *buf,
> + size_t count,
> + loff_t *f_pos)
> +{
> + struct device_data *dev_data = (struct device_data *)filp->private_data;
> + char local_buf[BUF_SIZE];
> + size_t read_size;
> +
> + if (down_interruptible(&dev_data->driver_lock))
> + return -ERESTARTSYS;
> +
> + while (kfifo_len(dev_data->read_fifo) == 0) {
> + up(&dev_data->driver_lock);
> + if (filp->f_flags & O_NONBLOCK)
> + return -EAGAIN;
> + if (wait_event_interruptible(dev_data->read_available,
> + (kfifo_len(dev_data->read_fifo) != 0)))
> + return -ERESTARTSYS;
> + if (down_interruptible(&dev_data->driver_lock))
> + return -ERESTARTSYS;
> + }
> +
> + read_size = min(count, (size_t)BUF_SIZE);
> + read_size = min(read_size, kfifo_len(dev_data->read_fifo));
> +
> + if (kfifo_get(dev_data->read_fifo, local_buf, read_size) != read_size) {
> + dev_err(dev_data->misc_device.dev, "read fifo failure");
> + up(&dev_data->driver_lock);
> + return -EIO;
> + }
> +
> + if (copy_to_user(buf, local_buf, read_size)) {
> + dev_err(dev_data->misc_device.dev, "user space failure");
> + up(&dev_data->driver_lock);
> + return -EFAULT;
> + }
> +
> + up(&dev_data->driver_lock);
> +
> + return read_size;
> +}
> +
> +static ssize_t write(struct file *filp,
> + const char __user *buf,
> + size_t count,
> + loff_t *f_pos)
> +{
> + struct device_data *dev_data = (struct device_data *)filp->private_data;
> + char local_buf[BUF_SIZE];
> + size_t write_size;
> +
> + if (down_interruptible(&dev_data->driver_lock))
> + return -ERESTARTSYS;
> +
> + while (kfifo_len(dev_data->write_fifo) == BUF_SIZE) {
> + up(&dev_data->driver_lock);
> + if (filp->f_flags & O_NONBLOCK)
> + return -EAGAIN;
> + if (wait_event_interruptible(dev_data->read_available,
> + (kfifo_len(dev_data->write_fifo) < BUF_SIZE)))
> + return -ERESTARTSYS;
> + if (down_interruptible(&dev_data->driver_lock))
> + return -ERESTARTSYS;
> + }
> +
> + write_size = min(count, (size_t)BUF_SIZE);
> + write_size = min(write_size,
> + BUF_SIZE - kfifo_len(dev_data->write_fifo));
> +
> + if (copy_from_user(local_buf, buf, write_size)) {
> + dev_err(dev_data->misc_device.dev, "user space failure");
> + up(&dev_data->driver_lock);
> + return -EFAULT;
> + }
> +
> + if (kfifo_put(dev_data->write_fifo, local_buf, write_size)
> + != write_size) {
> + dev_err(dev_data->misc_device.dev, "write fifo failure");
> + up(&dev_data->driver_lock);
> + return -EIO;
> + }
> +
> + kick(dev_data);
> +
> + up(&dev_data->driver_lock);
> +
> + return write_size;
> +}
> +
> +static int open(struct inode *inode, struct file *filp)
> +{
> + struct device_data *dev_data;
> +
> + dev_data = find_dev_data(iminor(inode));
> + if (dev_data == NULL) {
> + printk(KERN_ERR "no device data for minor %d\n",
> + iminor(inode));
> + return ENODEV;
> + }
> + filp->private_data = dev_data;
> +
> + return 0;
> +}
> +
> +static struct file_operations loopback_fops = {
> + .owner = THIS_MODULE,
> + .llseek = no_llseek,
> + .write = write,
> + .read = read,
> + .open = open,
> +};
> +
> +static int probe(struct device *dev)
> +{
> + struct device_data *dev_data;
> + struct spi_device *sdev;
> + int status;
> +
> + /* Allocate driver data */
> + dev_data = kcalloc(1, sizeof(struct device_data) + BUF_SIZE + 16,
> + GFP_KERNEL);
> + if (!dev_data) {
> + dev_err(dev, "problem allocating driver memory\n");
> + status = -ENOMEM;
> + goto out_error;
> + }
> +
> + /* Allocated input/output fifos */
> + dev_data->read_fifo = kfifo_alloc(BUF_SIZE,
> + GFP_KERNEL,
> + &dev_data->read_lock);
> + if (IS_ERR(dev_data->read_fifo)) {
> + status = -ENOMEM;
> + goto out_memalloc;
> + }
> +
> + dev_data->write_fifo = kfifo_alloc(BUF_SIZE,
> + GFP_KERNEL,
> + &dev_data->write_lock);
> + if (IS_ERR(dev_data->write_fifo)) {
> + status = -ENOMEM;
> + goto out_readalloc;
> + }
> +
> + /* Initialize the driver data */
> + init_MUTEX(&dev_data->driver_lock);
> + spin_lock_init(&dev_data->read_lock);
> + spin_lock_init(&dev_data->write_lock);
> + atomic_set(&dev_data->busy, -1);
> + init_waitqueue_head(&dev_data->read_available);
> + init_waitqueue_head(&dev_data->write_available);
> + dev_data->dma_buf = dev_data + sizeof(struct device_data);
> + dev_data->dma_buf = (void *)(((u32)(dev_data->dma_buf)&0xfffffff8)|8);
> + INIT_LIST_HEAD(&dev_data->device_entry);
> + dev_set_drvdata(dev, dev_data);
> +
> + /* Build misc device name from bus number and chip select */
> + sdev = to_spi_device(dev);
> + snprintf(dev_data->name, BUS_ID_SIZE, "slp%u%u",
> + sdev->master->bus_num,
> + sdev->chip_select);
> +
> + /* Register as a misc device with dynamic minor number */
> + dev_data->misc_device.name = dev_data->name;
> + dev_data->misc_device.dev = dev;
> + dev_data->misc_device.fops = &loopback_fops;
> + dev_data->misc_device.minor = MISC_DYNAMIC_MINOR;
> + status = misc_register(&dev_data->misc_device);
> + if (status != 0) {
> + dev_err(dev, "problem (%d) registering misc device\n", status);
> + goto out_writealloc;
> + }
> +
> + /* Add device data to internal device list */
> + add_device_data(dev_data);
> +
> + return 0;
> +
> +out_writealloc:
> + kfifo_free(dev_data->write_fifo);
> +
> +out_readalloc:
> + kfifo_free(dev_data->read_fifo);
> +
> +out_memalloc:
> + kfree(dev_data);
> +
> +out_error:
> + return status;
> +}
> +
> +static int remove(struct device *dev)
> +{
> + struct device_data *dev_data = dev_get_drvdata(dev);
> +
> + misc_deregister(&dev_data->misc_device);
> + remove_device_data(dev_data);
> + kfree(dev_data);
> +
> + return 0;
> +}
> +
> +struct device_driver loopback_spi = {
> + .name = "loopback",
> + .bus = &spi_bus_type,
> + .owner = THIS_MODULE,
> + .probe = probe,
> + .remove = remove,
> +};
> +
> +static int __init loopback_init(void)
> +{
> + return driver_register(&loopback_spi);
> +}
> +module_init(loopback_init);
> +
> +static void __exit loopback_exit(void)
> +{
> + driver_unregister(&loopback_spi);
> +}
> +module_exit(loopback_exit);
> --- linux-2.6.12-spi/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h 1969-12-31 16:00:00.000000000 -0800
> +++ linux-2.6.12-spi-pxa/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h 2005-10-25 15:58:06.000000000 -0700
> @@ -0,0 +1,42 @@
> +/* 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; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
> + */
> +
> +#ifndef PXA2XX_SPI_SSP_H_
> +#define PXA2XX_SPI_SSP_H_
> +
> +#define PXA2XX_CS_ASSERT (0x01)
> +#define PXA2XX_CS_DEASSERT (0x02)
> +
> +struct pxa2xx_spi_master {
> + u16 bus_num;
> + u32 clock_enable;
> + u16 num_chipselect;
> + u8 enable_dma;
> +};
> +
> +struct pxa2xx_spi_chip {
> + u8 mode;
> + u8 tx_threshold;
> + u8 rx_threshold;
> + u8 bits_per_word;
> + u8 dma_burst_size;
> + u32 timeout_microsecs;
> + u8 enable_loopback;
> + void (*cs_control)(u32 command);
> +};
> +
> +#endif /*PXA2XX_SPI_SSP_H_*/
> -
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>
--
-----------------------------------------------
Mike Lee
On Thu, 2005-10-27 at 19:33 +0800, Mike Lee wrote:
> Dear Stepen
> I am now writing another controller driver by david's framework.
> But leak of debuging layer, could your loopback driver serve for this
> purpose and how could i use it?
>
The file pxa2xx_loopback.c should be controller independent, but it does
require that the hardware (in my case the PXA255 NSSP) support a
loopback mode (i.e. tx connected to rx).
To create a loopback device for driver you should include:
static struct pxa2xx_spi_chip loopback_chip_info = {
.mode = SPI_MODE_3,
.tx_threshold = 12,
.rx_threshold = 4,
.dma_burst_size = 8,
.bits_per_word = 8,
.timeout_microsecs = 64,
.enable_loopback = 1,
};
static struct spi_board_info streetracer_spi_board_info[] __initdata = {
{
.modalias = "loopback",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 3,
.controller_data = &loopback_chip_info,
},
};
in your board init code and install the module per your configuration.
Anything written to /dev/slp23 will be echoed back to /dev/slp23 via the
"SPI controller".
Hope this helps!
-Stephen
Thanks for your info. I am working hard with that.
By the way, i have some problems with the SPI hardware arch. Please correct me.
What in my mind is that SPI clk is only triggered by master and master
read/write at the same time. But how could master know the read data
is valid? is it determine by the protocol driver? In a case, when
master place a tx-only msg to the controller driver, and slave request
to send in the middle of the transaction. The data from slave will
push into the rx buffer of the tx-only msg and finally ignore by the
protocol driver...... I really get confused to this "full" duplex
transmittion. Could anyone tell me the real situation?
Also, in your pxa_spi driver, you always read before write in the
interrupt handler. If rx and tx is pointing to the same buffer,
reading data will always overwrite the tx data. why not write before
read?
I am new to SPI , and with this loose bus structure i could not find a
good reference for me to study deeper. Do you have any reference
source?
Thanks for concern and patience
Mike,Lee
On 10/28/05, Stephen Street <[email protected]> wrote:
> On Thu, 2005-10-27 at 19:33 +0800, Mike Lee wrote:
> > Dear Stepen
> > I am now writing another controller driver by david's framework.
> > But leak of debuging layer, could your loopback driver serve for this
> > purpose and how could i use it?
> >
>
> The file pxa2xx_loopback.c should be controller independent, but it does
> require that the hardware (in my case the PXA255 NSSP) support a
> loopback mode (i.e. tx connected to rx).
>
> To create a loopback device for driver you should include:
>
> static struct pxa2xx_spi_chip loopback_chip_info = {
> .mode = SPI_MODE_3,
> .tx_threshold = 12,
> .rx_threshold = 4,
> .dma_burst_size = 8,
> .bits_per_word = 8,
> .timeout_microsecs = 64,
> .enable_loopback = 1,
> };
>
> static struct spi_board_info streetracer_spi_board_info[] __initdata = {
> {
> .modalias = "loopback",
> .max_speed_hz = 3686400,
> .bus_num = 2,
> .chip_select = 3,
> .controller_data = &loopback_chip_info,
> },
> };
>
> in your board init code and install the module per your configuration.
> Anything written to /dev/slp23 will be echoed back to /dev/slp23 via the
> "SPI controller".
>
> Hope this helps!
>
> -Stephen
>
>
>
--
-----------------------------------------------
Mike Lee
On Sun, 2005-10-30 at 02:25 +0800, Mike Lee wrote:
> Thanks for your info. I am working hard with that.
>
Sorry for delayed response.
I'm assuming the you board is configured with the PXA as a SPI master
and the chip you are talking to is the slave (part number of the slave
chip would be helpful). Which SSP port are you trying to use?
> What in my mind is that SPI clk is only triggered by master and master
> read/write at the same time. But how could master know the read data
> is valid?
The master determines when the data is valid by asserting the slave chip
select.
> is it determine by the protocol driver?
Yes, but setup by the board init code.
> In a case, when
> master place a tx-only msg to the controller driver, and slave request
> to send in the middle of the transaction. The data from slave will
> push into the rx buffer of the tx-only msg and finally ignore by the
> protocol driver......
I do not believe this can happen when the PXA is in master mode and the
chip is a slave.
> I really get confused to this "full" duplex
> transmittion. Could anyone tell me the real situation?
>
The PXA SPI master setup only generates a spi data clock when there is
data in the tx fifo thus to read you must write first even if you only
send zeros. The SSP port can be setup to use a free running clock but
this leads to coordination problems with the chip select and is a
limited use when the PXA is the SPI master. To make this work you will
need to allow the SSP to manage the chip select, see the PXA
documentation.
I would strongly recommend attaching a logic analyzer to the SSP port
you are trying to get working.
>
> Also, in your pxa_spi driver, you always read before write in the
> interrupt handler. If rx and tx is pointing to the same buffer,
> reading data will always overwrite the tx data. why not write before
> read?
See above. Must write to read.
>
> I am new to SPI , and with this loose bus structure i could not find a
> good reference for me to study deeper. Do you have any reference
> source?
I never found any documentation on the net, usually the data-sheet for
the chip you are talking to specifies the SPI requirements (master,
slave, clocks, chip selects, etc). Can you send me a link to the chip?
-Stephen
Hi stephen
On 11/2/05, Stephen Street <[email protected]> wrote:
> On Sun, 2005-10-30 at 02:25 +0800, Mike Lee wrote:
> > Also, in your pxa_spi driver, you always read before write in the
> > interrupt handler. If rx and tx is pointing to the same buffer,
> > reading data will always overwrite the tx data. why not write before
> > read?
> See above. Must write to read.
I found that i misunderstand your coding. i got your idea.
> I never found any documentation on the net, usually the data-sheet for
> the chip you are talking to specifies the SPI requirements (master,
> slave, clocks, chip selects, etc). Can you send me a link to the chip?
>
It is too bad that no good reference document for development.
I am now using i.MX Soc which is ARM920 core and embedded 2 SPI bus.
You can download the datasheet from freescale web site.
Actually, i am now available to use your loopback driver on my SPI
controller driver. But only limited to PIO mode, and i get stuck on
DMA mode because of trigger problem of SPI module, I am now trying
very hard to solve that.
In my driver, there is a little bug that i could not rmmod the driver.
it will stop at unregistering device. Below is a debug msg dump. it
seem to stop at down_write(&device_subsys.rwsem) in device_del
------------------------------------------------------------
/mnt/tmpfs/tmp # insmod imx_spi.ko
bus platform: add driver imx-spi
CLASS: registering class device: ID = 'spi1'
imx-spi imx-spi.0: registered master spi1
spi1.1-loopback: setup finish
DEV: registering device: ID = 'spi1.1-loopback'
bus spi: add device spi1.1-loopback
imx-spi imx-spi.0: registered child spi1.1-loopback
bound device 'imx-spi.0' to driver 'imx-spi'
CLASS: registering class device: ID = 'spi2'
imx-spi imx-spi.1: registered master spi2
bound device 'imx-spi.1' to driver 'imx-spi'
/mnt/tmpfs/tmp # rmmod imx_spi.ko
bus platform: remove driver imx-spi
DEV: Unregistering device. ID = 'spi1.1-loopback'
------------------------------------------------------------
Some question on SPI subsystem:
I really get confused on the structure on the whole system. e.g. the
platform_data and controller_data in board_info. What are thier
purposes?
Also i found that spi_register_board_info is declared as __init, that
mean i can not register board info as a module, is it because there is
no 'real' probe on SPI bus? (this comsume me time to reflash my board
to debug)
I know that SDIO could have a SPI mode. Could SPI subsystem be used to
control SDIO device?
Sorry for my annoying questions.
Thanks for helping
Mike,Lee
> Some question on SPI subsystem:
> I really get confused on the structure on the whole system. e.g. the
> platform_data and controller_data in board_info. What are thier
> purposes?
Normally you'd just use platform_data ... which might hold things like
resistance and capacitance settings for that particular board; or details
about which SPI chip variant was present. It gets stored in the struct
device (spi_device->dev) as "platform_data".
Stephen persuaded me to add controller_data too, which is stored in
"struct spi_device". His PXA SPI controller driver uses that for a
structure holding what I'd call DMA tuning information, plus a function
that tweaks the GPIO used for a chipselect. Treat it as readonly.
Controller drivers can have two different kinds of state in each
spi_device: static, and dynamic/runtime. The names used for them
are IMO very confusing (platform_data and controller_data) since
they don't mean the same as those names do in board_info. I'd take
a patch to provide better names for those two. :)
> Also i found that spi_register_board_info is declared as __init, that
> mean i can not register board info as a module, is it because there is
> no 'real' probe on SPI bus?
Combine that lack of probe with the fact that SPI isn't hotpluggable,
and that's pretty much why. You need a table of SPI devices present
on a given board; that boardinfo should be defined just once, in the
relevant arch/arm/mach-imx/board-*.c file. The table never changes,
so there'd be no point in code to change it after board init is done.
(Other than wasting some memory!)
You could also use spi_new_device()/spi_unregister_device() if for some
reason you want to define new boardinfo in a module. You'd need some
out-of-band data to determine what devices are present.
- Dave
p.s. There's a minor refresh to the SPI code coming up soonish; just
fine tuning, and catching up to 2.6.14-git which has removed a
pointless parameter from device_driver.{suspend,resume}() calls.
On Thu, 2005-11-03 at 17:37 +0800, Mike Lee wrote:
> Actually, i am now available to use your loopback driver on my SPI
> controller driver. But only limited to PIO mode, and i get stuck on
> DMA mode because of trigger problem of SPI module, I am now trying
> very hard to solve that.
I had problems get the DMA buffers aligned correctly.
>
> In my driver, there is a little bug that i could not rmmod the driver.
> it will stop at unregistering device. Below is a debug msg dump. it
> seem to stop at down_write(&device_subsys.rwsem) in device_del
> ------------------------------------------------------------
> /mnt/tmpfs/tmp # insmod imx_spi.ko
> bus platform: add driver imx-spi
> CLASS: registering class device: ID = 'spi1'
> imx-spi imx-spi.0: registered master spi1
> spi1.1-loopback: setup finish
> DEV: registering device: ID = 'spi1.1-loopback'
> bus spi: add device spi1.1-loopback
> imx-spi imx-spi.0: registered child spi1.1-loopback
> bound device 'imx-spi.0' to driver 'imx-spi'
> CLASS: registering class device: ID = 'spi2'
> imx-spi imx-spi.1: registered master spi2
> bound device 'imx-spi.1' to driver 'imx-spi'
> /mnt/tmpfs/tmp # rmmod imx_spi.ko
> bus platform: remove driver imx-spi
> DEV: Unregistering device. ID = 'spi1.1-loopback'
>
> ------------------------------------------------------------
>
I'm not sure what the problem is. What kernel version are you using?
Changes are continuing in the driver model which can affect module
unloading.
>
> Some question on SPI subsystem:
> I really get confused on the structure on the whole system. e.g. the
> platform_data and controller_data in board_info. What are thier
> purposes?
A little confusing I agree. Keep in mind that in a typical SPI setup
there is one SPI master (i.MX) connected to one or more SPI slaves
(external chips). You will have a device_driver for the master and a
device_driver for each SPI slave. So...
The platform_data contains configuration information for the SPI slave
device_driver and the controller data contain configuration information
for the SPI master device_driver which is specific to an individual SPI
Slave. This allows the SPI master to configure itself differently on
the fly, for each chip connected to it.
Here is code snippet from my board init
static struct resource pxa_spi_resources[] = {
[0] = {
.start = __PREG(SSCR0_P(2)),
.end = __PREG(SSCR0_P(2)) + 0x2c,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_NSSP,
.end = IRQ_NSSP,
.flags = IORESOURCE_IRQ,
},
};
static struct pxa2xx_spi_master pxa_nssp_master_info = {
.ssp_type = PXA25x_NSSP,
.clock_enable = CKEN9_NSSP,
.num_chipselect = 4,
.enable_dma = 1,
};
static struct platform_device pxa_spi_ssp = {
.name = "pxa2xx-spi-ssp",
.id = 2,
.resource = pxa_spi_resources,
.num_resources = ARRAY_SIZE(pxa_spi_resources),
.dev = {
.platform_data = &pxa_nssp_master_info,
},
};
static struct cs8415a_platform_data cs8415a_platform_info = {
.enabled = 0,
.muted = 1,
.channel = 0,
.pll_lock_delay = 100,
.irq_flags = SA_SHIRQ,
.mask_interrupt = cs8415a_mask_interrupt,
.unmask_interrupt = cs8415a_unmask_interrupt,
.service_requested = cs8415a_service_requested,
};
static struct pxa2xx_spi_chip cs8415a_chip_info = {
.tx_threshold = 8,
.rx_threshold = 8,
.dma_burst_size = 8,
.timeout_microsecs = 64,
.cs_control = cs8415a_cs_control,
};
static struct pxa2xx_spi_chip cs8405a_chip_info = {
.tx_threshold = 12,
.rx_threshold = 4,
.dma_burst_size = 8,
.timeout_microsecs = 64,
};
static struct pxa2xx_spi_chip cs4341_chip_info = {
.tx_threshold = 12,
.rx_threshold = 4,
.timeout_microsecs = 128,
};
static struct pxa2xx_spi_chip loopback_chip_info = {
.tx_threshold = 12,
.rx_threshold = 4,
.dma_burst_size = 8,
.timeout_microsecs = 64,
.enable_loopback = 1,
};
static struct spi_board_info streetracer_spi_board_info[] __initdata = {
{
.modalias = "cs8415a",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 0,
.platform_data = &cs8415a_platform_info,
.controller_data = &cs8415a_chip_info,
.irq = STREETRACER_APCI_IRQ,
},
{
.modalias = "cs8405a",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 1,
.controller_data = &cs8405a_chip_info,
.irq = STREETRACER_APCI_IRQ,
},
{
.modalias = "cs4341",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 2,
.controller_data = &cs4341_chip_info,
},
{
.modalias = "loopback",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 3,
.controller_data = &loopback_chip_info,
},
};
> Also i found that spi_register_board_info is declared as __init, that
> mean i can not register board info as a module, is it because there is
> no 'real' probe on SPI bus? (this comsume me time to reflash my board
> to debug)
Correct no real probe, but.... see
extern struct spi_device *spi_new_device(struct spi_master *, struct spi_board_info *);
in spi.h for creating new spi_device on the fly or in a module.
> I know that SDIO could have a SPI mode. Could SPI subsystem be used to
> control SDIO device?
No clue, sorry.
> Sorry for my annoying questions.
No problem! Happy to help out. What external chip are you talking to?
-Stephen
On Thu, 2005-11-03 at 16:15 -0800, David Brownell wrote:
> Stephen persuaded me to add controller_data too, which is stored in
> "struct spi_device". His PXA SPI controller driver uses that for a
> structure holding what I'd call DMA tuning information, plus a function
> that tweaks the GPIO used for a chipselect. Treat it as readonly.
>
> Controller drivers can have two different kinds of state in each
> spi_device: static, and dynamic/runtime. The names used for them
> are IMO very confusing (platform_data and controller_data) since
> they don't mean the same as those names do in board_info. I'd take
> a patch to provide better names for those two. :)
I agree, the names are bad... How about modifying struct spi_board_info
to
struct spi_board_info {
...
void *slave_data;
void *master_data;
...
};
-Stephen
On Friday 04 November 2005 10:52 am, Stephen Street wrote:
> On Thu, 2005-11-03 at 16:15 -0800, David Brownell wrote:
> > Stephen persuaded me to add controller_data too, which is stored in
> > "struct spi_device". His PXA SPI controller driver uses that for a
> > structure holding what I'd call DMA tuning information, plus a function
> > that tweaks the GPIO used for a chipselect. Treat it as readonly.
> >
> > Controller drivers can have two different kinds of state in each
> > spi_device: static, and dynamic/runtime. The names used for them
> > are IMO very confusing (platform_data and controller_data) since
> > they don't mean the same as those names do in board_info. I'd take
> > a patch to provide better names for those two. :)
>
> I agree, the names are bad... How about modifying struct spi_board_info
> to
>
> struct spi_board_info {
> ...
>
> void *slave_data;
> void *master_data;
I'd be confused. They're both slave-specific ... and owned by
the master/controller driver.
Instead, how about "controller_data" changing to match its role
in board_info (static info, not dynamic), and "platform_data"
becoming something like "controller_state"?
- Dave
> ...
> };
>
> -Stephen
>
>
>
>
--- Stephen Street <[email protected]> wrote:
> On Thu, 2005-11-03 at 17:37 +0800, Mike Lee wrote:
> > Actually, i am now available to use your loopback driver on my SPI
> > controller driver. But only limited to PIO mode, and i get stuck on
> > DMA mode because of trigger problem of SPI module, I am now trying
> > very hard to solve that.
>
> I had problems get the DMA buffers aligned correctly.
>
> >
> > In my driver, there is a little bug that i could not rmmod the driver.
> > it will stop at unregistering device. Below is a debug msg dump. it
> > seem to stop at down_write(&device_subsys.rwsem) in device_del
> > ------------------------------------------------------------
> > /mnt/tmpfs/tmp # insmod imx_spi.ko
> > bus platform: add driver imx-spi
> > CLASS: registering class device: ID = 'spi1'
> > imx-spi imx-spi.0: registered master spi1
> > spi1.1-loopback: setup finish
> > DEV: registering device: ID = 'spi1.1-loopback'
> > bus spi: add device spi1.1-loopback
> > imx-spi imx-spi.0: registered child spi1.1-loopback
> > bound device 'imx-spi.0' to driver 'imx-spi'
> > CLASS: registering class device: ID = 'spi2'
> > imx-spi imx-spi.1: registered master spi2
> > bound device 'imx-spi.1' to driver 'imx-spi'
> > /mnt/tmpfs/tmp # rmmod imx_spi.ko
> > bus platform: remove driver imx-spi
> > DEV: Unregistering device. ID = 'spi1.1-loopback'
> >
> > ------------------------------------------------------------
> >
> I'm not sure what the problem is. What kernel version are you using?
> Changes are continuing in the driver model which can affect module
> unloading.
I'm working on this problem. The first issue is that the spi_unregister_master calls
device_for_each_child which takes the devices lock and then in the __unregister routine goes on to
unregister a device which also tries to take the devices lock and hey presto your stuck :(. This
problem was easy to solve, but I now seem to be stuck in device_unregister where it gets and then
puts the reference to the device. It looks like someone is still holding the spi_master but I
haven't had time to find out where (or if problem is in my adapter driver ;).
An update on your work would be great David, I have some comments to go with your last patch but
wanted to finish my adapter driver and spi_device driver first.
Mark
>
> >
> > Some question on SPI subsystem:
> > I really get confused on the structure on the whole system. e.g. the
> > platform_data and controller_data in board_info. What are thier
> > purposes?
>
> A little confusing I agree. Keep in mind that in a typical SPI setup
> there is one SPI master (i.MX) connected to one or more SPI slaves
> (external chips). You will have a device_driver for the master and a
> device_driver for each SPI slave. So...
>
> The platform_data contains configuration information for the SPI slave
> device_driver and the controller data contain configuration information
> for the SPI master device_driver which is specific to an individual SPI
> Slave. This allows the SPI master to configure itself differently on
> the fly, for each chip connected to it.
>
> Here is code snippet from my board init
>
> static struct resource pxa_spi_resources[] = {
> [0] = {
> .start = __PREG(SSCR0_P(2)),
> .end = __PREG(SSCR0_P(2)) + 0x2c,
> .flags = IORESOURCE_MEM,
> },
> [1] = {
> .start = IRQ_NSSP,
> .end = IRQ_NSSP,
> .flags = IORESOURCE_IRQ,
> },
> };
>
> static struct pxa2xx_spi_master pxa_nssp_master_info = {
> .ssp_type = PXA25x_NSSP,
> .clock_enable = CKEN9_NSSP,
> .num_chipselect = 4,
> .enable_dma = 1,
> };
>
> static struct platform_device pxa_spi_ssp = {
> .name = "pxa2xx-spi-ssp",
> .id = 2,
> .resource = pxa_spi_resources,
> .num_resources = ARRAY_SIZE(pxa_spi_resources),
> .dev = {
> .platform_data = &pxa_nssp_master_info,
> },
> };
>
> static struct cs8415a_platform_data cs8415a_platform_info = {
> .enabled = 0,
> .muted = 1,
> .channel = 0,
> .pll_lock_delay = 100,
> .irq_flags = SA_SHIRQ,
> .mask_interrupt = cs8415a_mask_interrupt,
> .unmask_interrupt = cs8415a_unmask_interrupt,
> .service_requested = cs8415a_service_requested,
> };
>
> static struct pxa2xx_spi_chip cs8415a_chip_info = {
> .tx_threshold = 8,
> .rx_threshold = 8,
> .dma_burst_size = 8,
> .timeout_microsecs = 64,
> .cs_control = cs8415a_cs_control,
> };
>
> static struct pxa2xx_spi_chip cs8405a_chip_info = {
> .tx_threshold = 12,
> .rx_threshold = 4,
> .dma_burst_size = 8,
> .timeout_microsecs = 64,
> };
>
> static struct pxa2xx_spi_chip cs4341_chip_info = {
> .tx_threshold = 12,
> .rx_threshold = 4,
> .timeout_microsecs = 128,
> };
>
> static struct pxa2xx_spi_chip loopback_chip_info = {
> .tx_threshold = 12,
> .rx_threshold = 4,
> .dma_burst_size = 8,
> .timeout_microsecs = 64,
> .enable_loopback = 1,
> };
>
> static struct spi_board_info streetracer_spi_board_info[] __initdata = {
> {
> .modalias = "cs8415a",
> .max_speed_hz = 3686400,
> .bus_num = 2,
> .chip_select = 0,
> .platform_data = &cs8415a_platform_info,
> .controller_data = &cs8415a_chip_info,
> .irq = STREETRACER_APCI_IRQ,
> },
> {
> .modalias = "cs8405a",
> .max_speed_hz = 3686400,
> .bus_num = 2,
> .chip_select = 1,
> .controller_data = &cs8405a_chip_info,
> .irq = STREETRACER_APCI_IRQ,
> },
> {
> .modalias = "cs4341",
> .max_speed_hz = 3686400,
> .bus_num = 2,
> .chip_select = 2,
> .controller_data = &cs4341_chip_info,
> },
> {
> .modalias = "loopback",
> .max_speed_hz = 3686400,
> .bus_num = 2,
> .chip_select = 3,
> .controller_data = &loopback_chip_info,
> },
> };
>
> > Also i found that spi_register_board_info is declared as __init, that
> > mean i can not register board info as a module, is it because there is
> > no 'real' probe on SPI bus? (this comsume me time to reflash my board
> > to debug)
>
> Correct no real probe, but.... see
>
> extern struct spi_device *spi_new_device(struct spi_master *, struct spi_board_info *);
>
> in spi.h for creating new spi_device on the fly or in a module.
>
>
> > I know that SDIO could have a SPI mode. Could SPI subsystem be used to
> > control SDIO device?
>
> No clue, sorry.
>
> > Sorry for my annoying questions.
>
> No problem! Happy to help out. What external chip are you talking to?
>
> -Stephen
>
>
> -
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>
___________________________________________________________
Does your mail provider give you FREE antivirus protection?
Get Yahoo! Mail http://uk.mail.yahoo.com
On Fri, 2005-11-04 at 12:16 -0800, David Brownell wrote:
> I'd be confused. They're both slave-specific ... and owned by
> the master/controller driver.
I'm using the spi_board_info.platform_data to pass configuration
information to the spi_device (slave) driver and
spi_board_info.controller_data to pass SPI bus configuration for the
specific slave device. This allows different bus configurations for
each attached SPI device. The following is a concrete example:
/* CS8415A configuration information and board interface setup */
static struct cs8415a_platform_data cs8415a_platform_info = {
.enabled = 0,
.muted = 1,
.channel = 0,
.pll_lock_delay = 100,
.irq_flags = SA_SHIRQ,
.mask_interrupt = cs8415a_mask_interrupt,
.unmask_interrupt = cs8415a_unmask_interrupt,
.service_requested = cs8415a_service_requested,
};
/* PXA2XX SPI bus setup for CS8415A */
static struct pxa2xx_spi_chip cs8415a_chip_info = {
.tx_threshold = 12,
.rx_threshold = 4,
.dma_burst_size = 8,
.timeout_microsecs = 64,
.cs_control = cs8415a_cs_control,
};
static struct spi_board_info streetracer_spi_board_info[] __initdata = {
{
.modalias = "cs8415a",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 0,
.platform_data = &cs8415a_platform_info,
.controller_data = &cs8415a_chip_info,
.irq = STREETRACER_APCI_IRQ,
},
};
IMHO the confusion is coming from the fact that struct spi_board_info is
being used to pass related, but implementation dependent, configuration
information to both the master and the slave simultaneously. Maybe we
are asking spi_board_info to carry to much information?
> Instead, how about "controller_data" changing to match its role
> in board_info (static info, not dynamic), and "platform_data"
> becoming something like "controller_state"?
If you mean spi_device.controller_data becomes
spi_device.controller_state, yes!
-Stephen
On Friday 04 November 2005 3:38 pm, Stephen Street wrote:
> On Fri, 2005-11-04 at 12:16 -0800, David Brownell wrote:
> > I'd be confused. They're both slave-specific ... and owned by
> > the master/controller driver.
>
> I'm using the spi_board_info.platform_data to pass configuration
> information to the spi_device (slave) driver and
> spi_board_info.controller_data to pass SPI bus configuration for the
> specific slave device. This allows different bus configurations for
> each attached SPI device.
That's not what I thought we were talking about. Stepping back, what's
confusing is that there are three different kinds of per-device data,
and the names are used inconsistently:
spi_device.dev.platform_data ... from board_info.platform_data
This is for the driver of the spi_device ... board-specific
data that'd be the same for PXA or OMAP or PPC805 or whatever
spi_device.platform_data ... from board_info.controller_data
This is static controller-specific information, which you were
using for things like chipselect functions and fifo tuning.
(I had proposed to name this as "controller_data" in the
spi_device too.)
spi_device.controller_data ... runtime state for the controller
This is dynamic controller-specific information, which you
were using for things like copies of register settings that
set up clock speed and SPI mode for the device.
(I had proposed to rename this as "controller_state".)
I don't know how much board_info.controller_data would be used by other
kinds of SPI controller (that is, not PXA), but it seems to me that could
also be stored in platform_data for the controller itself (if we wanted
to simplify things).
> IMHO the confusion is coming from the fact that struct spi_board_info is
> being used to pass related, but implementation dependent, configuration
> information to both the master and the slave simultaneously. Maybe we
> are asking spi_board_info to carry to much information?
It'd probably be a lot simpler to get rid of board_info.controller_data
since that could just as easily be passed through the controller's own
platform_data ...
> > Instead, how about "controller_data" changing to match its role
> > in board_info (static info, not dynamic), and "platform_data"
> > becoming something like "controller_state"?
>
> If you mean spi_device.controller_data becomes
> spi_device.controller_state, yes!
Good. We agree on one half of my proposal. :)
Now as for board_info.controller_data and its clone in spi_device,
how about if I just delete that ... so that it'd be provided in the
platform_device.dev.platform_data for the controller? That'd also
let you substitute a typed pointer for a void* one, usually a sign
of goodness.
- Dave
On Fri, 2005-11-04 at 16:54 -0800, David Brownell wrote:
> That's not what I thought we were talking about. Stepping back, what's
> confusing is that there are three different kinds of per-device data,
> and the names are used inconsistently:
>
> spi_device.dev.platform_data ... from board_info.platform_data
> This is for the driver of the spi_device ... board-specific
> data that'd be the same for PXA or OMAP or PPC805 or whatever
>
> spi_device.platform_data ... from board_info.controller_data
> This is static controller-specific information, which you were
> using for things like chipselect functions and fifo tuning.
> (I had proposed to name this as "controller_data" in the
> spi_device too.)
Yes.
>
> spi_device.controller_data ... runtime state for the controller
> This is dynamic controller-specific information, which you
> were using for things like copies of register settings that
> set up clock speed and SPI mode for the device.
> (I had proposed to rename this as "controller_state".)
>
My understanding also. Let's at least do the renames.
> Now as for board_info.controller_data and its clone in spi_device,
> how about if I just delete that ... so that it'd be provided in the
> platform_device.dev.platform_data for the controller? That'd also
> let you substitute a typed pointer for a void* one, usually a sign
> of goodness.
>
This is where I am having the problem. I'm resisting removing the
board_info.controller_data (or what ever we decide to call it) because
the current code makes it easy to the have different master settings
(fifo threshold, chip select control) for each spi_device attached to
the master. If we move this to the platform_device.dev.platform_data
this then the master will have to maintain a table indexed by the
chip_select to track the per spi_device master configuration
information.
On the other hand if this is too confusing, then let make the API
simpler and the implementation more complex. Your call.
Bigger code snippet from my board init.
static struct cs8415a_platform_data cs8415a_platform_info = {
.enabled = 0,
.muted = 1,
.channel = 0,
.pll_lock_delay = 100,
.irq_flags = SA_SHIRQ,
.mask_interrupt = cs8415a_mask_interrupt,
.unmask_interrupt = cs8415a_unmask_interrupt,
.service_requested = cs8415a_service_requested,
};
static struct pxa2xx_spi_chip cs8415a_chip_info = {
.tx_threshold = 12,
.rx_threshold = 4,
.dma_burst_size = 8,
.timeout_microsecs = 64,
.cs_control = cs8415a_cs_control,
};
static struct pxa2xx_spi_chip cs8405a_chip_info = {
.tx_threshold = 12,
.rx_threshold = 4,
.dma_burst_size = 8,
.timeout_microsecs = 64,
.cs_control = cs8405a_cs_control,
};
static struct pxa2xx_spi_chip cs4341_chip_info = {
.tx_threshold = 8,
.rx_threshold = 8,
.timeout_microsecs = 1000,
.cs_control = cs4341_cs_control,
};
static struct spi_board_info streetracer_spi_board_info[] __initdata = {
{
.modalias = "cs8415a",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 0,
.platform_data = &cs8415a_platform_info,
.controller_data = &cs8415a_chip_info,
.irq = STREETRACER_APCI_IRQ,
},
{
.modalias = "cs8405a",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 1,
.controller_data = &cs8405a_chip_info,
.irq = STREETRACER_APCI_IRQ,
},
{
.modalias = "cs4341",
.max_speed_hz = 3686400,
.bus_num = 2,
.chip_select = 2,
.controller_data = &cs4341_chip_info,
},
};
Occupying some spare cycles is the idea that what we really need is the
ability to sub-class spi_device and spi_master via structure embedding.
This would be in the spirit of the 2.6 driver model and would map to the
platform_device model better. It would however, mean losing the
spi_board_info structure. Feel free to take a large hammer to me, if
this is really off base.
-Stephen
On Friday 04 November 2005 6:28 pm, Stephen Street wrote:
> My understanding also. Let's at least do the renames.
Consider it done.
I'll leave "controller_data" in board_info and spi_device too.
You're right ... plus, we need to let that be separate from the
controller platform_data for hotplugging usage, spi_new_device()
and such.
> Occupying some spare cycles is the idea that what we really need is the
> ability to sub-class spi_device and spi_master via structure embedding.
> This would be in the spirit of the 2.6 driver model and would map to the
> platform_device model better.
Not really. "Struture embedding" is used more by bus infrastruture than
by driver infrastructure ... and even there, it's more often done by
sharing storage. And spi_master supports that usage:
/* at the top of probe(dev) */
struct spi_master *master;
struct MYSOC_DATA *data;
master = spi_alloc_master(dev, sizeof *data);
if (!master)
return -ENODEV;
data = class_get_devdata(&master->cdev);
That's because spi_master is a class view of the underlying "dev",
used to access the controller hardware. (Likely a platform_device
created as part of board setup.)
A difference for normal spi_device nodes is that the drivers you'd
presumably want to "subclass" spi_device isn't responsible for
creating the device note. It might however create a class device
node, and that could end up looking much like that spi_master snippet.
- Dave
--- Mark Underwood <[email protected]> wrote:
>
> --- Stephen Street <[email protected]> wrote:
>
> > On Thu, 2005-11-03 at 17:37 +0800, Mike Lee wrote:
> > > Actually, i am now available to use your loopback driver on my SPI
> > > controller driver. But only limited to PIO mode, and i get stuck on
> > > DMA mode because of trigger problem of SPI module, I am now trying
> > > very hard to solve that.
> >
> > I had problems get the DMA buffers aligned correctly.
> >
> > >
> > > In my driver, there is a little bug that i could not rmmod the driver.
> > > it will stop at unregistering device. Below is a debug msg dump. it
> > > seem to stop at down_write(&device_subsys.rwsem) in device_del
> > > ------------------------------------------------------------
> > > /mnt/tmpfs/tmp # insmod imx_spi.ko
> > > bus platform: add driver imx-spi
> > > CLASS: registering class device: ID = 'spi1'
> > > imx-spi imx-spi.0: registered master spi1
> > > spi1.1-loopback: setup finish
> > > DEV: registering device: ID = 'spi1.1-loopback'
> > > bus spi: add device spi1.1-loopback
> > > imx-spi imx-spi.0: registered child spi1.1-loopback
> > > bound device 'imx-spi.0' to driver 'imx-spi'
> > > CLASS: registering class device: ID = 'spi2'
> > > imx-spi imx-spi.1: registered master spi2
> > > bound device 'imx-spi.1' to driver 'imx-spi'
> > > /mnt/tmpfs/tmp # rmmod imx_spi.ko
> > > bus platform: remove driver imx-spi
> > > DEV: Unregistering device. ID = 'spi1.1-loopback'
> > >
> > > ------------------------------------------------------------
> > >
> > I'm not sure what the problem is. What kernel version are you using?
> > Changes are continuing in the driver model which can affect module
> > unloading.
>
> I'm working on this problem. The first issue is that the spi_unregister_master calls
> device_for_each_child which takes the devices lock and then in the __unregister routine goes on
> to
> unregister a device which also tries to take the devices lock and hey presto your stuck :(. This
> problem was easy to solve, but I now seem to be stuck in device_unregister where it gets and
> then
> puts the reference to the device. It looks like someone is still holding the spi_master but I
> haven't had time to find out where (or if problem is in my adapter driver ;).
>
> An update on your work would be great David, I have some comments to go with your last patch but
> wanted to finish my adapter driver and spi_device driver first.
>
> Mark
OK, here is a patch to Davids spi.c which will hopefully fix the unregistering of spi_masters
problem and also adds an extra method required by platforms that want to register one or more
devices after arch init time.
--- spi-orig.c 2005-11-07 20:13:03.000000000 +0000
+++ spi.c 2005-11-07 20:12:35.000000000 +0000
@@ -315,6 +315,35 @@ static struct class spi_master_class = {
.release = spi_master_release,
};
+/* As spi devices that are registered after arch init time have to
+ * supply the spi_master pointer of the master that they sit on
+ * they need to use this function to get that pointer from the bus
+ * number
+ */
+struct spi_master* bus_num_to_master(u16 bus_num)
+{
+ struct spi_master *adap = NULL;
+ struct spi_master *tmp = NULL;
+ struct class_device *cdev;
+
+ down(&board_lock);
+ list_for_each_entry(cdev, &spi_master_class.children, node) {
+ tmp = container_of(cdev, struct spi_master, cdev);
+ printk("cdev = %p\n", cdev);
+ printk("tmp = %p\n", tmp);
+ printk("tmp->bus_num = %d, bus_num %d\n", tmp->bus_num, bus_num);
+ printk("tmp->num_chipselect = %d\n",tmp->num_chipselect);
+ if (tmp->bus_num == bus_num)
+ {
+ adap = tmp;
+ break;
+ }
+ }
+ up(&board_lock);
+ return adap;
+}
+EXPORT_SYMBOL(bus_num_to_master);
+
/**
* spi_alloc_master - allocate SPI master controller
@@ -418,8 +447,13 @@ static int __unregister(struct device *d
*/
void spi_unregister_master(struct spi_master *master)
{
- class_device_unregister(&master->cdev);
- (void) device_for_each_child(master->cdev.dev, NULL, __unregister);
+ struct device *dev = master->cdev.dev;
+ struct device *child, *next_ptr;
+
+ list_for_each_entry_safe(child, next_ptr, &dev->children, node) {
+ __unregister(child, NULL);
+ }
+ class_device_unregister(&master->cdev);
}
EXPORT_SYMBOL_GPL(spi_unregister_master);
Unfortunately I'm still having a problem unregistering my driver, but I think this is due to the
driver not the SPI subsystem. If someone could test that this patch fixes the unregister problem
and let me know that would be great.
Mark
>
> >
> > >
> > > Some question on SPI subsystem:
> > > I really get confused on the structure on the whole system. e.g. the
> > > platform_data and controller_data in board_info. What are thier
> > > purposes?
> >
> > A little confusing I agree. Keep in mind that in a typical SPI setup
> > there is one SPI master (i.MX) connected to one or more SPI slaves
> > (external chips). You will have a device_driver for the master and a
> > device_driver for each SPI slave. So...
> >
> > The platform_data contains configuration information for the SPI slave
> > device_driver and the controller data contain configuration information
> > for the SPI master device_driver which is specific to an individual SPI
> > Slave. This allows the SPI master to configure itself differently on
> > the fly, for each chip connected to it.
> >
> > Here is code snippet from my board init
> >
> > static struct resource pxa_spi_resources[] = {
> > [0] = {
> > .start = __PREG(SSCR0_P(2)),
> > .end = __PREG(SSCR0_P(2)) + 0x2c,
> > .flags = IORESOURCE_MEM,
> > },
> > [1] = {
> > .start = IRQ_NSSP,
> > .end = IRQ_NSSP,
> > .flags = IORESOURCE_IRQ,
> > },
> > };
> >
> > static struct pxa2xx_spi_master pxa_nssp_master_info = {
> > .ssp_type = PXA25x_NSSP,
> > .clock_enable = CKEN9_NSSP,
> > .num_chipselect = 4,
> > .enable_dma = 1,
> > };
> >
> > static struct platform_device pxa_spi_ssp = {
> > .name = "pxa2xx-spi-ssp",
> > .id = 2,
> > .resource = pxa_spi_resources,
> > .num_resources = ARRAY_SIZE(pxa_spi_resources),
> > .dev = {
> > .platform_data = &pxa_nssp_master_info,
> > },
> > };
> >
> > static struct cs8415a_platform_data cs8415a_platform_info = {
> > .enabled = 0,
> > .muted = 1,
> > .channel = 0,
> > .pll_lock_delay = 100,
> > .irq_flags = SA_SHIRQ,
> > .mask_interrupt = cs8415a_mask_interrupt,
> > .unmask_interrupt = cs8415a_unmask_interrupt,
> > .service_requested = cs8415a_service_requested,
> > };
> >
> > static struct pxa2xx_spi_chip cs8415a_chip_info = {
> > .tx_threshold = 8,
> > .rx_threshold = 8,
> > .dma_burst_size = 8,
> > .timeout_microsecs = 64,
> > .cs_control = cs8415a_cs_control,
> > };
> >
> > static struct pxa2xx_spi_chip cs8405a_chip_info = {
> > .tx_threshold = 12,
> > .rx_threshold = 4,
> > .dma_burst_size = 8,
> > .timeout_microsecs = 64,
> > };
> >
> > static struct pxa2xx_spi_chip cs4341_chip_info = {
> > .tx_threshold = 12,
> > .rx_threshold = 4,
> > .timeout_microsecs = 128,
> > };
> >
> > static struct pxa2xx_spi_chip loopback_chip_info = {
> > .tx_threshold = 12,
> > .rx_threshold = 4,
> > .dma_burst_size = 8,
> > .timeout_microsecs = 64,
> > .enable_loopback = 1,
> > };
> >
> > static struct spi_board_info streetracer_spi_board_info[] __initdata = {
> > {
> > .modalias = "cs8415a",
> > .max_speed_hz = 3686400,
> > .bus_num = 2,
> > .chip_select = 0,
> > .platform_data = &cs8415a_platform_info,
> > .controller_data = &cs8415a_chip_info,
> > .irq = STREETRACER_APCI_IRQ,
> > },
> > {
> > .modalias = "cs8405a",
> > .max_speed_hz = 3686400,
> > .bus_num = 2,
> > .chip_select = 1,
> > .controller_data = &cs8405a_chip_info,
> > .irq = STREETRACER_APCI_IRQ,
> > },
> > {
> > .modalias = "cs4341",
> > .max_speed_hz = 3686400,
> > .bus_num = 2,
> > .chip_select = 2,
> > .controller_data = &cs4341_chip_info,
> > },
> > {
> > .modalias = "loopback",
> > .max_speed_hz = 3686400,
> > .bus_num = 2,
> > .chip_select = 3,
> > .controller_data = &loopback_chip_info,
> > },
> > };
> >
> > > Also i found that spi_register_board_info is declared as __init, that
> > > mean i can not register board info as a module, is it because there is
> > > no 'real' probe on SPI bus? (this comsume me time to reflash my board
> > > to debug)
> >
> > Correct no real probe, but.... see
> >
> > extern struct spi_device *spi_new_device(struct spi_master *, struct spi_board_info *);
> >
> > in spi.h for creating new spi_device on the fly or in a module.
> >
> >
> > > I know that SDIO could have a SPI mode. Could SPI subsystem be used to
> > > control SDIO device?
> >
> > No clue, sorry.
> >
> > > Sorry for my annoying questions.
> >
> > No problem! Happy to help out. What external chip are you talking to?
> >
> > -Stephen
> >
> >
> > -
> > To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> > the body of a message to [email protected]
> > More majordomo info at http://vger.kernel.org/majordomo-info.html
> > Please read the FAQ at http://www.tux.org/lkml/
> >
>
>
>
>
> ___________________________________________________________
> Does your mail provider give you FREE antivirus protection?
> Get Yahoo! Mail http://uk.mail.yahoo.com
> -
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>
___________________________________________________________
Does your mail provider give you FREE antivirus protection?
Get Yahoo! Mail http://uk.mail.yahoo.com