]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/spi/atmel_spi.c
atmel_spi: support zero length transfer
[linux-2.6-omap-h63xx.git] / drivers / spi / atmel_spi.c
index ff10808183a36f4e9d642469e42653d50f13ca24..02c8e305b14fd783e3b94a71d3c8b4d9c4ed9c68 100644 (file)
@@ -51,7 +51,9 @@ struct atmel_spi {
        u8                      stopping;
        struct list_head        queue;
        struct spi_transfer     *current_transfer;
-       unsigned long           remaining_bytes;
+       unsigned long           current_remaining_bytes;
+       struct spi_transfer     *next_transfer;
+       unsigned long           next_remaining_bytes;
 
        void                    *buffer;
        dma_addr_t              buffer_dma;
@@ -85,6 +87,16 @@ static void cs_activate(struct atmel_spi *as, struct spi_device *spi)
        unsigned gpio = (unsigned) spi->controller_data;
        unsigned active = spi->mode & SPI_CS_HIGH;
        u32 mr;
+       int i;
+       u32 csr;
+       u32 cpol = (spi->mode & SPI_CPOL) ? SPI_BIT(CPOL) : 0;
+
+       /* Make sure clock polarity is correct */
+       for (i = 0; i < spi->master->num_chipselect; i++) {
+               csr = spi_readl(as, CSR0 + 4 * i);
+               if ((csr ^ cpol) & SPI_BIT(CPOL))
+                       spi_writel(as, CSR0 + 4 * i, csr ^ SPI_BIT(CPOL));
+       }
 
        mr = spi_readl(as, MR);
        mr = SPI_BFINS(PCS, ~(1 << spi->chip_select), mr);
@@ -121,6 +133,48 @@ static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi)
                gpio_set_value(gpio, !active);
 }
 
+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 void atmel_spi_next_xfer_data(struct spi_master *master,
+                               struct spi_transfer *xfer,
+                               dma_addr_t *tx_dma,
+                               dma_addr_t *rx_dma,
+                               u32 *plen)
+{
+       struct atmel_spi        *as = spi_master_get_devdata(master);
+       u32                     len = *plen;
+
+       /* use scratch buffer only when rx or tx data is unspecified */
+       if (xfer->rx_buf)
+               *rx_dma = xfer->rx_dma + xfer->len - len;
+       else {
+               *rx_dma = as->buffer_dma;
+               if (len > BUFFER_SIZE)
+                       len = BUFFER_SIZE;
+       }
+       if (xfer->tx_buf)
+               *tx_dma = xfer->tx_dma + xfer->len - len;
+       else {
+               *tx_dma = as->buffer_dma;
+               if (len > BUFFER_SIZE)
+                       len = BUFFER_SIZE;
+               memset(as->buffer, 0, len);
+               dma_sync_single_for_device(&as->pdev->dev,
+                               as->buffer_dma, len, DMA_TO_DEVICE);
+       }
+
+       *plen = len;
+}
+
 /*
  * Submit next transfer for DMA.
  * lock is held, spi irq is blocked
@@ -130,53 +184,78 @@ static void atmel_spi_next_xfer(struct spi_master *master,
 {
        struct atmel_spi        *as = spi_master_get_devdata(master);
        struct spi_transfer     *xfer;
-       u32                     len;
+       u32                     len, remaining, total;
        dma_addr_t              tx_dma, rx_dma;
 
-       xfer = as->current_transfer;
-       if (!xfer || as->remaining_bytes == 0) {
-               if (xfer)
-                       xfer = list_entry(xfer->transfer_list.next,
-                                       struct spi_transfer, transfer_list);
-               else
-                       xfer = list_entry(msg->transfers.next,
-                                       struct spi_transfer, transfer_list);
-               as->remaining_bytes = xfer->len;
-               as->current_transfer = xfer;
-       }
+       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;
 
-       len = as->remaining_bytes;
+       if (xfer) {
+               len = xfer->len;
+               atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+               remaining = xfer->len - len;
 
-       tx_dma = xfer->tx_dma + xfer->len - len;
-       rx_dma = xfer->rx_dma + xfer->len - len;
+               spi_writel(as, RPR, rx_dma);
+               spi_writel(as, TPR, tx_dma);
 
-       /* use scratch buffer only when rx or tx data is unspecified */
-       if (!xfer->rx_buf) {
-               rx_dma = as->buffer_dma;
-               if (len > BUFFER_SIZE)
-                       len = BUFFER_SIZE;
-       }
-       if (!xfer->tx_buf) {
-               tx_dma = as->buffer_dma;
-               if (len > BUFFER_SIZE)
-                       len = BUFFER_SIZE;
-               memset(as->buffer, 0, len);
-               dma_sync_single_for_device(&as->pdev->dev,
-                               as->buffer_dma, len, DMA_TO_DEVICE);
+               if (msg->spi->bits_per_word > 8)
+                       len >>= 1;
+               spi_writel(as, RCR, len);
+               spi_writel(as, TCR, len);
+
+               dev_dbg(&msg->spi->dev,
+                       "  start 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);
+       } else {
+               xfer = as->next_transfer;
+               remaining = as->next_remaining_bytes;
        }
 
-       spi_writel(as, RPR, rx_dma);
-       spi_writel(as, TPR, tx_dma);
+       as->current_transfer = xfer;
+       as->current_remaining_bytes = remaining;
 
-       as->remaining_bytes -= len;
-       if (msg->spi->bits_per_word > 8)
-               len >>= 1;
+       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;
 
-       /* REVISIT: when xfer->delay_usecs == 0, the PDC "next transfer"
-        * mechanism might help avoid the IRQ latency between transfers
-        * (and improve the nCS0 errata handling on at91rm9200 chips)
-        *
-        * We're also waiting for ENDRX before we start the next
+       as->next_transfer = xfer;
+
+       if (xfer) {
+               total = len;
+               atmel_spi_next_xfer_data(master, xfer, &tx_dma, &rx_dma, &len);
+               as->next_remaining_bytes = total - len;
+
+               spi_writel(as, RNPR, rx_dma);
+               spi_writel(as, TNPR, tx_dma);
+
+               if (msg->spi->bits_per_word > 8)
+                       len >>= 1;
+               spi_writel(as, RNCR, len);
+               spi_writel(as, TNCR, len);
+
+               dev_dbg(&msg->spi->dev,
+                       "  next 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);
+       } else {
+               spi_writel(as, RNCR, 0);
+               spi_writel(as, TNCR, 0);
+       }
+
+       /* REVISIT: We're waiting for ENDRX before we start the next
         * transfer because we need to handle some difficult timing
         * issues otherwise. If we wait for ENDTX in one transfer and
         * then starts waiting for ENDRX in the next, it's difficult
@@ -186,17 +265,7 @@ static void atmel_spi_next_xfer(struct spi_master *master,
         *
         * It should be doable, though. Just not now...
         */
-       spi_writel(as, TNCR, 0);
-       spi_writel(as, RNCR, 0);
        spi_writel(as, IER, SPI_BIT(ENDRX) | SPI_BIT(OVRES));
-
-       dev_dbg(&msg->spi->dev,
-               "  start xfer %p: len %u tx %p/%08x rx %p/%08x imr %03x\n",
-               xfer, xfer->len, xfer->tx_buf, xfer->tx_dma,
-               xfer->rx_buf, xfer->rx_dma, spi_readl(as, IMR));
-
-       spi_writel(as, RCR, len);
-       spi_writel(as, TCR, len);
        spi_writel(as, PTCR, SPI_BIT(TXTEN) | SPI_BIT(RXTEN));
 }
 
