spi: spi_imx updates
authorAndrea Paterniani <a.paterniani@swapp-eng.it>
Mon, 28 Apr 2008 09:14:21 +0000 (02:14 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 28 Apr 2008 15:58:31 +0000 (08:58 -0700)
Updates to the i.MX SPI controller driver:

 1) Some comments changed and/or added.

 2) End of transfers is now managed on TXFIFO empty interrupt after the
    last write to TXFIFO.  This speeds interrupt execution by removing
    the wait for TXFIFO to become empty.  On TXFIFO empty interrupt the
    handler needs only to poll for the end of the ongoing transaction
    (SPI_CONTROL_XCH) to close the transfer.
     (2.1) Write only transfers are closed flushing RXFIFO.
     (2.2) Read transfers are closed reading trailing bytes from RXFIFO.
     (2.3) Read transfers where RXFIFO overrun occurred are closed by
           flushing RXFIFO and aborting the message.

 3) Fifos are now flushed via SPI disable after the end of ongoing
    transaction.

Signed-off-by: Andrea Paterniani <a.paterniani@swapp-eng.it>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
drivers/spi/spi_imx.c

index d4ba640366b6c0039682bf24921e7736b76425d8..c730d05bfeb68b8db4cc2f3943e3c199821d1d97 100644 (file)
@@ -270,19 +270,26 @@ struct chip_data {
 
 static void pump_messages(struct work_struct *work);
 
-static int flush(struct driver_data *drv_data)
+static void flush(struct driver_data *drv_data)
 {
-       unsigned long limit = loops_per_jiffy << 1;
        void __iomem *regs = drv_data->regs;
-       volatile u32 d;
+       u32 control;
 
        dev_dbg(&drv_data->pdev->dev, "flush\n");
+
+       /* Wait for end of transaction */
        do {
-               while (readl(regs + SPI_INT_STATUS) & SPI_STATUS_RR)
-                       d = readl(regs + SPI_RXDATA);
-       } while ((readl(regs + SPI_CONTROL) & SPI_CONTROL_XCH) && limit--);
+               control = readl(regs + SPI_CONTROL);
+       } while (control & SPI_CONTROL_XCH);
+
+       /* Release chip select if requested, transfer delays are
+          handled in pump_transfers */
+       if (drv_data->cs_change)
+               drv_data->cs_control(SPI_CS_DEASSERT);
 
-       return limit;
+       /* Disable SPI to flush FIFOs */
+       writel(control & ~SPI_CONTROL_SPIEN, regs + SPI_CONTROL);
+       writel(control, regs + SPI_CONTROL);
 }
 
 static void restore_state(struct driver_data *drv_data)
@@ -570,6 +577,7 @@ static void giveback(struct spi_message *message, struct driver_data *drv_data)
        writel(0, regs + SPI_INT_STATUS);
        writel(0, regs + SPI_DMA);
 
+       /* Unconditioned deselct */
        drv_data->cs_control(SPI_CS_DEASSERT);
 
        message->state = NULL;
@@ -592,13 +600,10 @@ static void dma_err_handler(int channel, void *data, int errcode)
        /* Disable both rx and tx dma channels */
        imx_dma_disable(drv_data->rx_channel);
        imx_dma_disable(drv_data->tx_channel);
-
-       if (flush(drv_data) == 0)
-               dev_err(&drv_data->pdev->dev,
-                               "dma_err_handler - flush failed\n");
-
        unmap_dma_buffers(drv_data);
 
+       flush(drv_data);
+
        msg->state = ERROR_STATE;
        tasklet_schedule(&drv_data->pump_transfers);
 }
@@ -612,8 +617,7 @@ static void dma_tx_handler(int channel, void *data)
        imx_dma_disable(channel);
 
        /* Now waits for TX FIFO empty */
-       writel(readl(drv_data->regs + SPI_INT_STATUS) | SPI_INTEN_TE,
-                       drv_data->regs + SPI_INT_STATUS);
+       writel(SPI_INTEN_TE, drv_data->regs + SPI_INT_STATUS);
 }
 
 static irqreturn_t dma_transfer(struct driver_data *drv_data)
@@ -621,19 +625,18 @@ static irqreturn_t dma_transfer(struct driver_data *drv_data)
        u32 status;
        struct spi_message *msg = drv_data->cur_msg;
        void __iomem *regs = drv_data->regs;
-       unsigned long limit;
 
        status = readl(regs + SPI_INT_STATUS);
 
