[PATCH/RFC 1/2] simple SPI controller implementation on PXA2xx SSP port

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a prototype 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 interrupt driven transfers.
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.
 Internal NSSP chip select is not support (i.e. NSSPSRFM)

---- snip ----
 drivers/spi/Kconfig                       |   12 
 drivers/spi/Makefile                      |    2 
 drivers/spi/pxa2xx_spi_ssp.c              |  741 ++++++++++++++++++++++++++++++
 include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h |   36 +
 4 files changed, 791 insertions(+)

--- linux-2.6.12-spi/drivers/spi/Kconfig 2005-10-04 14:07:18.000000000 -0700
+++ linux-2.6.12-spi-pxa/drivers/spi/Kconfig 2005-10-04 14:00:12.279449000 -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,12 @@
 comment "SPI Protocol Masters"
 
 
+config SPI_CS8415A
+       tristate "CS8415A SPD/IF decoder"
+       help
+         This chip provides an 8 channel SPD/IF switcher with complete
+         SPD/IF decoding.
+
 #
 # Add new SPI protocol masters in alphabetical order above this line
 #
--- linux-2.6.12-spi/drivers/spi/Makefile 2005-10-04 14:07:18.000000000 -0700
+++ linux-2.6.12-spi-pxa/drivers/spi/Makefile 2005-10-04 14:00:12.279449000 -0700
@@ -11,9 +11,11 @@
 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_CS8415A)              += cs8415a.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-04 12:50:10.699272000 -0700
