diff options
Diffstat (limited to 'drivers/spi/spi-atmel.c')
| -rw-r--r-- | drivers/spi/spi-atmel.c | 728 | 
1 files changed, 660 insertions, 68 deletions
diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index 656d137db25..787bd2c22bc 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -15,16 +15,17 @@  #include <linux/platform_device.h>  #include <linux/delay.h>  #include <linux/dma-mapping.h> +#include <linux/dmaengine.h>  #include <linux/err.h>  #include <linux/interrupt.h>  #include <linux/spi/spi.h>  #include <linux/slab.h>  #include <linux/platform_data/atmel.h> +#include <linux/platform_data/dma-atmel.h>  #include <linux/of.h> -#include <asm/io.h> -#include <asm/gpio.h> -#include <mach/cpu.h> +#include <linux/io.h> +#include <linux/gpio.h>  /* SPI register offsets */  #define SPI_CR					0x0000 @@ -39,6 +40,7 @@  #define SPI_CSR1				0x0034  #define SPI_CSR2				0x0038  #define SPI_CSR3				0x003c +#define SPI_VERSION				0x00fc  #define SPI_RPR					0x0100  #define SPI_RCR					0x0104  #define SPI_TPR					0x0108 @@ -71,6 +73,8 @@  #define SPI_FDIV_SIZE				1  #define SPI_MODFDIS_OFFSET			4  #define SPI_MODFDIS_SIZE			1 +#define SPI_WDRBT_OFFSET			5 +#define SPI_WDRBT_SIZE				1  #define SPI_LLB_OFFSET				7  #define SPI_LLB_SIZE				1  #define SPI_PCS_OFFSET				16 @@ -180,6 +184,27 @@  #define spi_writel(port,reg,value) \  	__raw_writel((value), (port)->regs + SPI_##reg) +/* use PIO for small transfers, avoiding DMA setup/teardown overhead and + * cache operations; better heuristics consider wordsize and bitrate. + */ +#define DMA_MIN_BYTES	16 + +struct atmel_spi_dma { +	struct dma_chan			*chan_rx; +	struct dma_chan			*chan_tx; +	struct scatterlist		sgrx; +	struct scatterlist		sgtx; +	struct dma_async_tx_descriptor	*data_desc_rx; +	struct dma_async_tx_descriptor	*data_desc_tx; + +	struct at_dma_slave	dma_slave; +}; + +struct atmel_spi_caps { +	bool	is_spi2; +	bool	has_wdrbt; +	bool	has_dma_support; +};  /*   * The core SPI transfer engine just talks to a register bank to set up @@ -188,7 +213,9 @@   */  struct atmel_spi {  	spinlock_t		lock; +	unsigned long		flags; +	phys_addr_t		phybase;  	void __iomem		*regs;  	int			irq;  	struct clk		*clk; @@ -197,13 +224,23 @@ struct atmel_spi {  	u8			stopping;  	struct list_head	queue; +	struct tasklet_struct	tasklet;  	struct spi_transfer	*current_transfer;  	unsigned long		current_remaining_bytes;  	struct spi_transfer	*next_transfer;  	unsigned long		next_remaining_bytes; +	int			done_status; +	/* scratch buffer */  	void			*buffer;  	dma_addr_t		buffer_dma; + +	struct atmel_spi_caps	caps; + +	bool			use_dma; +	bool			use_pdc; +	/* dmaengine data */ +	struct atmel_spi_dma	dma;  };  /* Controller-specific per-slave state */ @@ -222,14 +259,10 @@ struct atmel_spi_device {   *  - SPI_SR.TXEMPTY, SPI_SR.NSSR (and corresponding irqs)   *  - SPI_CSRx.CSAAT   *  - SPI_CSRx.SBCR allows faster clocking - * - * We can determine the controller version by reading the VERSION - * register, but I haven't checked that it exists on all chips, and - * this is cheaper anyway.   */ -static bool atmel_spi_is_v2(void) +static bool atmel_spi_is_v2(struct atmel_spi *as)  { -	return !cpu_is_at91rm9200(); +	return as->caps.is_spi2;  }  /* @@ -250,11 +283,6 @@ static bool atmel_spi_is_v2(void)   * Master on Chip Select 0.")  No workaround exists for that ... so for   * nCS0 on that chip, we (a) don't use the GPIO, (b) can't support CS_HIGH,   * and (c) will trigger that first erratum in some cases. - * - * TODO: Test if the atmel_spi_is_v2() branch below works on - * AT91RM9200 if we use some other register than CSR0. However, don't - * do this unconditionally since AP7000 has an errata where the BITS - * field in CSR0 overrides all other CSRs.   */  static void cs_activate(struct atmel_spi *as, struct spi_device *spi) @@ -263,15 +291,25 @@ static void cs_activate(struct atmel_spi *as, struct spi_device *spi)  	unsigned active = spi->mode & SPI_CS_HIGH;  	u32 mr; -	if (atmel_spi_is_v2()) { -		/* -		 * Always use CSR0. This ensures that the clock -		 * switches to the correct idle polarity before we -		 * toggle the CS. +	if (atmel_spi_is_v2(as)) { +		spi_writel(as, CSR0 + 4 * spi->chip_select, asd->csr); +		/* For the low SPI version, there is a issue that PDC transfer +		 * on CS1,2,3 needs SPI_CSR0.BITS config as SPI_CSR1,2,3.BITS  		 */  		spi_writel(as, CSR0, asd->csr); -		spi_writel(as, MR, SPI_BF(PCS, 0x0e) | SPI_BIT(MODFDIS) -				| SPI_BIT(MSTR)); +		if (as->caps.has_wdrbt) { +			spi_writel(as, MR, +					SPI_BF(PCS, ~(0x01 << spi->chip_select)) +					| SPI_BIT(WDRBT) +					| SPI_BIT(MODFDIS) +					| SPI_BIT(MSTR)); +		} else { +			spi_writel(as, MR, +					SPI_BF(PCS, ~(0x01 << spi->chip_select)) +					| SPI_BIT(MODFDIS) +					| SPI_BIT(MSTR)); +		} +  		mr = spi_readl(as, MR);  		gpio_set_value(asd->npcs_pin, active);  	} else { @@ -318,10 +356,26 @@ static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi)  			asd->npcs_pin, active ? " (low)" : "",  			mr); -	if (atmel_spi_is_v2() || spi->chip_select != 0) +	if (atmel_spi_is_v2(as) || spi->chip_select != 0)  		gpio_set_value(asd->npcs_pin, !active);  } +static void atmel_spi_lock(struct atmel_spi *as) +{ +	spin_lock_irqsave(&as->lock, as->flags); +} + +static void atmel_spi_unlock(struct atmel_spi *as) +{ +	spin_unlock_irqrestore(&as->lock, as->flags); +} + +static inline bool atmel_spi_use_dma(struct atmel_spi *as, +				struct spi_transfer *xfer) +{ +	return as->use_dma && xfer->len >= DMA_MIN_BYTES; +} +  static inline int atmel_spi_xfer_is_last(struct spi_message *msg,  					struct spi_transfer *xfer)  { @@ -333,6 +387,265 @@ static inline int atmel_spi_xfer_can_be_chained(struct spi_transfer *xfer)  	return xfer->delay_usecs == 0 && !xfer->cs_change;  } +static int atmel_spi_dma_slave_config(struct atmel_spi *as, +				struct dma_slave_config *slave_config, +				u8 bits_per_word) +{ +	int err = 0; + +	if (bits_per_word > 8) { +		slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +		slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; +	} else { +		slave_config->dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; +		slave_config->src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; +	} + +	slave_config->dst_addr = (dma_addr_t)as->phybase + SPI_TDR; +	slave_config->src_addr = (dma_addr_t)as->phybase + SPI_RDR; +	slave_config->src_maxburst = 1; +	slave_config->dst_maxburst = 1; +	slave_config->device_fc = false; + +	slave_config->direction = DMA_MEM_TO_DEV; +	if (dmaengine_slave_config(as->dma.chan_tx, slave_config)) { +		dev_err(&as->pdev->dev, +			"failed to configure tx dma channel\n"); +		err = -EINVAL; +	} + +	slave_config->direction = DMA_DEV_TO_MEM; +	if (dmaengine_slave_config(as->dma.chan_rx, slave_config)) { +		dev_err(&as->pdev->dev, +			"failed to configure rx dma channel\n"); +		err = -EINVAL; +	} + +	return err; +} + +static bool filter(struct dma_chan *chan, void *slave) +{ +	struct	at_dma_slave *sl = slave; + +	if (sl->dma_dev == chan->device->dev) { +		chan->private = sl; +		return true; +	} else { +		return false; +	} +} + +static int atmel_spi_configure_dma(struct atmel_spi *as) +{ +	struct at_dma_slave *sdata = &as->dma.dma_slave; +	struct dma_slave_config	slave_config; +	int err; + +	if (sdata && sdata->dma_dev) { +		dma_cap_mask_t mask; + +		/* Try to grab two DMA channels */ +		dma_cap_zero(mask); +		dma_cap_set(DMA_SLAVE, mask); +		as->dma.chan_tx = dma_request_channel(mask, filter, sdata); +		if (as->dma.chan_tx) +			as->dma.chan_rx = +				dma_request_channel(mask, filter, sdata); +	} +	if (!as->dma.chan_rx || !as->dma.chan_tx) { +		dev_err(&as->pdev->dev, +			"DMA channel not available, SPI unable to use DMA\n"); +		err = -EBUSY; +		goto error; +	} + +	err = atmel_spi_dma_slave_config(as, &slave_config, 8); +	if (err) +		goto error; + +	dev_info(&as->pdev->dev, +			"Using %s (tx) and %s (rx) for DMA transfers\n", +			dma_chan_name(as->dma.chan_tx), +			dma_chan_name(as->dma.chan_rx)); +	return 0; +error: +	if (as->dma.chan_rx) +		dma_release_channel(as->dma.chan_rx); +	if (as->dma.chan_tx) +		dma_release_channel(as->dma.chan_tx); +	return err; +} + +static void atmel_spi_stop_dma(struct atmel_spi *as) +{ +	if (as->dma.chan_rx) +		as->dma.chan_rx->device->device_control(as->dma.chan_rx, +							DMA_TERMINATE_ALL, 0); +	if (as->dma.chan_tx) +		as->dma.chan_tx->device->device_control(as->dma.chan_tx, +							DMA_TERMINATE_ALL, 0); +} + +static void atmel_spi_release_dma(struct atmel_spi *as) +{ +	if (as->dma.chan_rx) +		dma_release_channel(as->dma.chan_rx); +	if (as->dma.chan_tx) +		dma_release_channel(as->dma.chan_tx); +} + +/* This function is called by the DMA driver from tasklet context */ +static void dma_callback(void *data) +{ +	struct spi_master	*master = data; +	struct atmel_spi	*as = spi_master_get_devdata(master); + +	/* trigger SPI tasklet */ +	tasklet_schedule(&as->tasklet); +} + +/* + * Next transfer using PIO. + * lock is held, spi tasklet is blocked + */ +static void atmel_spi_next_xfer_pio(struct spi_master *master, +				struct spi_transfer *xfer) +{ +	struct atmel_spi	*as = spi_master_get_devdata(master); + +	dev_vdbg(master->dev.parent, "atmel_spi_next_xfer_pio\n"); + +	as->current_remaining_bytes = xfer->len; + +	/* Make sure data is not remaining in RDR */ +	spi_readl(as, RDR); +	while (spi_readl(as, SR) & SPI_BIT(RDRF)) { +		spi_readl(as, RDR); +		cpu_relax(); +	} + +	if (xfer->tx_buf) +		spi_writel(as, TDR, *(u8 *)(xfer->tx_buf)); +	else +		spi_writel(as, TDR, 0); + +	dev_dbg(master->dev.parent, +		"  start pio xfer %p: len %u tx %p rx %p\n", +		xfer, xfer->len, xfer->tx_buf, xfer->rx_buf); + +	/* Enable relevant interrupts */ +	spi_writel(as, IER, SPI_BIT(RDRF) | SPI_BIT(OVRES)); +} + +/* + * Submit next transfer for DMA. + * lock is held, spi tasklet is blocked + */ +static int atmel_spi_next_xfer_dma_submit(struct spi_master *master, +				struct spi_transfer *xfer, +				u32 *plen) +{ +	struct atmel_spi	*as = spi_master_get_devdata(master); +	struct dma_chan		*rxchan = as->dma.chan_rx; +	struct dma_chan		*txchan = as->dma.chan_tx; +	struct dma_async_tx_descriptor *rxdesc; +	struct dma_async_tx_descriptor *txdesc; +	struct dma_slave_config	slave_config; +	dma_cookie_t		cookie; +	u32	len = *plen; + +	dev_vdbg(master->dev.parent, "atmel_spi_next_xfer_dma_submit\n"); + +	/* Check that the channels are available */ +	if (!rxchan || !txchan) +		return -ENODEV; + +	/* release lock for DMA operations */ +	atmel_spi_unlock(as); + +	/* prepare the RX dma transfer */ +	sg_init_table(&as->dma.sgrx, 1); +	if (xfer->rx_buf) { +		as->dma.sgrx.dma_address = xfer->rx_dma + xfer->len - *plen; +	} else { +		as->dma.sgrx.dma_address = as->buffer_dma; +		if (len > BUFFER_SIZE) +			len = BUFFER_SIZE; +	} + +	/* prepare the TX dma transfer */ +	sg_init_table(&as->dma.sgtx, 1); +	if (xfer->tx_buf) { +		as->dma.sgtx.dma_address = xfer->tx_dma + xfer->len - *plen; +	} else { +		as->dma.sgtx.dma_address = as->buffer_dma; +		if (len > BUFFER_SIZE) +			len = BUFFER_SIZE; +		memset(as->buffer, 0, len); +	} + +	sg_dma_len(&as->dma.sgtx) = len; +	sg_dma_len(&as->dma.sgrx) = len; + +	*plen = len; + +	if (atmel_spi_dma_slave_config(as, &slave_config, 8)) +		goto err_exit; + +	/* Send both scatterlists */ +	rxdesc = rxchan->device->device_prep_slave_sg(rxchan, +					&as->dma.sgrx, +					1, +					DMA_FROM_DEVICE, +					DMA_PREP_INTERRUPT | DMA_CTRL_ACK, +					NULL); +	if (!rxdesc) +		goto err_dma; + +	txdesc = txchan->device->device_prep_slave_sg(txchan, +					&as->dma.sgtx, +					1, +					DMA_TO_DEVICE, +					DMA_PREP_INTERRUPT | DMA_CTRL_ACK, +					NULL); +	if (!txdesc) +		goto err_dma; + +	dev_dbg(master->dev.parent, +		"  start dma xfer %p: len %u tx %p/%08x rx %p/%08x\n", +		xfer, xfer->len, xfer->tx_buf, xfer->tx_dma, +		xfer->rx_buf, xfer->rx_dma); + +	/* Enable relevant interrupts */ +	spi_writel(as, IER, SPI_BIT(OVRES)); + +	/* Put the callback on the RX transfer only, that should finish last */ +	rxdesc->callback = dma_callback; +	rxdesc->callback_param = master; + +	/* Submit and fire RX and TX with TX last so we're ready to read! */ +	cookie = rxdesc->tx_submit(rxdesc); +	if (dma_submit_error(cookie)) +		goto err_dma; +	cookie = txdesc->tx_submit(txdesc); +	if (dma_submit_error(cookie)) +		goto err_dma; +	rxchan->device->device_issue_pending(rxchan); +	txchan->device->device_issue_pending(txchan); + +	/* take back lock */ +	atmel_spi_lock(as); +	return 0; + +err_dma: +	spi_writel(as, IDR, SPI_BIT(OVRES)); +	atmel_spi_stop_dma(as); +err_exit: +	atmel_spi_lock(as); +	return -ENOMEM; +} +  static void atmel_spi_next_xfer_data(struct spi_master *master,  				struct spi_transfer *xfer,  				dma_addr_t *tx_dma, @@ -350,6 +663,7 @@ static void atmel_spi_next_xfer_data(struct spi_master *master,  		if (len > BUFFER_SIZE)  			len = BUFFER_SIZE;  	} +  	if (xfer->tx_buf)  		*tx_dma = xfer->tx_dma + xfer->len - *plen;  	else { @@ -365,10 +679,10 @@ static void atmel_spi_next_xfer_data(struct spi_master *master,  }  /* - * Submit next transfer for DMA. + * Submit next transfer for PDC.   * lock is held, spi irq is blocked   */ -static void atmel_spi_next_xfer(struct spi_master *master, +static void atmel_spi_pdc_next_xfer(struct spi_master *master,  				struct spi_message *msg)  {  	struct atmel_spi	*as = spi_master_get_devdata(master); @@ -465,6 +779,48 @@ static void atmel_spi_next_xfer(struct spi_master *master,  	spi_writel(as, PTCR, SPI_BIT(TXTEN) | SPI_BIT(RXTEN));  } +/* + * Choose way to submit next transfer and start it. + * lock is held, spi tasklet is blocked + */ +static void atmel_spi_dma_next_xfer(struct spi_master *master, +				struct spi_message *msg) +{ +	struct atmel_spi	*as = spi_master_get_devdata(master); +	struct spi_transfer	*xfer; +	u32	remaining, len; + +	remaining = as->current_remaining_bytes; +	if (remaining) { +		xfer = as->current_transfer; +		len = remaining; +	} else { +		if (!as->current_transfer) +			xfer = list_entry(msg->transfers.next, +				struct spi_transfer, transfer_list); +		else +			xfer = list_entry( +				as->current_transfer->transfer_list.next, +					struct spi_transfer, transfer_list); + +		as->current_transfer = xfer; +		len = xfer->len; +	} + +	if (atmel_spi_use_dma(as, xfer)) { +		u32 total = len; +		if (!atmel_spi_next_xfer_dma_submit(master, xfer, &len)) { +			as->current_remaining_bytes = total - len; +			return; +		} else { +			dev_err(&msg->spi->dev, "unable to use DMA, fallback to PIO\n"); +		} +	} + +	/* use PIO if error appened using DMA */ +	atmel_spi_next_xfer_pio(master, xfer); +} +  static void atmel_spi_next_message(struct spi_master *master)  {  	struct atmel_spi	*as = spi_master_get_devdata(master); @@ -489,7 +845,10 @@ static void atmel_spi_next_message(struct spi_master *master)  	} else  		cs_activate(as, spi); -	atmel_spi_next_xfer(master, msg); +	if (as->use_pdc) +		atmel_spi_pdc_next_xfer(master, msg); +	else +		atmel_spi_dma_next_xfer(master, msg);  }  /* @@ -542,38 +901,213 @@ static void atmel_spi_dma_unmap_xfer(struct spi_master *master,  				 xfer->len, DMA_FROM_DEVICE);  } +static void atmel_spi_disable_pdc_transfer(struct atmel_spi *as) +{ +	spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS)); +} +  static void  atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as, -		struct spi_message *msg, int status, int stay) +		struct spi_message *msg, int stay)  { -	if (!stay || status < 0) +	if (!stay || as->done_status < 0)  		cs_deactivate(as, msg->spi);  	else  		as->stay = msg->spi;  	list_del(&msg->queue); -	msg->status = status; +	msg->status = as->done_status;  	dev_dbg(master->dev.parent,  		"xfer complete: %u bytes transferred\n",  		msg->actual_length); -	spin_unlock(&as->lock); +	atmel_spi_unlock(as);  	msg->complete(msg->context); -	spin_lock(&as->lock); +	atmel_spi_lock(as);  	as->current_transfer = NULL;  	as->next_transfer = NULL; +	as->done_status = 0;  	/* continue if needed */ -	if (list_empty(&as->queue) || as->stopping) -		spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS)); -	else +	if (list_empty(&as->queue) || as->stopping) { +		if (as->use_pdc) +			atmel_spi_disable_pdc_transfer(as); +	} else {  		atmel_spi_next_message(master); +	} +} + +/* Called from IRQ + * lock is held + * + * Must update "current_remaining_bytes" to keep track of data + * to transfer. + */ +static void +atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer) +{ +	u8		*txp; +	u8		*rxp; +	unsigned long	xfer_pos = xfer->len - as->current_remaining_bytes; + +	if (xfer->rx_buf) { +		rxp = ((u8 *)xfer->rx_buf) + xfer_pos; +		*rxp = spi_readl(as, RDR); +	} else { +		spi_readl(as, RDR); +	} + +	as->current_remaining_bytes--; + +	if (as->current_remaining_bytes) { +		if (xfer->tx_buf) { +			txp = ((u8 *)xfer->tx_buf) + xfer_pos + 1; +			spi_writel(as, TDR, *txp); +		} else { +			spi_writel(as, TDR, 0); +		} +	} +} + +/* Tasklet + * Called from DMA callback + pio transfer and overrun IRQ. + */ +static void atmel_spi_tasklet_func(unsigned long data) +{ +	struct spi_master	*master = (struct spi_master *)data; +	struct atmel_spi	*as = spi_master_get_devdata(master); +	struct spi_message	*msg; +	struct spi_transfer	*xfer; + +	dev_vdbg(master->dev.parent, "atmel_spi_tasklet_func\n"); + +	atmel_spi_lock(as); + +	xfer = as->current_transfer; + +	if (xfer == NULL) +		/* already been there */ +		goto tasklet_out; + +	msg = list_entry(as->queue.next, struct spi_message, queue); + +	if (as->current_remaining_bytes == 0) { +		if (as->done_status < 0) { +			/* error happened (overrun) */ +			if (atmel_spi_use_dma(as, xfer)) +				atmel_spi_stop_dma(as); +		} else { +			/* only update length if no error */ +			msg->actual_length += xfer->len; +		} + +		if (atmel_spi_use_dma(as, xfer)) +			if (!msg->is_dma_mapped) +				atmel_spi_dma_unmap_xfer(master, xfer); + +		if (xfer->delay_usecs) +			udelay(xfer->delay_usecs); + +		if (atmel_spi_xfer_is_last(msg, xfer) || as->done_status < 0) { +			/* report completed (or erroneous) message */ +			atmel_spi_msg_done(master, as, msg, xfer->cs_change); +		} else { +			if (xfer->cs_change) { +				cs_deactivate(as, msg->spi); +				udelay(1); +				cs_activate(as, msg->spi); +			} + +			/* +			 * Not done yet. Submit the next transfer. +			 * +			 * FIXME handle protocol options for xfer +			 */ +			atmel_spi_dma_next_xfer(master, msg); +		} +	} else { +		/* +		 * Keep going, we still have data to send in +		 * the current transfer. +		 */ +		atmel_spi_dma_next_xfer(master, msg); +	} + +tasklet_out: +	atmel_spi_unlock(as);  } +/* Interrupt + * + * No need for locking in this Interrupt handler: done_status is the + * only information modified. What we need is the update of this field + * before tasklet runs. This is ensured by using barrier. + */  static irqreturn_t -atmel_spi_interrupt(int irq, void *dev_id) +atmel_spi_pio_interrupt(int irq, void *dev_id) +{ +	struct spi_master	*master = dev_id; +	struct atmel_spi	*as = spi_master_get_devdata(master); +	u32			status, pending, imr; +	struct spi_transfer	*xfer; +	int			ret = IRQ_NONE; + +	imr = spi_readl(as, IMR); +	status = spi_readl(as, SR); +	pending = status & imr; + +	if (pending & SPI_BIT(OVRES)) { +		ret = IRQ_HANDLED; +		spi_writel(as, IDR, SPI_BIT(OVRES)); +		dev_warn(master->dev.parent, "overrun\n"); + +		/* +		 * When we get an overrun, we disregard the current +		 * transfer. Data will not be copied back from any +		 * bounce buffer and msg->actual_len will not be +		 * updated with the last xfer. +		 * +		 * We will also not process any remaning transfers in +		 * the message. +		 * +		 * All actions are done in tasklet with done_status indication +		 */ +		as->done_status = -EIO; +		smp_wmb(); + +		/* Clear any overrun happening while cleaning up */ +		spi_readl(as, SR); + +		tasklet_schedule(&as->tasklet); + +	} else if (pending & SPI_BIT(RDRF)) { +		atmel_spi_lock(as); + +		if (as->current_remaining_bytes) { +			ret = IRQ_HANDLED; +			xfer = as->current_transfer; +			atmel_spi_pump_pio_data(as, xfer); +			if (!as->current_remaining_bytes) { +				/* no more data to xfer, kick tasklet */ +				spi_writel(as, IDR, pending); +				tasklet_schedule(&as->tasklet); +			} +		} + +		atmel_spi_unlock(as); +	} else { +		WARN_ONCE(pending, "IRQ not handled, pending = %x\n", pending); +		ret = IRQ_HANDLED; +		spi_writel(as, IDR, pending); +	} + +	return ret; +} + +static irqreturn_t +atmel_spi_pdc_interrupt(int irq, void *dev_id)  {  	struct spi_master	*master = dev_id;  	struct atmel_spi	*as = spi_master_get_devdata(master); @@ -582,7 +1116,7 @@ atmel_spi_interrupt(int irq, void *dev_id)  	u32			status, pending, imr;  	int			ret = IRQ_NONE; -	spin_lock(&as->lock); +	atmel_spi_lock(as);  	xfer = as->current_transfer;  	msg = list_entry(as->queue.next, struct spi_message, queue); @@ -641,7 +1175,8 @@ atmel_spi_interrupt(int irq, void *dev_id)  		/* Clear any overrun happening while cleaning up */  		spi_readl(as, SR); -		atmel_spi_msg_done(master, as, msg, -EIO, 0); +		as->done_status = -EIO; +		atmel_spi_msg_done(master, as, msg, 0);  	} else if (pending & (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX))) {  		ret = IRQ_HANDLED; @@ -659,7 +1194,7 @@ atmel_spi_interrupt(int irq, void *dev_id)  			if (atmel_spi_xfer_is_last(msg, xfer)) {  				/* report completed message */ -				atmel_spi_msg_done(master, as, msg, 0, +				atmel_spi_msg_done(master, as, msg,  						xfer->cs_change);  			} else {  				if (xfer->cs_change) { @@ -673,18 +1208,18 @@ atmel_spi_interrupt(int irq, void *dev_id)  				 *  				 * FIXME handle protocol options for xfer  				 */ -				atmel_spi_next_xfer(master, msg); +				atmel_spi_pdc_next_xfer(master, msg);  			}  		} else {  			/*  			 * Keep going, we still have data to send in  			 * the current transfer.  			 */ -			atmel_spi_next_xfer(master, msg); +			atmel_spi_pdc_next_xfer(master, msg);  		}  	} -	spin_unlock(&as->lock); +	atmel_spi_unlock(as);  	return ret;  } @@ -719,7 +1254,7 @@ static int atmel_spi_setup(struct spi_device *spi)  	}  	/* see notes above re chipselect */ -	if (!atmel_spi_is_v2() +	if (!atmel_spi_is_v2(as)  			&& spi->chip_select == 0  			&& (spi->mode & SPI_CS_HIGH)) {  		dev_dbg(&spi->dev, "setup: can't be active-high\n"); @@ -728,7 +1263,7 @@ static int atmel_spi_setup(struct spi_device *spi)  	/* v1 chips start out at half the peripheral bus speed. */  	bus_hz = clk_get_rate(as->clk); -	if (!atmel_spi_is_v2()) +	if (!atmel_spi_is_v2(as))  		bus_hz /= 2;  	if (spi->max_speed_hz) { @@ -789,13 +1324,11 @@ static int atmel_spi_setup(struct spi_device *spi)  		spi->controller_state = asd;  		gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));  	} else { -		unsigned long		flags; - -		spin_lock_irqsave(&as->lock, flags); +		atmel_spi_lock(as);  		if (as->stay == spi)  			as->stay = NULL;  		cs_deactivate(as, spi); -		spin_unlock_irqrestore(&as->lock, flags); +		atmel_spi_unlock(as);  	}  	asd->csr = csr; @@ -804,7 +1337,7 @@ static int atmel_spi_setup(struct spi_device *spi)  		"setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n",  		bus_hz / scbr, bits, spi->mode, spi->chip_select, csr); -	if (!atmel_spi_is_v2()) +	if (!atmel_spi_is_v2(as))  		spi_writel(as, CSR0 + 4 * spi->chip_select, csr);  	return 0; @@ -814,7 +1347,6 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)  {  	struct atmel_spi	*as;  	struct spi_transfer	*xfer; -	unsigned long		flags;  	struct device		*controller = spi->master->dev.parent;  	u8			bits;  	struct atmel_spi_device	*asd; @@ -854,13 +1386,10 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)  		/*  		 * DMA map early, for performance (empties dcache ASAP) and -		 * better fault reporting.  This is a DMA-only driver. -		 * -		 * NOTE that if dma_unmap_single() ever starts to do work on -		 * platforms supported by this driver, we would need to clean -		 * up mappings for previously-mapped transfers. +		 * better fault reporting.  		 */ -		if (!msg->is_dma_mapped) { +		if ((!msg->is_dma_mapped) && (atmel_spi_use_dma(as, xfer) +			|| as->use_pdc)) {  			if (atmel_spi_dma_map_xfer(as, xfer) < 0)  				return -ENOMEM;  		} @@ -879,11 +1408,11 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)  	msg->status = -EINPROGRESS;  	msg->actual_length = 0; -	spin_lock_irqsave(&as->lock, flags); +	atmel_spi_lock(as);  	list_add_tail(&msg->queue, &as->queue);  	if (!as->current_transfer)  		atmel_spi_next_message(spi->master); -	spin_unlock_irqrestore(&as->lock, flags); +	atmel_spi_unlock(as);  	return 0;  } @@ -893,23 +1422,39 @@ static void atmel_spi_cleanup(struct spi_device *spi)  	struct atmel_spi	*as = spi_master_get_devdata(spi->master);  	struct atmel_spi_device	*asd = spi->controller_state;  	unsigned		gpio = (unsigned) spi->controller_data; -	unsigned long		flags;  	if (!asd)  		return; -	spin_lock_irqsave(&as->lock, flags); +	atmel_spi_lock(as);  	if (as->stay == spi) {  		as->stay = NULL;  		cs_deactivate(as, spi);  	} -	spin_unlock_irqrestore(&as->lock, flags); +	atmel_spi_unlock(as);  	spi->controller_state = NULL;  	gpio_free(gpio);  	kfree(asd);  } +static inline unsigned int atmel_get_version(struct atmel_spi *as) +{ +	return spi_readl(as, VERSION) & 0x00000fff; +} + +static void atmel_get_caps(struct atmel_spi *as) +{ +	unsigned int version; + +	version = atmel_get_version(as); +	dev_info(&as->pdev->dev, "version: 0x%x\n", version); + +	as->caps.is_spi2 = version > 0x121; +	as->caps.has_wdrbt = version >= 0x210; +	as->caps.has_dma_support = version >= 0x212; +} +  /*-------------------------------------------------------------------------*/  static int atmel_spi_probe(struct platform_device *pdev) @@ -963,15 +1508,39 @@ static int atmel_spi_probe(struct platform_device *pdev)  	spin_lock_init(&as->lock);  	INIT_LIST_HEAD(&as->queue); +  	as->pdev = pdev;  	as->regs = ioremap(regs->start, resource_size(regs));  	if (!as->regs)  		goto out_free_buffer; +	as->phybase = regs->start;  	as->irq = irq;  	as->clk = clk; -	ret = request_irq(irq, atmel_spi_interrupt, 0, -			dev_name(&pdev->dev), master); +	atmel_get_caps(as); + +	as->use_dma = false; +	as->use_pdc = false; +	if (as->caps.has_dma_support) { +		if (atmel_spi_configure_dma(as) == 0) +			as->use_dma = true; +	} else { +		as->use_pdc = true; +	} + +	if (as->caps.has_dma_support && !as->use_dma) +		dev_info(&pdev->dev, "Atmel SPI Controller using PIO only\n"); + +	if (as->use_pdc) { +		ret = request_irq(irq, atmel_spi_pdc_interrupt, 0, +					dev_name(&pdev->dev), master); +	} else { +		tasklet_init(&as->tasklet, atmel_spi_tasklet_func, +					(unsigned long)master); + +		ret = request_irq(irq, atmel_spi_pio_interrupt, 0, +					dev_name(&pdev->dev), master); +	}  	if (ret)  		goto out_unmap_regs; @@ -979,8 +1548,15 @@ static int atmel_spi_probe(struct platform_device *pdev)  	clk_enable(clk);  	spi_writel(as, CR, SPI_BIT(SWRST));  	spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */ -	spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS)); -	spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS)); +	if (as->caps.has_wdrbt) { +		spi_writel(as, MR, SPI_BIT(WDRBT) | SPI_BIT(MODFDIS) +				| SPI_BIT(MSTR)); +	} else { +		spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS)); +	} + +	if (as->use_pdc) +		spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));  	spi_writel(as, CR, SPI_BIT(SPIEN));  	/* go! */ @@ -989,11 +1565,14 @@ static int atmel_spi_probe(struct platform_device *pdev)  	ret = spi_register_master(master);  	if (ret) -		goto out_reset_hw; +		goto out_free_dma;  	return 0; -out_reset_hw: +out_free_dma: +	if (as->use_dma) +		atmel_spi_release_dma(as); +  	spi_writel(as, CR, SPI_BIT(SWRST));  	spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */  	clk_disable(clk); @@ -1001,6 +1580,8 @@ out_reset_hw:  out_unmap_regs:  	iounmap(as->regs);  out_free_buffer: +	if (!as->use_pdc) +		tasklet_kill(&as->tasklet);  	dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,  			as->buffer_dma);  out_free: @@ -1014,10 +1595,16 @@ static int atmel_spi_remove(struct platform_device *pdev)  	struct spi_master	*master = platform_get_drvdata(pdev);  	struct atmel_spi	*as = spi_master_get_devdata(master);  	struct spi_message	*msg; +	struct spi_transfer	*xfer;  	/* reset the hardware and block queue progress */  	spin_lock_irq(&as->lock);  	as->stopping = 1; +	if (as->use_dma) { +		atmel_spi_stop_dma(as); +		atmel_spi_release_dma(as); +	} +  	spi_writel(as, CR, SPI_BIT(SWRST));  	spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */  	spi_readl(as, SR); @@ -1025,13 +1612,18 @@ static int atmel_spi_remove(struct platform_device *pdev)  	/* Terminate remaining queued transfers */  	list_for_each_entry(msg, &as->queue, queue) { -		/* REVISIT unmapping the dma is a NOP on ARM and AVR32 -		 * but we shouldn't depend on that... -		 */ +		list_for_each_entry(xfer, &msg->transfers, transfer_list) { +			if (!msg->is_dma_mapped +				&& (atmel_spi_use_dma(as, xfer) +					|| as->use_pdc)) +				atmel_spi_dma_unmap_xfer(master, xfer); +		}  		msg->status = -ESHUTDOWN;  		msg->complete(msg->context);  	} +	if (!as->use_pdc) +		tasklet_kill(&as->tasklet);  	dma_free_coherent(&pdev->dev, BUFFER_SIZE, as->buffer,  			as->buffer_dma);  |