-       if ((status & SPI_INTEN_RO) && (status & SPI_STATUS_RO)) {
+       if ((status & (SPI_INTEN_RO | SPI_STATUS_RO))
+                       == (SPI_INTEN_RO | SPI_STATUS_RO)) {
                writel(status & ~SPI_INTEN, regs + SPI_INT_STATUS);
 
+               imx_dma_disable(drv_data->tx_channel);
                imx_dma_disable(drv_data->rx_channel);
                unmap_dma_buffers(drv_data);
 
-               if (flush(drv_data) == 0)
-                       dev_err(&drv_data->pdev->dev,
-                               "dma_transfer - flush failed\n");
+               flush(drv_data);
 
                dev_warn(&drv_data->pdev->dev,
                                "dma_transfer - fifo overun\n");
@@ -649,20 +652,17 @@ static irqreturn_t dma_transfer(struct driver_data *drv_data)
 
                if (drv_data->rx) {
                        /* Wait end of transfer before read trailing data */
-                       limit = loops_per_jiffy << 1;
-                       while ((readl(regs + SPI_CONTROL) & SPI_CONTROL_XCH) &&
-                                       limit--);
-
-                       if (limit == 0)
-                               dev_err(&drv_data->pdev->dev,
-                                       "dma_transfer - end of tx failed\n");
-                       else
-                               dev_dbg(&drv_data->pdev->dev,
-                                       "dma_transfer - end of tx\n");
+                       while (readl(regs + SPI_CONTROL) & SPI_CONTROL_XCH)
+                               cpu_relax();
 
                        imx_dma_disable(drv_data->rx_channel);
                        unmap_dma_buffers(drv_data);
 
+                       /* Release chip select if requested, transfer delays are
+                          handled in pump_transfers() */
+                       if (drv_data->cs_change)
+                               drv_data->cs_control(SPI_CS_DEASSERT);
+
                        /* Calculate number of trailing data and read them */
                        dev_dbg(&drv_data->pdev->dev,
                                "dma_transfer - test = 0x%08X\n",
@@ -676,19 +676,12 @@ static irqreturn_t dma_transfer(struct driver_data *drv_data)
                        /* Write only transfer */
                        unmap_dma_buffers(drv_data);
 
-                       if (flush(drv_data) == 0)
-                               dev_err(&drv_data->pdev->dev,
-                                       "dma_transfer - flush failed\n");
+                       flush(drv_data);
                }
 
                /* End of transfer, update total byte transfered */
                msg->actual_length += drv_data->len;
 
-               /* Release chip select if requested, transfer delays are
-                  handled in pump_transfers() */
-               if (drv_data->cs_change)
-                       drv_data->cs_control(SPI_CS_DEASSERT);
-
                /* Move to next transfer */
                msg->state = next_transfer(drv_data);
 
@@ -711,44 +704,43 @@ static irqreturn_t interrupt_wronly_transfer(struct driver_data *drv_data)
 
        status = readl(regs + SPI_INT_STATUS);
 
-       while (status & SPI_STATUS_TH) {
+       if (status & SPI_INTEN_TE) {
+               /* TXFIFO Empty Interrupt on the last transfered word */
+               writel(status & ~SPI_INTEN, regs + SPI_INT_STATUS);
                dev_dbg(&drv_data->pdev->dev,
-                       "interrupt_wronly_transfer - status = 0x%08X\n", status);
+                       "interrupt_wronly_transfer - end of tx\n");
 
-               /* Pump data */
-               if (write(drv_data)) {
-                       writel(readl(regs + SPI_INT_STATUS) & ~SPI_INTEN,
-                               regs + SPI_INT_STATUS);
+               flush(drv_data);
 
-                       dev_dbg(&drv_data->pdev->dev,
-                               "interrupt_wronly_transfer - end of tx\n");
+               /* Update total byte transfered */
+               msg->actual_length += drv_data->len;
 
-                       if (flush(drv_data) == 0)
-                               dev_err(&drv_data->pdev->dev,
-                                       "interrupt_wronly_transfer - "
-                                       "flush failed\n");
+               /* Move to next transfer */
+               msg->state = next_transfer(drv_data);
 
-                       /* End of transfer, update total byte transfered */
-                       msg->actual_length += drv_data->len;
+               /* Schedule transfer tasklet */
+               tasklet_schedule(&drv_data->pump_transfers);
 
-                       /* Release chip select if requested, transfer delays are
-                          handled in pump_transfers */
-                       if (drv_data->cs_change)
-                               drv_data->cs_control(SPI_CS_DEASSERT);
+               return IRQ_HANDLED;
+       } else {
+               while (status & SPI_STATUS_TH) {
+                       dev_dbg(&drv_data->pdev->dev,
+                               "interrupt_wronly_transfer - status = 0x%08X\n",
+                               status);
 
-                       /* Move to next transfer */
-                       msg->state = next_transfer(drv_data);
+                       /* Pump data */
+                       if (write(drv_data)) {
+                               /* End of TXFIFO writes,
+                                  now wait until TXFIFO is empty */
+                               writel(SPI_INTEN_TE, regs + SPI_INT_STATUS);
+                               return IRQ_HANDLED;
+                       }
 
-                       /* Schedule transfer tasklet */
-                       tasklet_schedule(&drv_data->pump_transfers);
+                       status = readl(regs + SPI_INT_STATUS);
 
-                       return IRQ_HANDLED;
+                       /* We did something */
+                       handled = IRQ_HANDLED;
                }
-
-               status = readl(regs + SPI_INT_STATUS);
-
-               /* We did something */
-               handled = IRQ_HANDLED;
        }
 
        return handled;
@@ -758,45 +750,31 @@ static irqreturn_t interrupt_transfer(struct driver_data *drv_data)
 {
        struct spi_message *msg = drv_data->cur_msg;
        void __iomem *regs = drv_data->regs;
-       u32 status;
+       u32 status, control;
        irqreturn_t handled = IRQ_NONE;
        unsigned long limit;
 
        status = readl(regs + SPI_INT_STATUS);
 
-       while (status & (SPI_STATUS_TH | SPI_STATUS_RO)) {
+       if (status & SPI_INTEN_TE) {
+               /* TXFIFO Empty Interrupt on the last transfered word */
+               writel(status & ~SPI_INTEN, regs + SPI_INT_STATUS);
                dev_dbg(&drv_data->pdev->dev,
-                       "interrupt_transfer - status = 0x%08X\n", status);
-
-               if (status & SPI_STATUS_RO) {
-                       writel(readl(regs + SPI_INT_STATUS) & ~SPI_INTEN,
-                               regs + SPI_INT_STATUS);
-
-                       dev_warn(&drv_data->pdev->dev,
-                               "interrupt_transfer - fifo overun\n"
-                               "    data not yet written = %d\n"
-                               "    data not yet read    = %d\n",
-                               data_to_write(drv_data),
-                               data_to_read(drv_data));
-
-                       if (flush(drv_data) == 0)
-                               dev_err(&drv_data->pdev->dev,
-                                       "interrupt_transfer - flush failed\n");
-
-                       msg->state = ERROR_STATE;
-                       tasklet_schedule(&drv_data->pump_transfers);
+                       "interrupt_transfer - end of tx\n");
 
-                       return IRQ_HANDLED;
-               }
-
-               /* Pump data */
-               read(drv_data);
-               if (write(drv_data)) {
-                       writel(readl(regs + SPI_INT_STATUS) & ~SPI_INTEN,
-                               regs + SPI_INT_STATUS);
+               if (msg->state == ERROR_STATE) {
+                       /* RXFIFO overrun was detected and message aborted */
+                       flush(drv_data);
+               } else {
+                       /* Wait for end of transaction */
+                       do {
+                               control = readl(regs + SPI_CONTROL);
+                       } while (control & SPI_CONTROL_XCH);
 
-                       dev_dbg(&drv_data->pdev->dev,
-                               "interrupt_transfer - end of tx\n");
+                       /* Release chip select if requested, transfer delays are
+                          handled in pump_transfers */
+                       if (drv_data->cs_change)
+                               drv_data->cs_control(SPI_CS_DEASSERT);
 
                        /* Read trailing bytes */
                        limit = loops_per_jiffy << 1;
@@ -810,27 +788,54 @@ static irqreturn_t interrupt_transfer(struct driver_data *drv_data)
                                dev_dbg(&drv_data->pdev->dev,
                                        "interrupt_transfer - end of rx\n");
 
-                       /* End of transfer, update total byte transfered */
+                       /* Update total byte transfered */
                        msg->actual_length += drv_data->len;
 
-                       /* Release chip select if requested, transfer delays are
-                          handled in pump_transfers */
-                       if (drv_data->cs_change)
-                               drv_data->cs_control(SPI_CS_DEASSERT);
-
                        /* Move to next transfer */
                        msg->state = next_transfer(drv_data);
+               }
 
-                       /* Schedule transfer tasklet */
-                       tasklet_schedule(&drv_data->pump_transfers);
+               /* Schedule transfer tasklet */
+               tasklet_schedule(&drv_data->pump_transfers);
 
-                       return IRQ_HANDLED;
-               }
+               return IRQ_HANDLED;
+       } else {
+               while (status & (SPI_STATUS_TH | SPI_STATUS_RO)) {
+                       dev_dbg(&drv_data->pdev->dev,
+                               "interrupt_transfer - status = 0x%08X\n",
+                               status);
+
+                       if (status & SPI_STATUS_RO) {
+                               /* RXFIFO overrun, abort message end wait
+                                  until TXFIFO is empty */
+                               writel(SPI_INTEN_TE, regs + SPI_INT_STATUS);
+
+                               dev_warn(&drv_data->pdev->dev,
+                                       "interrupt_transfer - fifo overun\n"
+                                       "    data not yet written = %d\n"
+                                       "    data not yet read    = %d\n",
+                                       data_to_write(drv_data),
+                                       data_to_read(drv_data));
+
+                               msg->state = ERROR_STATE;
+
+                               return IRQ_HANDLED;
+                       }
 
-               status = readl(regs + SPI_INT_STATUS);
+                       /* Pump data */
+                       read(drv_data);
+                       if (write(drv_data)) {
+                               /* End of TXFIFO writes,
+                                  now wait until TXFIFO is empty */
+                               writel(SPI_INTEN_TE, regs + SPI_INT_STATUS);
+                               return IRQ_HANDLED;
+                       }
 
-               /* We did something */
-               handled = IRQ_HANDLED;
+                       status = readl(regs + SPI_INT_STATUS);
+
+                       /* We did something */
+                       handled = IRQ_HANDLED;
+               }
        }
 
        return handled;