@@ -294,6 +363,7 @@ atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
        spin_lock(&as->lock);
 
        as->current_transfer = NULL;
+       as->next_transfer = NULL;
 
        /* continue if needed */
        if (list_empty(&as->queue) || as->stopping)
@@ -377,7 +447,7 @@ atmel_spi_interrupt(int irq, void *dev_id)
 
                spi_writel(as, IDR, pending);
 
-               if (as->remaining_bytes == 0) {
+               if (as->current_remaining_bytes == 0) {
                        msg->actual_length += xfer->len;
 
                        if (!msg->is_dma_mapped)
@@ -387,7 +457,7 @@ atmel_spi_interrupt(int irq, void *dev_id)
                        if (xfer->delay_usecs)
                                udelay(xfer->delay_usecs);
 
-                       if (msg->transfers.prev == &xfer->transfer_list) {
+                       if (atmel_spi_xfer_is_last(msg, xfer)) {
                                /* report completed message */
                                atmel_spi_msg_done(master, as, msg, 0,
                                                xfer->cs_change);
@@ -490,9 +560,14 @@ static int atmel_spi_setup(struct spi_device *spi)
        if (!(spi->mode & SPI_CPHA))
                csr |= SPI_BIT(NCPHA);
 
-       /* TODO: DLYBS and DLYBCT */
-       csr |= SPI_BF(DLYBS, 10);
-       csr |= SPI_BF(DLYBCT, 10);
+       /* DLYBS is mostly irrelevant since we manage chipselect using GPIOs.
+        *
+        * DLYBCT would add delays between words, slowing down transfers.
+        * It could potentially be useful to cope with DMA bottlenecks, but
+        * in those cases it's probably best to just use a lower bitrate.
+        */
+       csr |= SPI_BF(DLYBS, 0);
+       csr |= SPI_BF(DLYBCT, 0);
 
        /* chipselect must have been muxed as GPIO (e.g. in board setup) */
        npcs_pin = (unsigned int)spi->controller_data;
@@ -541,7 +616,7 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
                return -ESHUTDOWN;
 
        list_for_each_entry(xfer, &msg->transfers, transfer_list) {
-               if (!(xfer->tx_buf || xfer->rx_buf)) {
+               if (!(xfer->tx_buf || xfer->rx_buf) && xfer->len) {
                        dev_dbg(&spi->dev, "missing rx or tx buf\n");
                        return -EINVAL;
                }
@@ -788,3 +863,4 @@ module_exit(atmel_spi_exit);
 MODULE_DESCRIPTION("Atmel AT32/AT91 SPI Controller driver");
 MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>");
 MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_spi");