From: Wenyou Yang <wenyou.yang@atmel.com>
To: <broonie@kernel.org>
Cc: <nicolas.ferre@atmel.com>, <richard.genoud@gmail.com>,
<linux-spi@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<linux-arm-kernel@lists.infradead.org>, <wenyou.yang@atmel.com>
Subject: [PATCH v2] spi: atmel: Refactor spi-atmel to use SPI framework queue
Date: Thu, 9 Jan 2014 13:19:15 +0800 [thread overview]
Message-ID: <1389244755-16188-1-git-send-email-wenyou.yang@atmel.com> (raw)
Replace the deprecated master->transfer with transfer_one_message()
and allow the SPI subsystem handle all the queuing of messages.
Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
Tested-by: Richard Genoud <richard.genoud@gmail.com>
---
Hi Mark,
Thanks for your feedback.
According to your advice, I prepared this version 2.
The patch is based on the for-next branch of
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git
I tested it on sama5d3xek and at91sam9m10g45ek board.
Richard helped tested it on at91sam9g35-ek board.
Best Regards,
Wenyou Yang
v2 changelog:
1./ Rebase the latest for-next branch included the patch from Richard.
2./ remove the lock on complete(&as->xfer_completion).
3./ rework the xfer->cs_change is true.
4./ remove the member stopping of struct atmel_spi.
drivers/spi/spi-atmel.c | 678 +++++++++++++++--------------------------------
1 file changed, 220 insertions(+), 458 deletions(-)
diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c
index b96f9a8..b0842f7 100644
--- a/drivers/spi/spi-atmel.c
+++ b/drivers/spi/spi-atmel.c
@@ -189,6 +189,8 @@
*/
#define DMA_MIN_BYTES 16
+#define SPI_DMA_TIMEOUT (msecs_to_jiffies(1000))
+
struct atmel_spi_dma {
struct dma_chan *chan_rx;
struct dma_chan *chan_tx;
@@ -220,17 +222,13 @@ struct atmel_spi {
int irq;
struct clk *clk;
struct platform_device *pdev;
- struct spi_device *stay;
- 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;
+ struct completion xfer_completion;
+
/* scratch buffer */
void *buffer;
dma_addr_t buffer_dma;
@@ -241,6 +239,9 @@ struct atmel_spi {
bool use_pdc;
/* dmaengine data */
struct atmel_spi_dma dma;
+
+ bool keep_cs;
+ bool cs_active;
};
/* Controller-specific per-slave state */
@@ -376,17 +377,6 @@ static inline bool atmel_spi_use_dma(struct atmel_spi *as,
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)
-{
- return msg->transfers.prev == &xfer->transfer_list;
-}
-
-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)
@@ -513,23 +503,20 @@ 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);
+ complete(&as->xfer_completion);
}
/*
* 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);
+ unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;
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)) {
@@ -537,13 +524,14 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master,
cpu_relax();
}
- if (xfer->tx_buf)
+ if (xfer->tx_buf) {
if (xfer->bits_per_word > 8)
- spi_writel(as, TDR, *(u16 *)(xfer->tx_buf));
+ spi_writel(as, TDR, *(u16 *)(xfer->tx_buf + xfer_pos));
else
- spi_writel(as, TDR, *(u8 *)(xfer->tx_buf));
- else
+ spi_writel(as, TDR, *(u8 *)(xfer->tx_buf + xfer_pos));
+ } else {
spi_writel(as, TDR, 0);
+ }
dev_dbg(master->dev.parent,
" start pio xfer %p: len %u tx %p rx %p bitpw %d\n",
@@ -556,7 +544,6 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master,
/*
* 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,
@@ -747,71 +734,37 @@ static int atmel_spi_set_xfer_speed(struct atmel_spi *as,
* lock is held, spi irq is blocked
*/
static void atmel_spi_pdc_next_xfer(struct spi_master *master,
- struct spi_message *msg)
+ struct spi_message *msg,
+ struct spi_transfer *xfer)
{
struct atmel_spi *as = spi_master_get_devdata(master);
- struct spi_transfer *xfer;
- u32 len, remaining;
- u32 ieval;
+ u32 len;
dma_addr_t tx_dma, rx_dma;
- if (!as->current_transfer)
- xfer = list_entry(msg->transfers.next,
- struct spi_transfer, transfer_list);
- else if (!as->next_transfer)
- xfer = list_entry(as->current_transfer->transfer_list.next,
- struct spi_transfer, transfer_list);
- else
- xfer = NULL;
-
- if (xfer) {
- spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
-
- len = xfer->len;
- atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
- remaining = xfer->len - len;
-
- spi_writel(as, RPR, rx_dma);
- spi_writel(as, TPR, tx_dma);
-
- if (msg->spi->bits_per_word > 8)
- len >>= 1;
- spi_writel(as, RCR, len);
- spi_writel(as, TCR, len);
-
- atmel_spi_set_xfer_speed(as, msg->spi, xfer);
-
- dev_dbg(&msg->spi->dev,
- " start xfer %p: len %u tx %p/%08llx rx %p/%08llx\n",
- xfer, xfer->len, xfer->tx_buf,
- (unsigned long long)xfer->tx_dma, xfer->rx_buf,
- (unsigned long long)xfer->rx_dma);
- } else {
- xfer = as->next_transfer;
- remaining = as->next_remaining_bytes;
- }
+ spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
- as->current_transfer = xfer;
- as->current_remaining_bytes = remaining;
+ len = as->current_remaining_bytes;
+ atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+ as->current_remaining_bytes -= len;
- if (remaining > 0)
- len = remaining;
- else if (!atmel_spi_xfer_is_last(msg, xfer)
- && atmel_spi_xfer_can_be_chained(xfer)) {
- xfer = list_entry(xfer->transfer_list.next,
- struct spi_transfer, transfer_list);
- len = xfer->len;
- } else
- xfer = NULL;
+ spi_writel(as, RPR, rx_dma);
+ spi_writel(as, TPR, tx_dma);
- as->next_transfer = xfer;
+ if (msg->spi->bits_per_word > 8)
+ len >>= 1;
+ spi_writel(as, RCR, len);
+ spi_writel(as, TCR, len);
- if (xfer) {
- u32 total;
+ dev_dbg(&msg->spi->dev,
+ " start xfer %p: len %u tx %p/%08llx rx %p/%08llx\n",
+ xfer, xfer->len, xfer->tx_buf,
+ (unsigned long long)xfer->tx_dma, xfer->rx_buf,
+ (unsigned long long)xfer->rx_dma);
- total = len;
+ if (as->current_remaining_bytes) {
+ len = as->current_remaining_bytes;
atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
- as->next_remaining_bytes = total - len;
+ as->current_remaining_bytes -= len;
spi_writel(as, RNPR, rx_dma);
spi_writel(as, TNPR, tx_dma);
@@ -826,11 +779,6 @@ static void atmel_spi_pdc_next_xfer(struct spi_master *master,
xfer, xfer->len, xfer->tx_buf,
(unsigned long long)xfer->tx_dma, xfer->rx_buf,
(unsigned long long)xfer->rx_dma);
- ieval = SPI_BIT(ENDRX) | SPI_BIT(OVRES);
- } else {
- spi_writel(as, RNCR, 0);
- spi_writel(as, TNCR, 0);
- ieval = SPI_BIT(RXBUFF) | SPI_BIT(ENDRX) | SPI_BIT(OVRES);
}
/* REVISIT: We're waiting for ENDRX before we start the next
@@ -843,84 +791,11 @@ static void atmel_spi_pdc_next_xfer(struct spi_master *master,
*
* It should be doable, though. Just not now...
*/
- spi_writel(as, IER, ieval);
+ spi_writel(as, IER, SPI_BIT(ENDRX) | SPI_BIT(OVRES));
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;
- atmel_spi_set_xfer_speed(as, msg->spi, xfer);
- }
-
- 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);
- struct spi_message *msg;
- struct spi_device *spi;
-
- BUG_ON(as->current_transfer);
-
- msg = list_entry(as->queue.next, struct spi_message, queue);
- spi = msg->spi;
-
- dev_dbg(master->dev.parent, "start message %p for %s\n",
- msg, dev_name(&spi->dev));
-
- /* select chip if it's not still active */
- if (as->stay) {
- if (as->stay != spi) {
- cs_deactivate(as, as->stay);
- cs_activate(as, spi);
- }
- as->stay = NULL;
- } else
- cs_activate(as, spi);
-
- if (as->use_pdc)
- atmel_spi_pdc_next_xfer(master, msg);
- else
- atmel_spi_dma_next_xfer(master, msg);
-}
-
-/*
* For DMA, tx_buf/tx_dma have the same relationship as rx_buf/rx_dma:
* - The buffer is either valid for CPU access, else NULL
* - If the buffer is valid, so is its DMA address
@@ -975,41 +850,7 @@ 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 stay)
-{
- if (!stay || as->done_status < 0)
- cs_deactivate(as, msg->spi);
- else
- as->stay = msg->spi;
-
- list_del(&msg->queue);
- msg->status = as->done_status;
-
- dev_dbg(master->dev.parent,
- "xfer complete: %u bytes transferred\n",
- msg->actual_length);
-
- atmel_spi_unlock(as);
- msg->complete(msg->context);
- 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) {
- 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.
@@ -1017,9 +858,7 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
static void
atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
{
- u8 *txp;
u8 *rxp;
- u16 *txp16;
u16 *rxp16;
unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;
@@ -1041,96 +880,12 @@ atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
} else {
as->current_remaining_bytes--;
}
-
- if (as->current_remaining_bytes) {
- if (xfer->tx_buf) {
- if (xfer->bits_per_word > 8) {
- txp16 = (u16 *)(((u8 *)xfer->tx_buf)
- + xfer_pos + 2);
- spi_writel(as, TDR, *txp16);
- } else {
- 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.
+ * only information modified.
*/
static irqreturn_t
atmel_spi_pio_interrupt(int irq, void *dev_id)
@@ -1158,8 +913,6 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
*
* 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();
@@ -1167,7 +920,7 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
/* Clear any overrun happening while cleaning up */
spi_readl(as, SR);
- tasklet_schedule(&as->tasklet);
+ complete(&as->xfer_completion);
} else if (pending & SPI_BIT(RDRF)) {
atmel_spi_lock(as);
@@ -1176,11 +929,10 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
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 */
+ if (!as->current_remaining_bytes)
spi_writel(as, IDR, pending);
- tasklet_schedule(&as->tasklet);
- }
+
+ complete(&as->xfer_completion);
}
atmel_spi_unlock(as);
@@ -1198,116 +950,35 @@ atmel_spi_pdc_interrupt(int irq, void *dev_id)
{
struct spi_master *master = dev_id;
struct atmel_spi *as = spi_master_get_devdata(master);
- struct spi_message *msg;
- struct spi_transfer *xfer;
u32 status, pending, imr;
int ret = IRQ_NONE;
- atmel_spi_lock(as);
-
- xfer = as->current_transfer;
- msg = list_entry(as->queue.next, struct spi_message, queue);
-
imr = spi_readl(as, IMR);
status = spi_readl(as, SR);
pending = status & imr;
if (pending & SPI_BIT(OVRES)) {
- int timeout;
ret = IRQ_HANDLED;
spi_writel(as, IDR, (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX)
| SPI_BIT(OVRES)));
- /*
- * 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.
- *
- * First, stop the transfer and unmap the DMA buffers.
- */
- spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
- if (!msg->is_dma_mapped)
- atmel_spi_dma_unmap_xfer(master, xfer);
-
- /* REVISIT: udelay in irq is unfriendly */
- if (xfer->delay_usecs)
- udelay(xfer->delay_usecs);
-
- dev_warn(master->dev.parent, "overrun (%u/%u remaining)\n",
- spi_readl(as, TCR), spi_readl(as, RCR));
-
- /*
- * Clean up DMA registers and make sure the data
- * registers are empty.
- */
- spi_writel(as, RNCR, 0);
- spi_writel(as, TNCR, 0);
- spi_writel(as, RCR, 0);
- spi_writel(as, TCR, 0);
- for (timeout = 1000; timeout; timeout--)
- if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
- break;
- if (!timeout)
- dev_warn(master->dev.parent,
- "timeout waiting for TXEMPTY");
- while (spi_readl(as, SR) & SPI_BIT(RDRF))
- spi_readl(as, RDR);
-
/* Clear any overrun happening while cleaning up */
spi_readl(as, SR);
as->done_status = -EIO;
- atmel_spi_msg_done(master, as, msg, 0);
+
+ complete(&as->xfer_completion);
+
} else if (pending & (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX))) {
ret = IRQ_HANDLED;
spi_writel(as, IDR, pending);
- if (as->current_remaining_bytes == 0) {
- msg->actual_length += xfer->len;
-
- if (!msg->is_dma_mapped)
- atmel_spi_dma_unmap_xfer(master, xfer);
-
- /* REVISIT: udelay in irq is unfriendly */
- if (xfer->delay_usecs)
- udelay(xfer->delay_usecs);
-
- if (atmel_spi_xfer_is_last(msg, xfer)) {
- /* report completed 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_pdc_next_xfer(master, msg);
- }
- } else {
- /*
- * Keep going, we still have data to send in
- * the current transfer.
- */
- atmel_spi_pdc_next_xfer(master, msg);
- }
+ complete(&as->xfer_completion);
}
- atmel_spi_unlock(as);
-
return ret;
}
@@ -1322,9 +993,6 @@ static int atmel_spi_setup(struct spi_device *spi)
as = spi_master_get_devdata(spi->master);
- if (as->stopping)
- return -ESHUTDOWN;
-
if (spi->chip_select > spi->master->num_chipselect) {
dev_dbg(&spi->dev,
"setup: invalid chipselect %u (%u defined)\n",
@@ -1376,12 +1044,6 @@ static int atmel_spi_setup(struct spi_device *spi)
asd->npcs_pin = npcs_pin;
spi->controller_state = asd;
gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
- } else {
- atmel_spi_lock(as);
- if (as->stay == spi)
- as->stay = NULL;
- cs_deactivate(as, spi);
- atmel_spi_unlock(as);
}
asd->csr = csr;
@@ -1396,97 +1058,218 @@ static int atmel_spi_setup(struct spi_device *spi)
return 0;
}
-static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+static int atmel_spi_one_transfer(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer *xfer)
{
struct atmel_spi *as;
- struct spi_transfer *xfer;
- struct device *controller = spi->master->dev.parent;
+ struct spi_device *spi = msg->spi;
u8 bits;
+ u32 len;
struct atmel_spi_device *asd;
+ int timeout;
+ int ret;
- as = spi_master_get_devdata(spi->master);
-
- dev_dbg(controller, "new message %p submitted for %s\n",
- msg, dev_name(&spi->dev));
+ as = spi_master_get_devdata(master);
- if (unlikely(list_empty(&msg->transfers)))
+ if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
+ dev_dbg(&spi->dev, "missing rx or tx buf\n");
return -EINVAL;
+ }
- if (as->stopping)
- return -ESHUTDOWN;
+ if (xfer->bits_per_word) {
+ asd = spi->controller_state;
+ bits = (asd->csr >> 4) & 0xf;
+ if (bits != xfer->bits_per_word - 8) {
+ dev_dbg(&spi->dev,
+ "you can't yet change bits_per_word in transfers\n");
+ return -ENOPROTOOPT;
+ }
+ }
- list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
- dev_dbg(&spi->dev, "missing rx or tx buf\n");
+ if (xfer->bits_per_word > 8) {
+ if (xfer->len % 2) {
+ dev_dbg(&spi->dev,
+ "buffer len should be 16 bits aligned\n");
return -EINVAL;
}
+ }
+
+ /*
+ * DMA map early, for performance (empties dcache ASAP) and
+ * better fault reporting.
+ */
+ 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;
+ }
- if (xfer->bits_per_word) {
- asd = spi->controller_state;
- bits = (asd->csr >> 4) & 0xf;
- if (bits != xfer->bits_per_word - 8) {
- dev_dbg(&spi->dev,
- "you can't yet change bits_per_word in transfers\n");
- return -ENOPROTOOPT;
+ atmel_spi_set_xfer_speed(as, msg->spi, xfer);
+
+ as->done_status = 0;
+ as->current_transfer = xfer;
+ as->current_remaining_bytes = xfer->len;
+ while (as->current_remaining_bytes) {
+ reinit_completion(&as->xfer_completion);
+
+ if (as->use_pdc) {
+ atmel_spi_pdc_next_xfer(master, msg, xfer);
+ } else if (atmel_spi_use_dma(as, xfer)) {
+ len = as->current_remaining_bytes;
+ ret = atmel_spi_next_xfer_dma_submit(master,
+ xfer, &len);
+ if (ret) {
+ dev_err(&spi->dev,
+ "unable to use DMA, fallback to PIO\n");
+ atmel_spi_next_xfer_pio(master, xfer);
+ } else {
+ as->current_remaining_bytes -= len;
}
+ } else {
+ atmel_spi_next_xfer_pio(master, xfer);
}
- if (xfer->bits_per_word > 8) {
- if (xfer->len % 2) {
- dev_dbg(&spi->dev, "buffer len should be 16 bits aligned\n");
- return -EINVAL;
- }
+ ret = wait_for_completion_timeout(&as->xfer_completion,
+ SPI_DMA_TIMEOUT);
+ if (WARN_ON(ret == 0)) {
+ dev_err(&spi->dev,
+ "spi trasfer timeout, err %d\n", ret);
+ as->done_status = -EIO;
+ } else {
+ ret = 0;
}
- /*
- * DMA map early, for performance (empties dcache ASAP) and
- * better fault reporting.
- */
- 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;
+ if (as->done_status)
+ break;
+ }
+
+ if (as->done_status) {
+ if (as->use_pdc) {
+ dev_warn(master->dev.parent,
+ "overrun (%u/%u remaining)\n",
+ spi_readl(as, TCR), spi_readl(as, RCR));
+
+ /*
+ * Clean up DMA registers and make sure the data
+ * registers are empty.
+ */
+ spi_writel(as, RNCR, 0);
+ spi_writel(as, TNCR, 0);
+ spi_writel(as, RCR, 0);
+ spi_writel(as, TCR, 0);
+ for (timeout = 1000; timeout; timeout--)
+ if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
+ break;
+ if (!timeout)
+ dev_warn(master->dev.parent,
+ "timeout waiting for TXEMPTY");
+ while (spi_readl(as, SR) & SPI_BIT(RDRF))
+ spi_readl(as, RDR);
+
+ /* Clear any overrun happening while cleaning up */
+ spi_readl(as, SR);
+
+ } else if (atmel_spi_use_dma(as, xfer)) {
+ atmel_spi_stop_dma(as);
+ }
+
+ if (!msg->is_dma_mapped
+ && (atmel_spi_use_dma(as, xfer) || as->use_pdc))
+ atmel_spi_dma_unmap_xfer(master, xfer);
+
+ return 0;
+
+ } else {
+ /* only update length if no error */
+ msg->actual_length += xfer->len;
+ }
+
+ if (!msg->is_dma_mapped
+ && (atmel_spi_use_dma(as, xfer) || as->use_pdc))
+ atmel_spi_dma_unmap_xfer(master, xfer);
+
+ if (xfer->delay_usecs)
+ udelay(xfer->delay_usecs);
+
+ if (xfer->cs_change) {
+ if (list_is_last(&xfer->transfer_list,
+ &msg->transfers)) {
+ as->keep_cs = true;
+ } else {
+ as->cs_active = !as->cs_active;
+ if (as->cs_active)
+ cs_activate(as, msg->spi);
+ else
+ cs_deactivate(as, msg->spi);
}
}
-#ifdef VERBOSE
+ return 0;
+}
+
+static int atmel_spi_transfer_one_message(struct spi_master *master,
+ struct spi_message *msg)
+{
+ struct atmel_spi *as;
+ struct spi_transfer *xfer;
+ struct spi_device *spi = msg->spi;
+ int ret = 0;
+
+ as = spi_master_get_devdata(master);
+
+ dev_dbg(&spi->dev, "new message %p submitted for %s\n",
+ msg, dev_name(&spi->dev));
+
+ if (unlikely(list_empty(&msg->transfers)))
+ return -EINVAL;
+
+ atmel_spi_lock(as);
+ cs_activate(as, spi);
+
+ as->cs_active = true;
+ as->keep_cs = false;
+
+ msg->status = 0;
+ msg->actual_length = 0;
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ ret = atmel_spi_one_transfer(master, msg, xfer);
+ if (ret)
+ goto msg_done;
+ }
+
+ if (as->use_pdc)
+ atmel_spi_disable_pdc_transfer(as);
+
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- dev_dbg(controller,
+ dev_dbg(&spi->dev,
" 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);
}
-#endif
- msg->status = -EINPROGRESS;
- msg->actual_length = 0;
+msg_done:
+ if (!as->keep_cs)
+ cs_deactivate(as, msg->spi);
- atmel_spi_lock(as);
- list_add_tail(&msg->queue, &as->queue);
- if (!as->current_transfer)
- atmel_spi_next_message(spi->master);
atmel_spi_unlock(as);
- return 0;
+ msg->status = as->done_status;
+ spi_finalize_current_message(spi->master);
+
+ return ret;
}
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;
if (!asd)
return;
- atmel_spi_lock(as);
- if (as->stay == spi) {
- as->stay = NULL;
- cs_deactivate(as, spi);
- }
- atmel_spi_unlock(as);
-
spi->controller_state = NULL;
gpio_free(gpio);
kfree(asd);
@@ -1545,7 +1328,7 @@ static int atmel_spi_probe(struct platform_device *pdev)
master->bus_num = pdev->id;
master->num_chipselect = master->dev.of_node ? 0 : 4;
master->setup = atmel_spi_setup;
- master->transfer = atmel_spi_transfer;
+ master->transfer_one_message = atmel_spi_transfer_one_message;
master->cleanup = atmel_spi_cleanup;
platform_set_drvdata(pdev, master);
@@ -1561,7 +1344,6 @@ static int atmel_spi_probe(struct platform_device *pdev)
goto out_free;
spin_lock_init(&as->lock);
- INIT_LIST_HEAD(&as->queue);
as->pdev = pdev;
as->regs = devm_ioremap_resource(&pdev->dev, regs);
@@ -1573,6 +1355,8 @@ static int atmel_spi_probe(struct platform_device *pdev)
as->irq = irq;
as->clk = clk;
+ init_completion(&as->xfer_completion);
+
atmel_get_caps(as);
as->use_dma = false;
@@ -1591,9 +1375,6 @@ static int atmel_spi_probe(struct platform_device *pdev)
ret = devm_request_irq(&pdev->dev, irq, atmel_spi_pdc_interrupt,
0, dev_name(&pdev->dev), master);
} else {
- tasklet_init(&as->tasklet, atmel_spi_tasklet_func,
- (unsigned long)master);
-
ret = devm_request_irq(&pdev->dev, irq, atmel_spi_pio_interrupt,
0, dev_name(&pdev->dev), master);
}
@@ -1637,8 +1418,6 @@ out_free_dma:
out_free_irq:
out_unmap_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:
@@ -1650,12 +1429,9 @@ 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);
@@ -1666,20 +1442,6 @@ static int atmel_spi_remove(struct platform_device *pdev)
spi_readl(as, SR);
spin_unlock_irq(&as->lock);
- /* Terminate remaining queued transfers */
- list_for_each_entry(msg, &as->queue, queue) {
- 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);
--
1.7.9.5
WARNING: multiple messages have this Message-ID (diff)
From: Wenyou Yang <wenyou.yang@atmel.com>
To: <broonie@kernel.org>
Cc: richard.genoud@gmail.com, nicolas.ferre@atmel.com,
linux-kernel@vger.kernel.org, wenyou.yang@atmel.com,
linux-spi@vger.kernel.org, linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2] spi: atmel: Refactor spi-atmel to use SPI framework queue
Date: Thu, 9 Jan 2014 13:19:15 +0800 [thread overview]
Message-ID: <1389244755-16188-1-git-send-email-wenyou.yang@atmel.com> (raw)
Replace the deprecated master->transfer with transfer_one_message()
and allow the SPI subsystem handle all the queuing of messages.
Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
Tested-by: Richard Genoud <richard.genoud@gmail.com>
---
Hi Mark,
Thanks for your feedback.
According to your advice, I prepared this version 2.
The patch is based on the for-next branch of
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git
I tested it on sama5d3xek and at91sam9m10g45ek board.
Richard helped tested it on at91sam9g35-ek board.
Best Regards,
Wenyou Yang
v2 changelog:
1./ Rebase the latest for-next branch included the patch from Richard.
2./ remove the lock on complete(&as->xfer_completion).
3./ rework the xfer->cs_change is true.
4./ remove the member stopping of struct atmel_spi.
drivers/spi/spi-atmel.c | 678 +++++++++++++++--------------------------------
1 file changed, 220 insertions(+), 458 deletions(-)
diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c
index b96f9a8..b0842f7 100644
--- a/drivers/spi/spi-atmel.c
+++ b/drivers/spi/spi-atmel.c
@@ -189,6 +189,8 @@
*/
#define DMA_MIN_BYTES 16
+#define SPI_DMA_TIMEOUT (msecs_to_jiffies(1000))
+
struct atmel_spi_dma {
struct dma_chan *chan_rx;
struct dma_chan *chan_tx;
@@ -220,17 +222,13 @@ struct atmel_spi {
int irq;
struct clk *clk;
struct platform_device *pdev;
- struct spi_device *stay;
- 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;
+ struct completion xfer_completion;
+
/* scratch buffer */
void *buffer;
dma_addr_t buffer_dma;
@@ -241,6 +239,9 @@ struct atmel_spi {
bool use_pdc;
/* dmaengine data */
struct atmel_spi_dma dma;
+
+ bool keep_cs;
+ bool cs_active;
};
/* Controller-specific per-slave state */
@@ -376,17 +377,6 @@ static inline bool atmel_spi_use_dma(struct atmel_spi *as,
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)
-{
- return msg->transfers.prev == &xfer->transfer_list;
-}
-
-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)
@@ -513,23 +503,20 @@ 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);
+ complete(&as->xfer_completion);
}
/*
* 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);
+ unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;
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)) {
@@ -537,13 +524,14 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master,
cpu_relax();
}
- if (xfer->tx_buf)
+ if (xfer->tx_buf) {
if (xfer->bits_per_word > 8)
- spi_writel(as, TDR, *(u16 *)(xfer->tx_buf));
+ spi_writel(as, TDR, *(u16 *)(xfer->tx_buf + xfer_pos));
else
- spi_writel(as, TDR, *(u8 *)(xfer->tx_buf));
- else
+ spi_writel(as, TDR, *(u8 *)(xfer->tx_buf + xfer_pos));
+ } else {
spi_writel(as, TDR, 0);
+ }
dev_dbg(master->dev.parent,
" start pio xfer %p: len %u tx %p rx %p bitpw %d\n",
@@ -556,7 +544,6 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master,
/*
* 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,
@@ -747,71 +734,37 @@ static int atmel_spi_set_xfer_speed(struct atmel_spi *as,
* lock is held, spi irq is blocked
*/
static void atmel_spi_pdc_next_xfer(struct spi_master *master,
- struct spi_message *msg)
+ struct spi_message *msg,
+ struct spi_transfer *xfer)
{
struct atmel_spi *as = spi_master_get_devdata(master);
- struct spi_transfer *xfer;
- u32 len, remaining;
- u32 ieval;
+ u32 len;
dma_addr_t tx_dma, rx_dma;
- if (!as->current_transfer)
- xfer = list_entry(msg->transfers.next,
- struct spi_transfer, transfer_list);
- else if (!as->next_transfer)
- xfer = list_entry(as->current_transfer->transfer_list.next,
- struct spi_transfer, transfer_list);
- else
- xfer = NULL;
-
- if (xfer) {
- spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
-
- len = xfer->len;
- atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
- remaining = xfer->len - len;
-
- spi_writel(as, RPR, rx_dma);
- spi_writel(as, TPR, tx_dma);
-
- if (msg->spi->bits_per_word > 8)
- len >>= 1;
- spi_writel(as, RCR, len);
- spi_writel(as, TCR, len);
-
- atmel_spi_set_xfer_speed(as, msg->spi, xfer);
-
- dev_dbg(&msg->spi->dev,
- " start xfer %p: len %u tx %p/%08llx rx %p/%08llx\n",
- xfer, xfer->len, xfer->tx_buf,
- (unsigned long long)xfer->tx_dma, xfer->rx_buf,
- (unsigned long long)xfer->rx_dma);
- } else {
- xfer = as->next_transfer;
- remaining = as->next_remaining_bytes;
- }
+ spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
- as->current_transfer = xfer;
- as->current_remaining_bytes = remaining;
+ len = as->current_remaining_bytes;
+ atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+ as->current_remaining_bytes -= len;
- if (remaining > 0)
- len = remaining;
- else if (!atmel_spi_xfer_is_last(msg, xfer)
- && atmel_spi_xfer_can_be_chained(xfer)) {
- xfer = list_entry(xfer->transfer_list.next,
- struct spi_transfer, transfer_list);
- len = xfer->len;
- } else
- xfer = NULL;
+ spi_writel(as, RPR, rx_dma);
+ spi_writel(as, TPR, tx_dma);
- as->next_transfer = xfer;
+ if (msg->spi->bits_per_word > 8)
+ len >>= 1;
+ spi_writel(as, RCR, len);
+ spi_writel(as, TCR, len);
- if (xfer) {
- u32 total;
+ dev_dbg(&msg->spi->dev,
+ " start xfer %p: len %u tx %p/%08llx rx %p/%08llx\n",
+ xfer, xfer->len, xfer->tx_buf,
+ (unsigned long long)xfer->tx_dma, xfer->rx_buf,
+ (unsigned long long)xfer->rx_dma);
- total = len;
+ if (as->current_remaining_bytes) {
+ len = as->current_remaining_bytes;
atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
- as->next_remaining_bytes = total - len;
+ as->current_remaining_bytes -= len;
spi_writel(as, RNPR, rx_dma);
spi_writel(as, TNPR, tx_dma);
@@ -826,11 +779,6 @@ static void atmel_spi_pdc_next_xfer(struct spi_master *master,
xfer, xfer->len, xfer->tx_buf,
(unsigned long long)xfer->tx_dma, xfer->rx_buf,
(unsigned long long)xfer->rx_dma);
- ieval = SPI_BIT(ENDRX) | SPI_BIT(OVRES);
- } else {
- spi_writel(as, RNCR, 0);
- spi_writel(as, TNCR, 0);
- ieval = SPI_BIT(RXBUFF) | SPI_BIT(ENDRX) | SPI_BIT(OVRES);
}
/* REVISIT: We're waiting for ENDRX before we start the next
@@ -843,84 +791,11 @@ static void atmel_spi_pdc_next_xfer(struct spi_master *master,
*
* It should be doable, though. Just not now...
*/
- spi_writel(as, IER, ieval);
+ spi_writel(as, IER, SPI_BIT(ENDRX) | SPI_BIT(OVRES));
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;
- atmel_spi_set_xfer_speed(as, msg->spi, xfer);
- }
-
- 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);
- struct spi_message *msg;
- struct spi_device *spi;
-
- BUG_ON(as->current_transfer);
-
- msg = list_entry(as->queue.next, struct spi_message, queue);
- spi = msg->spi;
-
- dev_dbg(master->dev.parent, "start message %p for %s\n",
- msg, dev_name(&spi->dev));
-
- /* select chip if it's not still active */
- if (as->stay) {
- if (as->stay != spi) {
- cs_deactivate(as, as->stay);
- cs_activate(as, spi);
- }
- as->stay = NULL;
- } else
- cs_activate(as, spi);
-
- if (as->use_pdc)
- atmel_spi_pdc_next_xfer(master, msg);
- else
- atmel_spi_dma_next_xfer(master, msg);
-}
-
-/*
* For DMA, tx_buf/tx_dma have the same relationship as rx_buf/rx_dma:
* - The buffer is either valid for CPU access, else NULL
* - If the buffer is valid, so is its DMA address
@@ -975,41 +850,7 @@ 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 stay)
-{
- if (!stay || as->done_status < 0)
- cs_deactivate(as, msg->spi);
- else
- as->stay = msg->spi;
-
- list_del(&msg->queue);
- msg->status = as->done_status;
-
- dev_dbg(master->dev.parent,
- "xfer complete: %u bytes transferred\n",
- msg->actual_length);
-
- atmel_spi_unlock(as);
- msg->complete(msg->context);
- 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) {
- 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.
@@ -1017,9 +858,7 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
static void
atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
{
- u8 *txp;
u8 *rxp;
- u16 *txp16;
u16 *rxp16;
unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;
@@ -1041,96 +880,12 @@ atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
} else {
as->current_remaining_bytes--;
}
-
- if (as->current_remaining_bytes) {
- if (xfer->tx_buf) {
- if (xfer->bits_per_word > 8) {
- txp16 = (u16 *)(((u8 *)xfer->tx_buf)
- + xfer_pos + 2);
- spi_writel(as, TDR, *txp16);
- } else {
- 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.
+ * only information modified.
*/
static irqreturn_t
atmel_spi_pio_interrupt(int irq, void *dev_id)
@@ -1158,8 +913,6 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
*
* 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();
@@ -1167,7 +920,7 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
/* Clear any overrun happening while cleaning up */
spi_readl(as, SR);
- tasklet_schedule(&as->tasklet);
+ complete(&as->xfer_completion);
} else if (pending & SPI_BIT(RDRF)) {
atmel_spi_lock(as);
@@ -1176,11 +929,10 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
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 */
+ if (!as->current_remaining_bytes)
spi_writel(as, IDR, pending);
- tasklet_schedule(&as->tasklet);
- }
+
+ complete(&as->xfer_completion);
}
atmel_spi_unlock(as);
@@ -1198,116 +950,35 @@ atmel_spi_pdc_interrupt(int irq, void *dev_id)
{
struct spi_master *master = dev_id;
struct atmel_spi *as = spi_master_get_devdata(master);
- struct spi_message *msg;
- struct spi_transfer *xfer;
u32 status, pending, imr;
int ret = IRQ_NONE;
- atmel_spi_lock(as);
-
- xfer = as->current_transfer;
- msg = list_entry(as->queue.next, struct spi_message, queue);
-
imr = spi_readl(as, IMR);
status = spi_readl(as, SR);
pending = status & imr;
if (pending & SPI_BIT(OVRES)) {
- int timeout;
ret = IRQ_HANDLED;
spi_writel(as, IDR, (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX)
| SPI_BIT(OVRES)));
- /*
- * 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.
- *
- * First, stop the transfer and unmap the DMA buffers.
- */
- spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
- if (!msg->is_dma_mapped)
- atmel_spi_dma_unmap_xfer(master, xfer);
-
- /* REVISIT: udelay in irq is unfriendly */
- if (xfer->delay_usecs)
- udelay(xfer->delay_usecs);
-
- dev_warn(master->dev.parent, "overrun (%u/%u remaining)\n",
- spi_readl(as, TCR), spi_readl(as, RCR));
-
- /*
- * Clean up DMA registers and make sure the data
- * registers are empty.
- */
- spi_writel(as, RNCR, 0);
- spi_writel(as, TNCR, 0);
- spi_writel(as, RCR, 0);
- spi_writel(as, TCR, 0);
- for (timeout = 1000; timeout; timeout--)
- if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
- break;
- if (!timeout)
- dev_warn(master->dev.parent,
- "timeout waiting for TXEMPTY");
- while (spi_readl(as, SR) & SPI_BIT(RDRF))
- spi_readl(as, RDR);
-
/* Clear any overrun happening while cleaning up */
spi_readl(as, SR);
as->done_status = -EIO;
- atmel_spi_msg_done(master, as, msg, 0);
+
+ complete(&as->xfer_completion);
+
} else if (pending & (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX))) {
ret = IRQ_HANDLED;
spi_writel(as, IDR, pending);
- if (as->current_remaining_bytes == 0) {
- msg->actual_length += xfer->len;
-
- if (!msg->is_dma_mapped)
- atmel_spi_dma_unmap_xfer(master, xfer);
-
- /* REVISIT: udelay in irq is unfriendly */
- if (xfer->delay_usecs)
- udelay(xfer->delay_usecs);
-
- if (atmel_spi_xfer_is_last(msg, xfer)) {
- /* report completed 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_pdc_next_xfer(master, msg);
- }
- } else {
- /*
- * Keep going, we still have data to send in
- * the current transfer.
- */
- atmel_spi_pdc_next_xfer(master, msg);
- }
+ complete(&as->xfer_completion);
}
- atmel_spi_unlock(as);
-
return ret;
}
@@ -1322,9 +993,6 @@ static int atmel_spi_setup(struct spi_device *spi)
as = spi_master_get_devdata(spi->master);
- if (as->stopping)
- return -ESHUTDOWN;
-
if (spi->chip_select > spi->master->num_chipselect) {
dev_dbg(&spi->dev,
"setup: invalid chipselect %u (%u defined)\n",
@@ -1376,12 +1044,6 @@ static int atmel_spi_setup(struct spi_device *spi)
asd->npcs_pin = npcs_pin;
spi->controller_state = asd;
gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
- } else {
- atmel_spi_lock(as);
- if (as->stay == spi)
- as->stay = NULL;
- cs_deactivate(as, spi);
- atmel_spi_unlock(as);
}
asd->csr = csr;
@@ -1396,97 +1058,218 @@ static int atmel_spi_setup(struct spi_device *spi)
return 0;
}
-static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+static int atmel_spi_one_transfer(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer *xfer)
{
struct atmel_spi *as;
- struct spi_transfer *xfer;
- struct device *controller = spi->master->dev.parent;
+ struct spi_device *spi = msg->spi;
u8 bits;
+ u32 len;
struct atmel_spi_device *asd;
+ int timeout;
+ int ret;
- as = spi_master_get_devdata(spi->master);
-
- dev_dbg(controller, "new message %p submitted for %s\n",
- msg, dev_name(&spi->dev));
+ as = spi_master_get_devdata(master);
- if (unlikely(list_empty(&msg->transfers)))
+ if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
+ dev_dbg(&spi->dev, "missing rx or tx buf\n");
return -EINVAL;
+ }
- if (as->stopping)
- return -ESHUTDOWN;
+ if (xfer->bits_per_word) {
+ asd = spi->controller_state;
+ bits = (asd->csr >> 4) & 0xf;
+ if (bits != xfer->bits_per_word - 8) {
+ dev_dbg(&spi->dev,
+ "you can't yet change bits_per_word in transfers\n");
+ return -ENOPROTOOPT;
+ }
+ }
- list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
- dev_dbg(&spi->dev, "missing rx or tx buf\n");
+ if (xfer->bits_per_word > 8) {
+ if (xfer->len % 2) {
+ dev_dbg(&spi->dev,
+ "buffer len should be 16 bits aligned\n");
return -EINVAL;
}
+ }
+
+ /*
+ * DMA map early, for performance (empties dcache ASAP) and
+ * better fault reporting.
+ */
+ 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;
+ }
- if (xfer->bits_per_word) {
- asd = spi->controller_state;
- bits = (asd->csr >> 4) & 0xf;
- if (bits != xfer->bits_per_word - 8) {
- dev_dbg(&spi->dev,
- "you can't yet change bits_per_word in transfers\n");
- return -ENOPROTOOPT;
+ atmel_spi_set_xfer_speed(as, msg->spi, xfer);
+
+ as->done_status = 0;
+ as->current_transfer = xfer;
+ as->current_remaining_bytes = xfer->len;
+ while (as->current_remaining_bytes) {
+ reinit_completion(&as->xfer_completion);
+
+ if (as->use_pdc) {
+ atmel_spi_pdc_next_xfer(master, msg, xfer);
+ } else if (atmel_spi_use_dma(as, xfer)) {
+ len = as->current_remaining_bytes;
+ ret = atmel_spi_next_xfer_dma_submit(master,
+ xfer, &len);
+ if (ret) {
+ dev_err(&spi->dev,
+ "unable to use DMA, fallback to PIO\n");
+ atmel_spi_next_xfer_pio(master, xfer);
+ } else {
+ as->current_remaining_bytes -= len;
}
+ } else {
+ atmel_spi_next_xfer_pio(master, xfer);
}
- if (xfer->bits_per_word > 8) {
- if (xfer->len % 2) {
- dev_dbg(&spi->dev, "buffer len should be 16 bits aligned\n");
- return -EINVAL;
- }
+ ret = wait_for_completion_timeout(&as->xfer_completion,
+ SPI_DMA_TIMEOUT);
+ if (WARN_ON(ret == 0)) {
+ dev_err(&spi->dev,
+ "spi trasfer timeout, err %d\n", ret);
+ as->done_status = -EIO;
+ } else {
+ ret = 0;
}
- /*
- * DMA map early, for performance (empties dcache ASAP) and
- * better fault reporting.
- */
- 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;
+ if (as->done_status)
+ break;
+ }
+
+ if (as->done_status) {
+ if (as->use_pdc) {
+ dev_warn(master->dev.parent,
+ "overrun (%u/%u remaining)\n",
+ spi_readl(as, TCR), spi_readl(as, RCR));
+
+ /*
+ * Clean up DMA registers and make sure the data
+ * registers are empty.
+ */
+ spi_writel(as, RNCR, 0);
+ spi_writel(as, TNCR, 0);
+ spi_writel(as, RCR, 0);
+ spi_writel(as, TCR, 0);
+ for (timeout = 1000; timeout; timeout--)
+ if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
+ break;
+ if (!timeout)
+ dev_warn(master->dev.parent,
+ "timeout waiting for TXEMPTY");
+ while (spi_readl(as, SR) & SPI_BIT(RDRF))
+ spi_readl(as, RDR);
+
+ /* Clear any overrun happening while cleaning up */
+ spi_readl(as, SR);
+
+ } else if (atmel_spi_use_dma(as, xfer)) {
+ atmel_spi_stop_dma(as);
+ }
+
+ if (!msg->is_dma_mapped
+ && (atmel_spi_use_dma(as, xfer) || as->use_pdc))
+ atmel_spi_dma_unmap_xfer(master, xfer);
+
+ return 0;
+
+ } else {
+ /* only update length if no error */
+ msg->actual_length += xfer->len;
+ }
+
+ if (!msg->is_dma_mapped
+ && (atmel_spi_use_dma(as, xfer) || as->use_pdc))
+ atmel_spi_dma_unmap_xfer(master, xfer);
+
+ if (xfer->delay_usecs)
+ udelay(xfer->delay_usecs);
+
+ if (xfer->cs_change) {
+ if (list_is_last(&xfer->transfer_list,
+ &msg->transfers)) {
+ as->keep_cs = true;
+ } else {
+ as->cs_active = !as->cs_active;
+ if (as->cs_active)
+ cs_activate(as, msg->spi);
+ else
+ cs_deactivate(as, msg->spi);
}
}
-#ifdef VERBOSE
+ return 0;
+}
+
+static int atmel_spi_transfer_one_message(struct spi_master *master,
+ struct spi_message *msg)
+{
+ struct atmel_spi *as;
+ struct spi_transfer *xfer;
+ struct spi_device *spi = msg->spi;
+ int ret = 0;
+
+ as = spi_master_get_devdata(master);
+
+ dev_dbg(&spi->dev, "new message %p submitted for %s\n",
+ msg, dev_name(&spi->dev));
+
+ if (unlikely(list_empty(&msg->transfers)))
+ return -EINVAL;
+
+ atmel_spi_lock(as);
+ cs_activate(as, spi);
+
+ as->cs_active = true;
+ as->keep_cs = false;
+
+ msg->status = 0;
+ msg->actual_length = 0;
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ ret = atmel_spi_one_transfer(master, msg, xfer);
+ if (ret)
+ goto msg_done;
+ }
+
+ if (as->use_pdc)
+ atmel_spi_disable_pdc_transfer(as);
+
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- dev_dbg(controller,
+ dev_dbg(&spi->dev,
" 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);
}
-#endif
- msg->status = -EINPROGRESS;
- msg->actual_length = 0;
+msg_done:
+ if (!as->keep_cs)
+ cs_deactivate(as, msg->spi);
- atmel_spi_lock(as);
- list_add_tail(&msg->queue, &as->queue);
- if (!as->current_transfer)
- atmel_spi_next_message(spi->master);
atmel_spi_unlock(as);
- return 0;
+ msg->status = as->done_status;
+ spi_finalize_current_message(spi->master);
+
+ return ret;
}
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;
if (!asd)
return;
- atmel_spi_lock(as);
- if (as->stay == spi) {
- as->stay = NULL;
- cs_deactivate(as, spi);
- }
- atmel_spi_unlock(as);
-
spi->controller_state = NULL;
gpio_free(gpio);
kfree(asd);
@@ -1545,7 +1328,7 @@ static int atmel_spi_probe(struct platform_device *pdev)
master->bus_num = pdev->id;
master->num_chipselect = master->dev.of_node ? 0 : 4;
master->setup = atmel_spi_setup;
- master->transfer = atmel_spi_transfer;
+ master->transfer_one_message = atmel_spi_transfer_one_message;
master->cleanup = atmel_spi_cleanup;
platform_set_drvdata(pdev, master);
@@ -1561,7 +1344,6 @@ static int atmel_spi_probe(struct platform_device *pdev)
goto out_free;
spin_lock_init(&as->lock);
- INIT_LIST_HEAD(&as->queue);
as->pdev = pdev;
as->regs = devm_ioremap_resource(&pdev->dev, regs);
@@ -1573,6 +1355,8 @@ static int atmel_spi_probe(struct platform_device *pdev)
as->irq = irq;
as->clk = clk;
+ init_completion(&as->xfer_completion);
+
atmel_get_caps(as);
as->use_dma = false;
@@ -1591,9 +1375,6 @@ static int atmel_spi_probe(struct platform_device *pdev)
ret = devm_request_irq(&pdev->dev, irq, atmel_spi_pdc_interrupt,
0, dev_name(&pdev->dev), master);
} else {
- tasklet_init(&as->tasklet, atmel_spi_tasklet_func,
- (unsigned long)master);
-
ret = devm_request_irq(&pdev->dev, irq, atmel_spi_pio_interrupt,
0, dev_name(&pdev->dev), master);
}
@@ -1637,8 +1418,6 @@ out_free_dma:
out_free_irq:
out_unmap_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:
@@ -1650,12 +1429,9 @@ 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);
@@ -1666,20 +1442,6 @@ static int atmel_spi_remove(struct platform_device *pdev)
spi_readl(as, SR);
spin_unlock_irq(&as->lock);
- /* Terminate remaining queued transfers */
- list_for_each_entry(msg, &as->queue, queue) {
- 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);
--
1.7.9.5
WARNING: multiple messages have this Message-ID (diff)
From: wenyou.yang@atmel.com (Wenyou Yang)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v2] spi: atmel: Refactor spi-atmel to use SPI framework queue
Date: Thu, 9 Jan 2014 13:19:15 +0800 [thread overview]
Message-ID: <1389244755-16188-1-git-send-email-wenyou.yang@atmel.com> (raw)
Replace the deprecated master->transfer with transfer_one_message()
and allow the SPI subsystem handle all the queuing of messages.
Signed-off-by: Wenyou Yang <wenyou.yang@atmel.com>
Tested-by: Richard Genoud <richard.genoud@gmail.com>
---
Hi Mark,
Thanks for your feedback.
According to your advice, I prepared this version 2.
The patch is based on the for-next branch of
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git
I tested it on sama5d3xek and at91sam9m10g45ek board.
Richard helped tested it on at91sam9g35-ek board.
Best Regards,
Wenyou Yang
v2 changelog:
1./ Rebase the latest for-next branch included the patch from Richard.
2./ remove the lock on complete(&as->xfer_completion).
3./ rework the xfer->cs_change is true.
4./ remove the member stopping of struct atmel_spi.
drivers/spi/spi-atmel.c | 678 +++++++++++++++--------------------------------
1 file changed, 220 insertions(+), 458 deletions(-)
diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c
index b96f9a8..b0842f7 100644
--- a/drivers/spi/spi-atmel.c
+++ b/drivers/spi/spi-atmel.c
@@ -189,6 +189,8 @@
*/
#define DMA_MIN_BYTES 16
+#define SPI_DMA_TIMEOUT (msecs_to_jiffies(1000))
+
struct atmel_spi_dma {
struct dma_chan *chan_rx;
struct dma_chan *chan_tx;
@@ -220,17 +222,13 @@ struct atmel_spi {
int irq;
struct clk *clk;
struct platform_device *pdev;
- struct spi_device *stay;
- 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;
+ struct completion xfer_completion;
+
/* scratch buffer */
void *buffer;
dma_addr_t buffer_dma;
@@ -241,6 +239,9 @@ struct atmel_spi {
bool use_pdc;
/* dmaengine data */
struct atmel_spi_dma dma;
+
+ bool keep_cs;
+ bool cs_active;
};
/* Controller-specific per-slave state */
@@ -376,17 +377,6 @@ static inline bool atmel_spi_use_dma(struct atmel_spi *as,
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)
-{
- return msg->transfers.prev == &xfer->transfer_list;
-}
-
-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)
@@ -513,23 +503,20 @@ 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);
+ complete(&as->xfer_completion);
}
/*
* 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);
+ unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;
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)) {
@@ -537,13 +524,14 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master,
cpu_relax();
}
- if (xfer->tx_buf)
+ if (xfer->tx_buf) {
if (xfer->bits_per_word > 8)
- spi_writel(as, TDR, *(u16 *)(xfer->tx_buf));
+ spi_writel(as, TDR, *(u16 *)(xfer->tx_buf + xfer_pos));
else
- spi_writel(as, TDR, *(u8 *)(xfer->tx_buf));
- else
+ spi_writel(as, TDR, *(u8 *)(xfer->tx_buf + xfer_pos));
+ } else {
spi_writel(as, TDR, 0);
+ }
dev_dbg(master->dev.parent,
" start pio xfer %p: len %u tx %p rx %p bitpw %d\n",
@@ -556,7 +544,6 @@ static void atmel_spi_next_xfer_pio(struct spi_master *master,
/*
* 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,
@@ -747,71 +734,37 @@ static int atmel_spi_set_xfer_speed(struct atmel_spi *as,
* lock is held, spi irq is blocked
*/
static void atmel_spi_pdc_next_xfer(struct spi_master *master,
- struct spi_message *msg)
+ struct spi_message *msg,
+ struct spi_transfer *xfer)
{
struct atmel_spi *as = spi_master_get_devdata(master);
- struct spi_transfer *xfer;
- u32 len, remaining;
- u32 ieval;
+ u32 len;
dma_addr_t tx_dma, rx_dma;
- if (!as->current_transfer)
- xfer = list_entry(msg->transfers.next,
- struct spi_transfer, transfer_list);
- else if (!as->next_transfer)
- xfer = list_entry(as->current_transfer->transfer_list.next,
- struct spi_transfer, transfer_list);
- else
- xfer = NULL;
-
- if (xfer) {
- spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
-
- len = xfer->len;
- atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
- remaining = xfer->len - len;
-
- spi_writel(as, RPR, rx_dma);
- spi_writel(as, TPR, tx_dma);
-
- if (msg->spi->bits_per_word > 8)
- len >>= 1;
- spi_writel(as, RCR, len);
- spi_writel(as, TCR, len);
-
- atmel_spi_set_xfer_speed(as, msg->spi, xfer);
-
- dev_dbg(&msg->spi->dev,
- " start xfer %p: len %u tx %p/%08llx rx %p/%08llx\n",
- xfer, xfer->len, xfer->tx_buf,
- (unsigned long long)xfer->tx_dma, xfer->rx_buf,
- (unsigned long long)xfer->rx_dma);
- } else {
- xfer = as->next_transfer;
- remaining = as->next_remaining_bytes;
- }
+ spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
- as->current_transfer = xfer;
- as->current_remaining_bytes = remaining;
+ len = as->current_remaining_bytes;
+ atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+ as->current_remaining_bytes -= len;
- if (remaining > 0)
- len = remaining;
- else if (!atmel_spi_xfer_is_last(msg, xfer)
- && atmel_spi_xfer_can_be_chained(xfer)) {
- xfer = list_entry(xfer->transfer_list.next,
- struct spi_transfer, transfer_list);
- len = xfer->len;
- } else
- xfer = NULL;
+ spi_writel(as, RPR, rx_dma);
+ spi_writel(as, TPR, tx_dma);
- as->next_transfer = xfer;
+ if (msg->spi->bits_per_word > 8)
+ len >>= 1;
+ spi_writel(as, RCR, len);
+ spi_writel(as, TCR, len);
- if (xfer) {
- u32 total;
+ dev_dbg(&msg->spi->dev,
+ " start xfer %p: len %u tx %p/%08llx rx %p/%08llx\n",
+ xfer, xfer->len, xfer->tx_buf,
+ (unsigned long long)xfer->tx_dma, xfer->rx_buf,
+ (unsigned long long)xfer->rx_dma);
- total = len;
+ if (as->current_remaining_bytes) {
+ len = as->current_remaining_bytes;
atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
- as->next_remaining_bytes = total - len;
+ as->current_remaining_bytes -= len;
spi_writel(as, RNPR, rx_dma);
spi_writel(as, TNPR, tx_dma);
@@ -826,11 +779,6 @@ static void atmel_spi_pdc_next_xfer(struct spi_master *master,
xfer, xfer->len, xfer->tx_buf,
(unsigned long long)xfer->tx_dma, xfer->rx_buf,
(unsigned long long)xfer->rx_dma);
- ieval = SPI_BIT(ENDRX) | SPI_BIT(OVRES);
- } else {
- spi_writel(as, RNCR, 0);
- spi_writel(as, TNCR, 0);
- ieval = SPI_BIT(RXBUFF) | SPI_BIT(ENDRX) | SPI_BIT(OVRES);
}
/* REVISIT: We're waiting for ENDRX before we start the next
@@ -843,84 +791,11 @@ static void atmel_spi_pdc_next_xfer(struct spi_master *master,
*
* It should be doable, though. Just not now...
*/
- spi_writel(as, IER, ieval);
+ spi_writel(as, IER, SPI_BIT(ENDRX) | SPI_BIT(OVRES));
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;
- atmel_spi_set_xfer_speed(as, msg->spi, xfer);
- }
-
- 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);
- struct spi_message *msg;
- struct spi_device *spi;
-
- BUG_ON(as->current_transfer);
-
- msg = list_entry(as->queue.next, struct spi_message, queue);
- spi = msg->spi;
-
- dev_dbg(master->dev.parent, "start message %p for %s\n",
- msg, dev_name(&spi->dev));
-
- /* select chip if it's not still active */
- if (as->stay) {
- if (as->stay != spi) {
- cs_deactivate(as, as->stay);
- cs_activate(as, spi);
- }
- as->stay = NULL;
- } else
- cs_activate(as, spi);
-
- if (as->use_pdc)
- atmel_spi_pdc_next_xfer(master, msg);
- else
- atmel_spi_dma_next_xfer(master, msg);
-}
-
-/*
* For DMA, tx_buf/tx_dma have the same relationship as rx_buf/rx_dma:
* - The buffer is either valid for CPU access, else NULL
* - If the buffer is valid, so is its DMA address
@@ -975,41 +850,7 @@ 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 stay)
-{
- if (!stay || as->done_status < 0)
- cs_deactivate(as, msg->spi);
- else
- as->stay = msg->spi;
-
- list_del(&msg->queue);
- msg->status = as->done_status;
-
- dev_dbg(master->dev.parent,
- "xfer complete: %u bytes transferred\n",
- msg->actual_length);
-
- atmel_spi_unlock(as);
- msg->complete(msg->context);
- 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) {
- 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.
@@ -1017,9 +858,7 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
static void
atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
{
- u8 *txp;
u8 *rxp;
- u16 *txp16;
u16 *rxp16;
unsigned long xfer_pos = xfer->len - as->current_remaining_bytes;
@@ -1041,96 +880,12 @@ atmel_spi_pump_pio_data(struct atmel_spi *as, struct spi_transfer *xfer)
} else {
as->current_remaining_bytes--;
}
-
- if (as->current_remaining_bytes) {
- if (xfer->tx_buf) {
- if (xfer->bits_per_word > 8) {
- txp16 = (u16 *)(((u8 *)xfer->tx_buf)
- + xfer_pos + 2);
- spi_writel(as, TDR, *txp16);
- } else {
- 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.
+ * only information modified.
*/
static irqreturn_t
atmel_spi_pio_interrupt(int irq, void *dev_id)
@@ -1158,8 +913,6 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
*
* 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();
@@ -1167,7 +920,7 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
/* Clear any overrun happening while cleaning up */
spi_readl(as, SR);
- tasklet_schedule(&as->tasklet);
+ complete(&as->xfer_completion);
} else if (pending & SPI_BIT(RDRF)) {
atmel_spi_lock(as);
@@ -1176,11 +929,10 @@ atmel_spi_pio_interrupt(int irq, void *dev_id)
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 */
+ if (!as->current_remaining_bytes)
spi_writel(as, IDR, pending);
- tasklet_schedule(&as->tasklet);
- }
+
+ complete(&as->xfer_completion);
}
atmel_spi_unlock(as);
@@ -1198,116 +950,35 @@ atmel_spi_pdc_interrupt(int irq, void *dev_id)
{
struct spi_master *master = dev_id;
struct atmel_spi *as = spi_master_get_devdata(master);
- struct spi_message *msg;
- struct spi_transfer *xfer;
u32 status, pending, imr;
int ret = IRQ_NONE;
- atmel_spi_lock(as);
-
- xfer = as->current_transfer;
- msg = list_entry(as->queue.next, struct spi_message, queue);
-
imr = spi_readl(as, IMR);
status = spi_readl(as, SR);
pending = status & imr;
if (pending & SPI_BIT(OVRES)) {
- int timeout;
ret = IRQ_HANDLED;
spi_writel(as, IDR, (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX)
| SPI_BIT(OVRES)));
- /*
- * 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.
- *
- * First, stop the transfer and unmap the DMA buffers.
- */
- spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));
- if (!msg->is_dma_mapped)
- atmel_spi_dma_unmap_xfer(master, xfer);
-
- /* REVISIT: udelay in irq is unfriendly */
- if (xfer->delay_usecs)
- udelay(xfer->delay_usecs);
-
- dev_warn(master->dev.parent, "overrun (%u/%u remaining)\n",
- spi_readl(as, TCR), spi_readl(as, RCR));
-
- /*
- * Clean up DMA registers and make sure the data
- * registers are empty.
- */
- spi_writel(as, RNCR, 0);
- spi_writel(as, TNCR, 0);
- spi_writel(as, RCR, 0);
- spi_writel(as, TCR, 0);
- for (timeout = 1000; timeout; timeout--)
- if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
- break;
- if (!timeout)
- dev_warn(master->dev.parent,
- "timeout waiting for TXEMPTY");
- while (spi_readl(as, SR) & SPI_BIT(RDRF))
- spi_readl(as, RDR);
-
/* Clear any overrun happening while cleaning up */
spi_readl(as, SR);
as->done_status = -EIO;
- atmel_spi_msg_done(master, as, msg, 0);
+
+ complete(&as->xfer_completion);
+
} else if (pending & (SPI_BIT(RXBUFF) | SPI_BIT(ENDRX))) {
ret = IRQ_HANDLED;
spi_writel(as, IDR, pending);
- if (as->current_remaining_bytes == 0) {
- msg->actual_length += xfer->len;
-
- if (!msg->is_dma_mapped)
- atmel_spi_dma_unmap_xfer(master, xfer);
-
- /* REVISIT: udelay in irq is unfriendly */
- if (xfer->delay_usecs)
- udelay(xfer->delay_usecs);
-
- if (atmel_spi_xfer_is_last(msg, xfer)) {
- /* report completed 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_pdc_next_xfer(master, msg);
- }
- } else {
- /*
- * Keep going, we still have data to send in
- * the current transfer.
- */
- atmel_spi_pdc_next_xfer(master, msg);
- }
+ complete(&as->xfer_completion);
}
- atmel_spi_unlock(as);
-
return ret;
}
@@ -1322,9 +993,6 @@ static int atmel_spi_setup(struct spi_device *spi)
as = spi_master_get_devdata(spi->master);
- if (as->stopping)
- return -ESHUTDOWN;
-
if (spi->chip_select > spi->master->num_chipselect) {
dev_dbg(&spi->dev,
"setup: invalid chipselect %u (%u defined)\n",
@@ -1376,12 +1044,6 @@ static int atmel_spi_setup(struct spi_device *spi)
asd->npcs_pin = npcs_pin;
spi->controller_state = asd;
gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
- } else {
- atmel_spi_lock(as);
- if (as->stay == spi)
- as->stay = NULL;
- cs_deactivate(as, spi);
- atmel_spi_unlock(as);
}
asd->csr = csr;
@@ -1396,97 +1058,218 @@ static int atmel_spi_setup(struct spi_device *spi)
return 0;
}
-static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
+static int atmel_spi_one_transfer(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer *xfer)
{
struct atmel_spi *as;
- struct spi_transfer *xfer;
- struct device *controller = spi->master->dev.parent;
+ struct spi_device *spi = msg->spi;
u8 bits;
+ u32 len;
struct atmel_spi_device *asd;
+ int timeout;
+ int ret;
- as = spi_master_get_devdata(spi->master);
-
- dev_dbg(controller, "new message %p submitted for %s\n",
- msg, dev_name(&spi->dev));
+ as = spi_master_get_devdata(master);
- if (unlikely(list_empty(&msg->transfers)))
+ if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
+ dev_dbg(&spi->dev, "missing rx or tx buf\n");
return -EINVAL;
+ }
- if (as->stopping)
- return -ESHUTDOWN;
+ if (xfer->bits_per_word) {
+ asd = spi->controller_state;
+ bits = (asd->csr >> 4) & 0xf;
+ if (bits != xfer->bits_per_word - 8) {
+ dev_dbg(&spi->dev,
+ "you can't yet change bits_per_word in transfers\n");
+ return -ENOPROTOOPT;
+ }
+ }
- list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
- dev_dbg(&spi->dev, "missing rx or tx buf\n");
+ if (xfer->bits_per_word > 8) {
+ if (xfer->len % 2) {
+ dev_dbg(&spi->dev,
+ "buffer len should be 16 bits aligned\n");
return -EINVAL;
}
+ }
+
+ /*
+ * DMA map early, for performance (empties dcache ASAP) and
+ * better fault reporting.
+ */
+ 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;
+ }
- if (xfer->bits_per_word) {
- asd = spi->controller_state;
- bits = (asd->csr >> 4) & 0xf;
- if (bits != xfer->bits_per_word - 8) {
- dev_dbg(&spi->dev,
- "you can't yet change bits_per_word in transfers\n");
- return -ENOPROTOOPT;
+ atmel_spi_set_xfer_speed(as, msg->spi, xfer);
+
+ as->done_status = 0;
+ as->current_transfer = xfer;
+ as->current_remaining_bytes = xfer->len;
+ while (as->current_remaining_bytes) {
+ reinit_completion(&as->xfer_completion);
+
+ if (as->use_pdc) {
+ atmel_spi_pdc_next_xfer(master, msg, xfer);
+ } else if (atmel_spi_use_dma(as, xfer)) {
+ len = as->current_remaining_bytes;
+ ret = atmel_spi_next_xfer_dma_submit(master,
+ xfer, &len);
+ if (ret) {
+ dev_err(&spi->dev,
+ "unable to use DMA, fallback to PIO\n");
+ atmel_spi_next_xfer_pio(master, xfer);
+ } else {
+ as->current_remaining_bytes -= len;
}
+ } else {
+ atmel_spi_next_xfer_pio(master, xfer);
}
- if (xfer->bits_per_word > 8) {
- if (xfer->len % 2) {
- dev_dbg(&spi->dev, "buffer len should be 16 bits aligned\n");
- return -EINVAL;
- }
+ ret = wait_for_completion_timeout(&as->xfer_completion,
+ SPI_DMA_TIMEOUT);
+ if (WARN_ON(ret == 0)) {
+ dev_err(&spi->dev,
+ "spi trasfer timeout, err %d\n", ret);
+ as->done_status = -EIO;
+ } else {
+ ret = 0;
}
- /*
- * DMA map early, for performance (empties dcache ASAP) and
- * better fault reporting.
- */
- 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;
+ if (as->done_status)
+ break;
+ }
+
+ if (as->done_status) {
+ if (as->use_pdc) {
+ dev_warn(master->dev.parent,
+ "overrun (%u/%u remaining)\n",
+ spi_readl(as, TCR), spi_readl(as, RCR));
+
+ /*
+ * Clean up DMA registers and make sure the data
+ * registers are empty.
+ */
+ spi_writel(as, RNCR, 0);
+ spi_writel(as, TNCR, 0);
+ spi_writel(as, RCR, 0);
+ spi_writel(as, TCR, 0);
+ for (timeout = 1000; timeout; timeout--)
+ if (spi_readl(as, SR) & SPI_BIT(TXEMPTY))
+ break;
+ if (!timeout)
+ dev_warn(master->dev.parent,
+ "timeout waiting for TXEMPTY");
+ while (spi_readl(as, SR) & SPI_BIT(RDRF))
+ spi_readl(as, RDR);
+
+ /* Clear any overrun happening while cleaning up */
+ spi_readl(as, SR);
+
+ } else if (atmel_spi_use_dma(as, xfer)) {
+ atmel_spi_stop_dma(as);
+ }
+
+ if (!msg->is_dma_mapped
+ && (atmel_spi_use_dma(as, xfer) || as->use_pdc))
+ atmel_spi_dma_unmap_xfer(master, xfer);
+
+ return 0;
+
+ } else {
+ /* only update length if no error */
+ msg->actual_length += xfer->len;
+ }
+
+ if (!msg->is_dma_mapped
+ && (atmel_spi_use_dma(as, xfer) || as->use_pdc))
+ atmel_spi_dma_unmap_xfer(master, xfer);
+
+ if (xfer->delay_usecs)
+ udelay(xfer->delay_usecs);
+
+ if (xfer->cs_change) {
+ if (list_is_last(&xfer->transfer_list,
+ &msg->transfers)) {
+ as->keep_cs = true;
+ } else {
+ as->cs_active = !as->cs_active;
+ if (as->cs_active)
+ cs_activate(as, msg->spi);
+ else
+ cs_deactivate(as, msg->spi);
}
}
-#ifdef VERBOSE
+ return 0;
+}
+
+static int atmel_spi_transfer_one_message(struct spi_master *master,
+ struct spi_message *msg)
+{
+ struct atmel_spi *as;
+ struct spi_transfer *xfer;
+ struct spi_device *spi = msg->spi;
+ int ret = 0;
+
+ as = spi_master_get_devdata(master);
+
+ dev_dbg(&spi->dev, "new message %p submitted for %s\n",
+ msg, dev_name(&spi->dev));
+
+ if (unlikely(list_empty(&msg->transfers)))
+ return -EINVAL;
+
+ atmel_spi_lock(as);
+ cs_activate(as, spi);
+
+ as->cs_active = true;
+ as->keep_cs = false;
+
+ msg->status = 0;
+ msg->actual_length = 0;
+
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ ret = atmel_spi_one_transfer(master, msg, xfer);
+ if (ret)
+ goto msg_done;
+ }
+
+ if (as->use_pdc)
+ atmel_spi_disable_pdc_transfer(as);
+
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
- dev_dbg(controller,
+ dev_dbg(&spi->dev,
" 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);
}
-#endif
- msg->status = -EINPROGRESS;
- msg->actual_length = 0;
+msg_done:
+ if (!as->keep_cs)
+ cs_deactivate(as, msg->spi);
- atmel_spi_lock(as);
- list_add_tail(&msg->queue, &as->queue);
- if (!as->current_transfer)
- atmel_spi_next_message(spi->master);
atmel_spi_unlock(as);
- return 0;
+ msg->status = as->done_status;
+ spi_finalize_current_message(spi->master);
+
+ return ret;
}
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;
if (!asd)
return;
- atmel_spi_lock(as);
- if (as->stay == spi) {
- as->stay = NULL;
- cs_deactivate(as, spi);
- }
- atmel_spi_unlock(as);
-
spi->controller_state = NULL;
gpio_free(gpio);
kfree(asd);
@@ -1545,7 +1328,7 @@ static int atmel_spi_probe(struct platform_device *pdev)
master->bus_num = pdev->id;
master->num_chipselect = master->dev.of_node ? 0 : 4;
master->setup = atmel_spi_setup;
- master->transfer = atmel_spi_transfer;
+ master->transfer_one_message = atmel_spi_transfer_one_message;
master->cleanup = atmel_spi_cleanup;
platform_set_drvdata(pdev, master);
@@ -1561,7 +1344,6 @@ static int atmel_spi_probe(struct platform_device *pdev)
goto out_free;
spin_lock_init(&as->lock);
- INIT_LIST_HEAD(&as->queue);
as->pdev = pdev;
as->regs = devm_ioremap_resource(&pdev->dev, regs);
@@ -1573,6 +1355,8 @@ static int atmel_spi_probe(struct platform_device *pdev)
as->irq = irq;
as->clk = clk;
+ init_completion(&as->xfer_completion);
+
atmel_get_caps(as);
as->use_dma = false;
@@ -1591,9 +1375,6 @@ static int atmel_spi_probe(struct platform_device *pdev)
ret = devm_request_irq(&pdev->dev, irq, atmel_spi_pdc_interrupt,
0, dev_name(&pdev->dev), master);
} else {
- tasklet_init(&as->tasklet, atmel_spi_tasklet_func,
- (unsigned long)master);
-
ret = devm_request_irq(&pdev->dev, irq, atmel_spi_pio_interrupt,
0, dev_name(&pdev->dev), master);
}
@@ -1637,8 +1418,6 @@ out_free_dma:
out_free_irq:
out_unmap_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:
@@ -1650,12 +1429,9 @@ 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);
@@ -1666,20 +1442,6 @@ static int atmel_spi_remove(struct platform_device *pdev)
spi_readl(as, SR);
spin_unlock_irq(&as->lock);
- /* Terminate remaining queued transfers */
- list_for_each_entry(msg, &as->queue, queue) {
- 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);
--
1.7.9.5
next reply other threads:[~2014-01-09 5:21 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-01-09 5:19 Wenyou Yang [this message]
2014-01-09 5:19 ` [PATCH v2] spi: atmel: Refactor spi-atmel to use SPI framework queue Wenyou Yang
2014-01-09 5:19 ` Wenyou Yang
2014-01-09 17:42 ` Mark Brown
2014-01-09 17:42 ` Mark Brown
2014-01-09 17:42 ` Mark Brown
2016-01-05 19:27 ` Måns Rullgård
2016-01-05 19:27 ` Måns Rullgård
2016-01-05 19:27 ` Måns Rullgård
2016-01-14 3:23 ` Yang, Wenyou
2016-01-14 3:23 ` Yang, Wenyou
2016-01-14 3:23 ` Yang, Wenyou
2016-01-18 1:26 ` Måns Rullgård
2016-01-18 1:26 ` Måns Rullgård
2016-01-18 1:26 ` Måns Rullgård
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1389244755-16188-1-git-send-email-wenyou.yang@atmel.com \
--to=wenyou.yang@atmel.com \
--cc=broonie@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-spi@vger.kernel.org \
--cc=nicolas.ferre@atmel.com \
--cc=richard.genoud@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.