[PATCH/RFC 1/2] simple SPI controller on PXA2xx SSP port
From: stephen
Date: Wed Oct 05 2005 - 15:14:47 EST
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.
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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/