[PATCH/RFC 1/2] simple SPI controller 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.

 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