* [PATCH/RFC 1/2] simple SPI controller implementation on PXA2xx SSP port
@ 2005-10-04 22:28 Stephen Street
0 siblings, 0 replies; only message in thread
From: Stephen Street @ 2005-10-04 22:28 UTC (permalink / raw)
To: linux-kernel; +Cc: david-b, spi-devel-general, basicmark, dpervushin
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_*/
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2005-10-04 22:28 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-10-04 22:28 [PATCH/RFC 1/2] simple SPI controller implementation on PXA2xx SSP port Stephen Street
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.