@@ -0,0 +1,741 @@
+/*
+ * 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 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). NEED TO ADD
+ * PXA SPECIFIED INITIALIZATION INFORMATION.
+ * 
+ * This follow code snippet demostrates a sample board configuration using
+ * the PXA255 NSSP port connect to a CS8415A chip via GPIO chip select 2.
+ * 
+ * static struct cs8415a_platform_data cs8415a_platform_info = {
+ * .enabled = 1,
+ * .muted = 1,
+ * .channel = 0,
+ * .mask_interrupt = cs8415a_mask_interrupt,
+ * .unmask_interrupt = cs8415a_unmask_interrupt,
+ * .service_requested = cs8415a_service_requested,
+ * };
+ *
+ * static struct pxa2xx_spi_chip cs8415a_chip_info = {
+ * .mode = SPI_MODE_3,
+ * .tx_threshold = 12,
+ * .rx_threshold = 4,
+ * .bits_per_word = 8,
+ * .chip_select_gpio = 2,
+ * .timeout_microsecs = 64,
+ * };
+ *
+ * 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,
+ * },
+ * };
+ *
+ * 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,
+ * };
+ *
+ * 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(streetracer_spi_board_info, 
+ *     ARRAY_SIZE(streetracer_spi_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 interrupt driven transfers.
+ * 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.
+ *  Internal NSSP chip select is not support (i.e. NSSPSRFM)
+ *  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 <asm/io.h>
+#include <asm/irq.h>
+#include <asm/hardware.h>
+#include <asm/delay.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 GET_IRQ_STATUS(x) (__REG(sssr)&(SSSR_TINT|SSSR_RFS|SSSR_TFS|SSSR_ROR))
+
+struct transfer_state {
+ int index;
+ int len;
+ u32 gpio;
+ 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);
+};
+
+struct master_data {
+ spinlock_t lock;
+ struct spi_master *master;
+ struct list_head queue;
+ struct tasklet_struct pump_messages;
+ struct tasklet_struct pump_transfers;
+ struct spi_message* cur_msg;
+ struct transfer_state cur_state;
+ 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;
+ u16 cs_gpio;
+ u32 timeout;
+ u8 n_bytes;
+ void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state);
+ void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state);
+};
+
+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;
+ 
+ pr_debug("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 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 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;
+ u32 sssr = drv_data->sssr;
+ u32 ssdr = drv_data->ssdr;
+ u32 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->ssto;
+ u32 irq_status;
+ struct spi_message *msg;
+ 
+ if (!drv_data->cur_msg || !drv_data->cur_msg->state) {
+  printk(KERN_ERR "pxs2xx_spi_ssp: bad message or message "
+    "state in interrupt handler\n");
+ }
+ state = (struct transfer_state *)drv_data->cur_msg->state;
+ msg = drv_data->cur_msg;
+
+ 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 "fifo overun: "
+     "index=%d tx_len=%d rx_len%d\n", 
+     state->index, 
+     (state->tx_end - state->tx), 
+     (state->rx_end - state->rx)); 
+
+   state->index = -2;
+   tasklet_schedule(&drv_data->pump_transfers);
+
+   return IRQ_HANDLED;
+  }
+
+  
+  /* Pump data */
+  state->read(sssr, ssdr, state);
+  state->write(sssr, ssdr, state);
+  
+  if ((irq_status & SSSR_TINT) || (state->rx <= state->rx_end)) {
+   
+   /* Look for false positive timeout */
+   if (state->rx < state->rx_end) {
+    __REG(sssr) = SSSR_TINT;
+    break;
+   }
+   
+   /* 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) 
+    /* Fix me, need to handle cs polarity */
+    GPSR(state->gpio) = GPIO_bit(state->gpio);
+
+   /* Schedule transfer tasklet */
+   ++state->index;
+   tasklet_schedule(&drv_data->pump_transfers);
+   
+   return IRQ_HANDLED;
+  }
+ }
+ 
+ return IRQ_HANDLED;
+} 
+
+static void pump_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 sscr1 = drv_data->sscr1;
+ u32 ssto = drv_data->ssto;
+ 
+ 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;
+  save_state(drv_data, chip);
+
+  tasklet_schedule(&drv_data->pump_messages);  
+  
+  return;
+ }
+
+ /* Handle end of message */
+ if (state->index == message->n_transfer) {
+  
+  if (!message->transfers[state->index].cs_change) 
+   /* Fix me, need to handle cs polarity */
+   GPSR(state->gpio) = GPIO_bit(state->gpio);
+   
+  message->status = 0;
+  if (message->complete)
+   message->complete(message->context);
+
+  drv_data->cur_msg = NULL;
+  save_state(drv_data, chip);
+
+  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 */ 
+ transfer = message->transfers + state->index;
+ state->gpio = chip->cs_gpio;  
+ 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;
+ 
+ /* Fix me, need to handle cs polarity */
+ GPCR(chip->cs_gpio) = GPIO_bit(chip->cs_gpio);
+ 
+ /* Go baby, go */
+ __REG(ssto) = chip->timeout;
+ __REG(sscr1) |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE);
+}
+
+
+static void pump_messages(unsigned long data)
+{
+ struct master_data *drv_data = (struct master_data *)data;
+
+ spin_lock(&drv_data->lock);
+
+ /* 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(&drv_data->lock);
+}
+
+static int transfer(struct spi_device *spi, struct spi_message *msg)
+{
+ struct master_data *drv_data = class_get_devdata(&spi->master->cdev);
+
+ msg->actual_length = 0;
+ msg->status = 0;
+  
+ spin_lock_bh(&drv_data->lock);
+ list_add_tail(&msg->queue, &drv_data->queue);
+ spin_unlock_bh(&drv_data->lock);
+ 
+ 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_gpio = chip_info->chip_select_gpio;
+ 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 = SSCR1_RxTresh(chip_info->rx_threshold) 
+   | SSCR1_TxTresh(chip_info->tx_threshold) 
+   | (((spi->mode & SPI_CPHA) != 0) << 4) 
+   | (((spi->mode & SPI_CPOL) != 0) << 3);
+ chip->to = 0;
+ chip->psp = 0;
+ chip->timeout = (chip_info->timeout_microsecs * 10000) / 2712;
+ 
+ if (spi->bits_per_word <= 8) {
+  chip->n_bytes = 1;
+  chip->read = u8_reader;
+  chip->write = u8_writer;
+ }
+ else if (spi->bits_per_word <= 16) {
+  chip->n_bytes = 2;
+  chip->read = u16_reader;
+  chip->write = u16_writer;
+ }
+ else if (spi->bits_per_word <= 32) {
+  chip->n_bytes = 4;
+  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);
+ 
+ dev_dbg(&spi->dev, "gpio=%u sscr0=0x%08x sscr1=0x%08x "
+    "ssto=0x%08x sspsp=0x%08x\n", 
+    chip->cs_gpio, chip->cr0, 
+    chip->cr1, chip->to, chip->psp);
+   
+ 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 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));
+ 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);
+
+ tasklet_init(&drv_data->pump_transfers, 
+   pump_transfers, 
+   (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;
+ 
+ /* 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;
+ }
+ 
+ /* 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;
+ 
+ dev_set_drvdata(dev, master);
+ status = spi_register_master(master);
+ if (status != 0) {
+  goto out_error_irq;
+ }
+  
+ return status;
+
+out_error_irq:
+ free_irq(irq, drv_data);
+ 
+out_error_memory:
+ class_device_put(&master->cdev);
+
+ 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;
+ 
+ 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);
+
+ 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/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-04 12:50:22.922007000 -0700
@@ -0,0 +1,36 @@
+/* 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_
+
+struct pxa2xx_spi_master {
+ u16 bus_num;
+ u32 clock_enable;
+ u16 num_chipselect;
+};
+
+struct pxa2xx_spi_chip {
+ u8 mode;
+ u8 tx_threshold;
+ u8 rx_threshold;
+ u8 bits_per_word;
+ u16 chip_select_gpio;
+ u32 timeout_microsecs;
+};
+
+#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/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux