From: Stephen S. <sg...@gm...> - 2005-10-05 07:29:28
|
This is a prototype interrupt driven SPI "controller" for Intel's PXA2xx series SOC. The driver plugs into the lightweight SPI framework developed b= y 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=092005-10-04 14:07:18.000000000 -0= 700 +++ linux-2.6.12-spi-pxa/drivers/spi/Kconfig=092005-10-04 14:00:12.27944900= 0 -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=092005-10-04 14:07:18.000000000 -= 0700 +++ linux-2.6.12-spi-pxa/drivers/spi/Makefile=092005-10-04 14:00:12.279449000 -0700 @@ -11,9 +11,11 @@ obj-$(CONFIG_SPI_MASTER) +=3D spi.o # SPI master controller drivers (bus) +obj-$(CONFIG_SPI_PXA_SSP) +=3D pxa2xx_spi_ssp.o # ... add above this line ... # SPI protocol drivers (device/link on bus) +obj-$(CONFIG_SPI_CS8415A) +=3D cs8415a.o # ... add above this line ... # SPI slave controller drivers (upstream link) --- linux-2.6.12-spi/drivers/spi/pxa2xx_spi_ssp.c=091969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/drivers/spi/pxa2xx_spi_ssp.c=092005-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 PXA2x= x + * series SOC. The driver plugs into the lightweight SPI framework develop= ed 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 =3D { + *=09.enabled =3D 1, + *=09.muted =3D 1, + *=09.channel =3D 0, + *=09.mask_interrupt =3D cs8415a_mask_interrupt, + *=09.unmask_interrupt =3D cs8415a_unmask_interrupt, + *=09.service_requested =3D cs8415a_service_requested, + * }; + * + * static struct pxa2xx_spi_chip cs8415a_chip_info =3D { + *=09.mode =3D SPI_MODE_3, + *=09.tx_threshold =3D 12, + *=09.rx_threshold =3D 4, + *=09.bits_per_word =3D 8, + *=09.chip_select_gpio =3D 2, + *=09.timeout_microsecs =3D 64, + * }; + * + * static struct spi_board_info streetracer_spi_board_info[] __initdata = =3D { + *=09{ + *=09=09.modalias =3D "cs8415a", + *=09=09.max_speed_hz =3D 3686400, + *=09=09.bus_num =3D 2, + *=09=09.chip_select =3D 0, + *=09=09.platform_data =3D &cs8415a_platform_info, + *=09=09.controller_data =3D &cs8415a_chip_info, + *=09=09.irq =3D STREETRACER_APCI_IRQ, + *=09}, + * }; + * + * static struct resource pxa_spi_resources[] =3D { + *=09[0] =3D { + *=09=09.start=09=3D __PREG(SSCR0_P(2)), + *=09=09.end=09=3D __PREG(SSCR0_P(2)) + 0x2c, + *=09=09.flags=09=3D IORESOURCE_MEM, + *=09}, + *=09[1] =3D { + *=09=09.start=09=3D IRQ_NSSP, + *=09=09.end=09=3D IRQ_NSSP, + *=09=09.flags=09=3D IORESOURCE_IRQ, + *=09}, + * }; + * + * static struct pxa2xx_spi_master pxa_nssp_master_info =3D { + *=09.bus_num =3D 2, + *=09.clock_enable =3D CKEN9_NSSP, + *=09.num_chipselect =3D 3, + * }; + * + * static struct platform_device pxa_spi_ssp =3D { + *=09.name =3D "pxa2xx-spi-ssp", + *=09.id =3D -1, + *=09.resource =3D pxa_spi_resources, + *=09.num_resources =3D ARRAY_SIZE(pxa_spi_resources), + *=09.dev =3D { + *=09=09.platform_data =3D &pxa_nssp_master_info, + *=09}, + * }; + * + * static void __init streetracer_init(void) + * { + *=09platform_device_register(&pxa_spi_ssp); + *=09spi_register_board_info(streetracer_spi_board_info, + * =09=09=09=09ARRAY_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 transactio= ns + * 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: + * =09Does not handle invert chip select polarity. + * =09Heavy loaded systems may see transaction failures. + * =09Wordsize support is untested. + * =09Internal NSSP chip select is not support (i.e. NSSPSRFM) + * =09Module 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_R= OR)) + +struct transfer_state { +=09int index; +=09int len; +=09u32 gpio; +=09void *tx; +=09void *tx_end; +=09void *rx; +=09void *rx_end; +=09void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state); +=09void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state); +}; + +struct master_data { +=09spinlock_t lock; +=09struct spi_master *master; +=09struct list_head queue; +=09struct tasklet_struct pump_messages; +=09struct tasklet_struct pump_transfers; +=09struct spi_message* cur_msg; +=09struct transfer_state cur_state; +=09u32 sscr0; +=09u32 sscr1; +=09u32 sssr; +=09u32 ssitr; +=09u32 ssdr; +=09u32 ssto; +=09u32 sspsp; +}; + +struct chip_data { +=09u32 cr0; +=09u32 cr1; +=09u32 to; +=09u32 psp; +=09u16 cs_gpio; +=09u32 timeout; +=09u8 n_bytes; +=09void (*write)(u32 sssr, u32 ssdr, struct transfer_state *state); +=09void (*read)(u32 sssr, u32 ssdr, struct transfer_state *state); +}; + +static inline void flush(struct master_data *drv_data) +{ +=09u32 sssr =3D drv_data->sssr; +=09u32 ssdr =3D drv_data->ssdr; +=09 +=09do { +=09=09while (__REG(sssr) & SSSR_RNE) { +=09=09=09(void)__REG(ssdr); +=09=09} +=09} while (__REG(sssr) & SSSR_BSY); +=09__REG(sssr) =3D SSSR_ROR ; +} + +static inline void save_state(struct master_data *drv_data, +=09=09=09=09struct chip_data *chip) +{ +=09/* Save critical register */ +=09chip->cr0 =3D __REG(drv_data->sscr0); +=09chip->cr1 =3D __REG(drv_data->sscr1); +=09chip->to =3D __REG(drv_data->ssto); +=09chip->psp =3D __REG(drv_data->sspsp); +=09 +=09/* Disable clock */ +=09__REG(drv_data->sscr0) &=3D ~SSCR0_SSE; +} + +static inline void restore_state(struct master_data *drv_data, +=09=09=09=09=09struct chip_data *chip) +{ +=09/* Clear status and disable clock*/ +=09__REG(drv_data->sssr) =3D SSSR_ROR | SSSR_TUR | SSSR_BCE; +=09__REG(drv_data->sscr0) =3D chip->cr0 & ~SSCR0_SSE; +=09 +=09/* Load the registers */ +=09__REG(drv_data->sscr1) =3D chip->cr1; +=09__REG(drv_data->ssto) =3D chip->to; +=09__REG(drv_data->sspsp) =3D chip->psp; +=09__REG(drv_data->sscr0) =3D chip->cr0; +} + +static inline void dump_state(struct master_data* drv_data) +{ +=09u32 sscr0 =3D drv_data->sscr0; +=09u32 sscr1 =3D drv_data->sscr1; +=09u32 sssr =3D drv_data->sssr; +=09u32 ssto =3D drv_data->ssto; +=09u32 sspsp =3D drv_data->sspsp; +=09 +=09pr_debug("SSP dump: sscr0=3D0x%08x, sscr1=3D0x%08x, " +=09=09=09"ssto=3D0x%08x, sspsp=3D0x%08x, sssr=3D0x%08x\n", +=09=09=09__REG(sscr0), __REG(sscr1), __REG(ssto), +=09=09=09__REG(sspsp), __REG(sssr)); +} + +static void null_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D 0; +=09=09++state->tx; +=09} +} + +static void null_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09(void)(__REG(ssdr)); +=09=09++state->rx; +=09} +} + +static void u8_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D *(u8 *)(state->tx); +=09=09++state->tx; +=09} +} + +static void u8_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09*(u8 *)(state->rx) =3D __REG(ssdr); +=09=09++state->rx; +=09} +} + +static void u16_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D *(u16 *)(state->tx); +=09=09state->tx +=3D 2; +=09} +} + +static void u16_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09*(u16 *)(state->rx) =3D __REG(ssdr); +=09=09state->rx +=3D 2; +=09} +} +static void u32_writer(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_TNF) && (state->tx < state->tx_end)) { +=09=09__REG(ssdr) =3D *(u32 *)(state->tx); +=09=09state->tx +=3D 4; +=09} +} + +static void u32_reader(u32 sssr, u32 ssdr, struct transfer_state *state) +{ +=09while ((__REG(sssr) & SSSR_RNE) && (state->rx < state->rx_end)) { +=09=09*(u32 *)(state->rx) =3D __REG(ssdr); +=09=09state->rx +=3D 4; +=09} +} + +static irqreturn_t ssp_int(int irq, void *dev_id, struct pt_regs *regs) +{ +=09struct master_data *drv_data =3D (struct master_data *)dev_id; +=09struct transfer_state *state; +=09u32 sssr =3D drv_data->sssr; +=09u32 ssdr =3D drv_data->ssdr; +=09u32 sscr1 =3D drv_data->sscr1; +=09u32 ssto =3D drv_data->ssto; +=09u32 irq_status; +=09struct spi_message *msg; +=09 +=09if (!drv_data->cur_msg || !drv_data->cur_msg->state) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad message or message " +=09=09=09=09"state in interrupt handler\n"); +=09} +=09state =3D (struct transfer_state *)drv_data->cur_msg->state; +=09msg =3D drv_data->cur_msg; + +=09while ((irq_status =3D GET_IRQ_STATUS(sssr))) { + +=09=09if (irq_status & SSSR_ROR) { + +=09=09=09/* Clear and disable interrupts */ +=09=09=09__REG(ssto) =3D 0; +=09=09=09__REG(sssr) =3D SSSR_TINT | SSSR_ROR; +=09=09=09__REG(sscr1) &=3D ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +=09=09=09 +=09=09=09flush(drv_data); +=09=09=09 +=09=09=09printk(KERN_WARNING "fifo overun: " +=09=09=09=09=09"index=3D%d tx_len=3D%d rx_len%d\n", +=09=09=09=09=09state->index, +=09=09=09=09=09(state->tx_end - state->tx), +=09=09=09=09=09(state->rx_end - state->rx)); + +=09=09=09state->index =3D -2; +=09=09=09tasklet_schedule(&drv_data->pump_transfers); + +=09=09=09return IRQ_HANDLED; +=09=09} + +=09=09 +=09=09/* Pump data */ +=09=09state->read(sssr, ssdr, state); +=09=09state->write(sssr, ssdr, state); +=09=09 +=09=09if ((irq_status & SSSR_TINT) || (state->rx <=3D state->rx_end)) { +=09=09=09 +=09=09=09/* Look for false positive timeout */ +=09=09=09if (state->rx < state->rx_end) { +=09=09=09=09__REG(sssr) =3D SSSR_TINT; +=09=09=09=09break; +=09=09=09} +=09=09=09 +=09=09=09/* Clear timeout */ +=09=09=09__REG(ssto) =3D 0; +=09=09=09__REG(sssr) =3D SSSR_TINT | SSSR_ROR ; +=09=09=09__REG(sscr1) &=3D ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +=09=09=09 +=09=09=09msg->actual_length +=3D msg->transfers[state->index].len; +=09 +=09=09=09if (msg->transfers[state->index].cs_change)=09 +=09=09=09=09/* Fix me, need to handle cs polarity */ +=09=09=09=09GPSR(state->gpio) =3D GPIO_bit(state->gpio); + +=09=09=09/* Schedule transfer tasklet */ +=09=09=09++state->index; +=09=09=09tasklet_schedule(&drv_data->pump_transfers); +=09=09=09 +=09=09=09return IRQ_HANDLED; +=09=09} +=09} +=09 +=09return IRQ_HANDLED; +} + +static void pump_transfers(unsigned long data) +{ +=09struct master_data *drv_data =3D (struct master_data *)data; +=09struct spi_message *message =3D drv_data->cur_msg; +=09struct chip_data *chip; +=09struct transfer_state * state; +=09struct spi_transfer *transfer; +=09u32 sscr1 =3D drv_data->sscr1; +=09u32 ssto =3D drv_data->ssto; +=09 +=09if (!message) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad pump_transfers " +=09=09=09=09"schedule\n"); +=09=09tasklet_schedule(&drv_data->pump_messages); +=09=09return; +=09} +=09 +=09state =3D (struct transfer_state *)message->state; +=09if (!state) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad message state\n"); +=09=09drv_data->cur_msg =3D NULL; +=09=09tasklet_schedule(&drv_data->pump_messages); +=09=09return; +=09} +=09 +=09chip =3D spi_get_ctldata(message->dev); +=09if (!chip) { +=09=09printk(KERN_ERR "pxs2xx_spi_ssp: bad chip data\n"); +=09=09drv_data->cur_msg =3D NULL; +=09=09tasklet_schedule(&drv_data->pump_messages); +=09=09return; +=09} +=09 +=09/* Handle for abort */ +=09if (state->index =3D=3D -2) { +=09=09 +=09=09message->status =3D -EIO; +=09=09if (message->complete) +=09=09=09message->complete(message->context); + +=09=09drv_data->cur_msg =3D NULL; +=09=09save_state(drv_data, chip); + +=09=09tasklet_schedule(&drv_data->pump_messages);=09=09 +=09=09 +=09=09return; +=09} + +=09/* Handle end of message */ +=09if (state->index =3D=3D message->n_transfer) { +=09=09 +=09=09if (!message->transfers[state->index].cs_change)=09 +=09=09=09/* Fix me, need to handle cs polarity */ +=09=09=09GPSR(state->gpio) =3D GPIO_bit(state->gpio); +=09=09=09 +=09=09message->status =3D 0; +=09=09if (message->complete) +=09=09=09message->complete(message->context); + +=09=09drv_data->cur_msg =3D NULL; +=09=09save_state(drv_data, chip); + +=09=09tasklet_schedule(&drv_data->pump_messages);=09=09 +=09=09 +=09=09return; +=09} +=09 +=09/* Handle start of message */ +=09if (state->index =3D=3D -1) { +=09=09 +=09=09restore_state(drv_data, chip); + +=09=09flush(drv_data); + +=09=09++state->index; +=09} +=09 +=09/* Delay if requested at end of transfer*/ +=09if (state->index > 1) { +=09=09transfer =3D message->transfers + (state->index - 1); +=09=09if (transfer->delay_usecs) +=09=09=09udelay(transfer->delay_usecs); +=09} + +=09/* Setup the transfer state */=09 +=09transfer =3D message->transfers + state->index; +=09state->gpio =3D chip->cs_gpio;=09=09 +=09state->tx =3D (void *)transfer->tx_buf; +=09state->tx_end =3D state->tx + (transfer->len * chip->n_bytes); +=09state->rx =3D transfer->rx_buf; +=09state->rx_end =3D state->rx + (transfer->len * chip->n_bytes); +=09state->write =3D state->tx ? chip->write : null_writer; +=09state->read =3D state->rx ? chip->read : null_reader; +=09 +=09/* Fix me, need to handle cs polarity */ +=09GPCR(chip->cs_gpio) =3D GPIO_bit(chip->cs_gpio); +=09 +=09/* Go baby, go */ +=09__REG(ssto) =3D chip->timeout; +=09__REG(sscr1) |=3D (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); +} + + +static void pump_messages(unsigned long data) +{ +=09struct master_data *drv_data =3D (struct master_data *)data; + +=09spin_lock(&drv_data->lock); + +=09/* Check for list empty */=09 +=09if (list_empty(&drv_data->queue)) { +=09=09spin_unlock(&drv_data->lock); +=09=09return; +=09} +=09 +=09/* Check to see if we are already running */ +=09if (drv_data->cur_msg) { +=09=09spin_unlock(&drv_data->lock); +=09=09return; +=09}=09=09 + +=09/* Extract head of queue and check for tasklet reschedule */ +=09drv_data->cur_msg =3D list_entry(drv_data->queue.next, +=09=09=09=09=09struct spi_message, queue); +=09list_del_init(&drv_data->cur_msg->queue); +=09 +=09/* Setup message transfer and schedule transfer pump */ +=09drv_data->cur_msg->state =3D &drv_data->cur_state; +=09drv_data->cur_state.index =3D -1; +=09drv_data->cur_state.len =3D 0; +=09tasklet_schedule(&drv_data->pump_transfers); +=09=09=09 +=09spin_unlock(&drv_data->lock); +} + +static int transfer(struct spi_device *spi, struct spi_message *msg) +{ +=09struct master_data *drv_data =3D class_get_devdata(&spi->master->cdev); + +=09msg->actual_length =3D 0; +=09msg->status =3D 0; +=09=09 +=09spin_lock_bh(&drv_data->lock); +=09list_add_tail(&msg->queue, &drv_data->queue); +=09spin_unlock_bh(&drv_data->lock); +=09 +=09tasklet_schedule(&drv_data->pump_messages); +=09 +=09return 0; +} + +static int setup(struct spi_device *spi) +{ +=09struct pxa2xx_spi_chip *chip_info; +=09struct chip_data *chip; +=09 +=09chip_info =3D (struct pxa2xx_spi_chip *)spi->platform_data; +=09 +=09/* Only alloc on first setup */ +=09chip =3D spi_get_ctldata(spi); +=09if (chip =3D=3D NULL) { +=09=09chip =3D kcalloc(1, sizeof(struct chip_data), GFP_KERNEL); +=09=09if (!chip) +=09=09=09return -ENOMEM; + +=09=09spi->mode =3D chip_info->mode; +=09=09spi->bits_per_word =3D chip_info->bits_per_word; +=09} +=09 +=09chip->cs_gpio =3D chip_info->chip_select_gpio; +=09chip->cr0 =3D SSCR0_SerClkDiv((MAX_SPEED_HZ / spi->max_speed_hz) + 2) +=09=09=09| SSCR0_Motorola +=09=09=09| SSCR0_DataSize(spi->bits_per_word) +=09=09=09| SSCR0_SSE +=09=09=09| (spi->bits_per_word > 16 ? SSCR0_EDSS : 0); +=09chip->cr1 =3D SSCR1_RxTresh(chip_info->rx_threshold) +=09=09=09| SSCR1_TxTresh(chip_info->tx_threshold) +=09=09=09| (((spi->mode & SPI_CPHA) !=3D 0) << 4) +=09=09=09| (((spi->mode & SPI_CPOL) !=3D 0) << 3); +=09chip->to =3D 0; +=09chip->psp =3D 0; +=09chip->timeout =3D (chip_info->timeout_microsecs * 10000) / 2712; +=09 +=09if (spi->bits_per_word <=3D 8) { +=09=09chip->n_bytes =3D 1; +=09=09chip->read =3D u8_reader; +=09=09chip->write =3D u8_writer; +=09} +=09else if (spi->bits_per_word <=3D 16) { +=09=09chip->n_bytes =3D 2; +=09=09chip->read =3D u16_reader; +=09=09chip->write =3D u16_writer; +=09} +=09else if (spi->bits_per_word <=3D 32) { +=09=09chip->n_bytes =3D 4; +=09=09chip->read =3D u32_reader; +=09=09chip->write =3D u32_writer; +=09} +=09else { +=09=09printk(KERN_ERR "pxa2xx_spi_ssp: invalid wordsize\n"); +=09=09kfree(chip); +=09=09return -ENODEV; +=09} +=09=09 +=09spi_set_ctldata(spi, chip); +=09 +=09dev_dbg(&spi->dev, "gpio=3D%u sscr0=3D0x%08x sscr1=3D0x%08x " +=09=09=09=09"ssto=3D0x%08x sspsp=3D0x%08x\n", +=09=09=09=09chip->cs_gpio, chip->cr0, +=09=09=09=09chip->cr1, chip->to, chip->psp); +=09=09=09 +=09return 0; +} + +static void cleanup(const struct spi_device *spi) +{ +=09struct chip_data *chip =3D spi_get_ctldata((struct spi_device *)spi); +=09 +=09if (chip) +=09=09kfree(chip); +=09 +=09dev_dbg(&spi->dev, "spi_device %u.%u cleanup\n", +=09=09=09=09spi->master->bus_num, spi->chip_select); +} + +static int probe(struct device *dev) +{ +=09struct platform_device *pdev =3D to_platform_device(dev); +=09struct pxa2xx_spi_master *platform_info; +=09struct spi_master *master; +=09struct master_data *drv_data =3D 0; +=09struct resource *memory_resource; +=09int irq; +=09int status =3D 0; + +=09platform_info =3D (struct pxa2xx_spi_master *)pdev->dev.platform_data; +=09 +=09master =3D spi_alloc_master(dev, sizeof(struct master_data)); +=09if (!master) +=09=09return -ENOMEM; +=09drv_data =3D class_get_devdata(&master->cdev); +=09drv_data->master =3D master;=09 +=09=09 +=09INIT_LIST_HEAD(&drv_data->queue); +=09spin_lock_init(&drv_data->lock); + +=09tasklet_init(&drv_data->pump_messages, +=09=09=09pump_messages, +=09=09=09(unsigned long)drv_data); + +=09tasklet_init(&drv_data->pump_transfers, +=09=09=09pump_transfers, +=09=09=09(unsigned long)drv_data); +=09 +=09master->bus_num =3D platform_info->bus_num; +=09master->num_chipselect =3D platform_info->num_chipselect; +=09master->cleanup =3D cleanup; +=09master->setup =3D setup; +=09master->transfer =3D transfer; +=09 +=09/* Setup register addresses */ +=09memory_resource =3D platform_get_resource(pdev, IORESOURCE_MEM, 0); +=09if (!memory_resource) { +=09=09dev_dbg(dev, "can not find platform io memory\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_memory; +=09} +=09 +=09drv_data->sscr0 =3D memory_resource->start + 0x00000000; +=09drv_data->sscr1 =3D memory_resource->start + 0x00000004; +=09drv_data->sssr =3D memory_resource->start + 0x00000008; +=09drv_data->ssitr =3D memory_resource->start + 0x0000000c; +=09drv_data->ssdr =3D memory_resource->start + 0x00000010; +=09drv_data->ssto =3D memory_resource->start + 0x00000028; +=09drv_data->sspsp =3D memory_resource->start + 0x0000002c; +=09 +=09/* Attach to IRQ */ +=09irq =3D platform_get_irq(pdev, 0); +=09if (irq =3D=3D 0) { +=09=09dev_dbg(dev, "problem getting IORESOURCE_IRQ[0]\n"); +=09=09status =3D -ENODEV; +=09=09goto out_error_memory; +=09} +=09 +=09status =3D request_irq(irq, ssp_int, SA_INTERRUPT, dev->bus_id, drv_dat= a); +=09if (status < 0) { +=09=09dev_dbg(dev, "problem requesting IORESOURCE_IRQ %u\n", irq); +=09=09goto out_error_memory; +=09} +=09 +=09/* Enable SOC clock */ +=09pxa_set_cken(platform_info->clock_enable, 1); +=09=09 +=09/* Load default SSP configuration */ +=09__REG(drv_data->sscr0) =3D 0; +=09__REG(drv_data->sscr1) =3D SSCR1_RxTresh(4) | SSCR1_TxTresh(12); +=09__REG(drv_data->sscr0) =3D SSCR0_SerClkDiv(2) +=09=09=09=09=09| SSCR0_Motorola +=09=09=09=09=09| SSCR0_DataSize(8); +=09__REG(drv_data->ssto) =3D 0; +=09__REG(drv_data->sspsp) =3D 0; +=09 +=09dev_set_drvdata(dev, master); +=09status =3D spi_register_master(master); +=09if (status !=3D 0) { +=09=09goto out_error_irq; +=09} +=09=09 +=09return status; + +out_error_irq: +=09free_irq(irq, drv_data); +=09 +out_error_memory: +=09class_device_put(&master->cdev); + +=09return status; +} + +static int remove(struct device *dev) +{ +=09struct platform_device *pdev =3D to_platform_device(dev); +=09struct spi_master *master =3D dev_get_drvdata(dev); +=09struct master_data *drv_data =3D class_get_devdata(&master->cdev); +=09struct pxa2xx_spi_master *platform_info; +=09 +=09int irq; +=09unsigned long flags; +=09 +=09platform_info =3D (struct pxa2xx_spi_master *)pdev->dev.platform_data; + +=09spin_lock_irqsave(&drv_data->lock, flags); + +=09__REG(drv_data->sscr0) =3D 0; +=09pxa_set_cken(platform_info->clock_enable, 0); + +=09irq =3D platform_get_irq(pdev, 0); +=09if (irq !=3D 0) +=09=09free_irq(irq, drv_data); +=09 +=09spin_unlock_irqrestore(&drv_data->lock, flags); + +=09spi_unregister_master(master); +=09 +=09return 0; +} + +static struct device_driver driver =3D { +=09.name =3D "pxa2xx-spi-ssp", +=09.bus =3D &platform_bus_type, +=09.owner =3D THIS_MODULE, +=09.probe =3D probe, +=09.remove =3D remove, +}; + +static int pxa2xx_spi_ssp_init(void) +{ +=09driver_register(&driver); +=09 +=09return 0; +} +module_init(pxa2xx_spi_ssp_init); + +static void pxa2xx_spi_ssp_exit(void) +{ +=09driver_unregister(&driver); +} +module_exit(pxa2xx_spi_ssp_exit); --- linux-2.6.12-spi/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h=091969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.12-spi-pxa/include/asm-arm/arch-pxa/pxa2xx_spi_ssp.h=092005-1= 0-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 { +=09u16 bus_num; +=09u32 clock_enable; +=09u16 num_chipselect; +}; + +struct pxa2xx_spi_chip { +=09u8 mode; +=09u8 tx_threshold; +=09u8 rx_threshold; +=09u8 bits_per_word; +=09u16 chip_select_gpio; +=09u32 timeout_microsecs; +}; + +#endif /*PXA2XX_SPI_SSP_H_*/ |