spi/bfin_spi: utilize the SPI interrupt in PIO mode
authorYi Li <yi.li@analog.com>
Wed, 3 Jun 2009 09:46:22 +0000 (09:46 +0000)
committerMike Frysinger <vapier@gentoo.org>
Mon, 18 Oct 2010 06:49:29 +0000 (02:49 -0400)
The current behavior in PIO mode is to poll the SPI status registers which
can obviously lead to higher latencies when doing a lot of SPI traffic.
There is a SPI interrupt which can be used instead to signal individual
completion of transactions.

Signed-off-by: Yi Li <yi.li@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
drivers/spi/spi_bfin5xx.c

index b835254..3736c35 100644 (file)
@@ -92,6 +92,9 @@ struct driver_data {
        dma_addr_t rx_dma;
        dma_addr_t tx_dma;
 
+       int irq_requested;
+       int spi_irq;
+
        size_t rx_map_len;
        size_t tx_map_len;
        u8 n_bytes;
@@ -115,6 +118,7 @@ struct chip_data {
        u16 cs_chg_udelay;      /* Some devices require > 255usec delay */
        u32 cs_gpio;
        u16 idle_tx_val;
+       u8 pio_interrupt;       /* use spi data irq */
        void (*write) (struct driver_data *);
        void (*read) (struct driver_data *);
        void (*duplex) (struct driver_data *);
@@ -525,6 +529,79 @@ static void bfin_spi_giveback(struct driver_data *drv_data)
                msg->complete(msg->context);
 }
 
+/* spi data irq handler */
+static irqreturn_t bfin_spi_pio_irq_handler(int irq, void *dev_id)
+{
+       struct driver_data *drv_data = dev_id;
+       struct chip_data *chip = drv_data->cur_chip;
+       struct spi_message *msg = drv_data->cur_msg;
+       int n_bytes = drv_data->n_bytes;
+
+       /* wait until transfer finished. */
+       while (!(read_STAT(drv_data) & BIT_STAT_RXS))
+               cpu_relax();
+
+       if ((drv_data->tx && drv_data->tx >= drv_data->tx_end) ||
+               (drv_data->rx && drv_data->rx >= (drv_data->rx_end - n_bytes))) {
+               /* last read */
+               if (drv_data->rx) {
+                       dev_dbg(&drv_data->pdev->dev, "last read\n");
+                       if (n_bytes == 2)
+                               *(u16 *) (drv_data->rx) = read_RDBR(drv_data);
+                       else if (n_bytes == 1)
+                               *(u8 *) (drv_data->rx) = read_RDBR(drv_data);
+                       drv_data->rx += n_bytes;
+               }
+
+               msg->actual_length += drv_data->len_in_bytes;
+               if (drv_data->cs_change)
+                       bfin_spi_cs_deactive(drv_data, chip);
+               /* Move to next transfer */
+               msg->state = bfin_spi_next_transfer(drv_data);
+
+               disable_irq(drv_data->spi_irq);
+
+               /* Schedule transfer tasklet */
+               tasklet_schedule(&drv_data->pump_transfers);
+               return IRQ_HANDLED;
+       }
+
+       if (drv_data->rx && drv_data->tx) {
+               /* duplex */
+               dev_dbg(&drv_data->pdev->dev, "duplex: write_TDBR\n");
+               if (drv_data->n_bytes == 2) {
+                       *(u16 *) (drv_data->rx) = read_RDBR(drv_data);
+                       write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
+               } else if (drv_data->n_bytes == 1) {
+                       *(u8 *) (drv_data->rx) = read_RDBR(drv_data);
+                       write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
+               }
+       } else if (drv_data->rx) {
+               /* read */
+               dev_dbg(&drv_data->pdev->dev, "read: write_TDBR\n");
+               if (drv_data->n_bytes == 2)
+                       *(u16 *) (drv_data->rx) = read_RDBR(drv_data);
+               else if (drv_data->n_bytes == 1)
+                       *(u8 *) (drv_data->rx) = read_RDBR(drv_data);
+               write_TDBR(drv_data, chip->idle_tx_val);
+       } else if (drv_data->tx) {
+               /* write */
+               dev_dbg(&drv_data->pdev->dev, "write: write_TDBR\n");
+               bfin_spi_dummy_read(drv_data);
+               if (drv_data->n_bytes == 2)
+                       write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
+               else if (drv_data->n_bytes == 1)
+                       write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
+       }
+
+       if (drv_data->tx)
+               drv_data->tx += n_bytes;
+       if (drv_data->rx)
+               drv_data->rx += n_bytes;
+
+       return IRQ_HANDLED;
+}
+
 static irqreturn_t bfin_spi_dma_irq_handler(int irq, void *dev_id)
 {
        struct driver_data *drv_data = dev_id;
@@ -700,6 +777,7 @@ static void bfin_spi_pump_transfers(unsigned long data)
 
        default:
                /* No change, the same as default setting */
+               transfer->bits_per_word = chip->bits_per_word;
                drv_data->n_bytes = chip->n_bytes;
                width = chip->width;
                drv_data->write = drv_data->tx ? chip->write : bfin_spi_null_writer;
@@ -842,60 +920,86 @@ static void bfin_spi_pump_transfers(unsigned long data)
                dma_enable_irq(drv_data->dma_channel);
                local_irq_restore(flags);
 
-       } else {
-               /* IO mode write then read */
-               dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
+               return;
+       }
 
-               /* we always use SPI_WRITE mode. SPI_READ mode
-                  seems to have problems with setting up the
-                  output value in TDBR prior to the transfer. */
+       if (chip->pio_interrupt) {
+               /* use write mode. spi irq should have been disabled */
+               cr = (read_CTRL(drv_data) & (~BIT_CTL_TIMOD));
                write_CTRL(drv_data, (cr | CFG_SPI_WRITE));
 
-               if (full_duplex) {
-                       /* full duplex mode */
-                       BUG_ON((drv_data->tx_end - drv_data->tx) !=
-                              (drv_data->rx_end - drv_data->rx));
-                       dev_dbg(&drv_data->pdev->dev,
-                               "IO duplex: cr is 0x%x\n", cr);
-
-                       drv_data->duplex(drv_data);
-
-                       if (drv_data->tx != drv_data->tx_end)
-                               tranf_success = 0;
-               } else if (drv_data->tx != NULL) {
-                       /* write only half duplex */
-                       dev_dbg(&drv_data->pdev->dev,
-                               "IO write: cr is 0x%x\n", cr);
+               /* discard old RX data and clear RXS */
+               bfin_spi_dummy_read(drv_data);
 
-                       drv_data->write(drv_data);
+               /* start transfer */
+               if (drv_data->tx == NULL)
+                       write_TDBR(drv_data, chip->idle_tx_val);
+               else {
+                       if (transfer->bits_per_word == 8)
+                               write_TDBR(drv_data, (*(u8 *) (drv_data->tx)));
+                       else if (transfer->bits_per_word == 16)
+                               write_TDBR(drv_data, (*(u16 *) (drv_data->tx)));
+                       drv_data->tx += drv_data->n_bytes;
+               }
 
-                       if (drv_data->tx != drv_data->tx_end)
-                               tranf_success = 0;
-               } else if (drv_data->rx != NULL) {
-                       /* read only half duplex */
-                       dev_dbg(&drv_data->pdev->dev,
-                               "IO read: cr is 0x%x\n", cr);
+               /* once TDBR is empty, interrupt is triggered */
+               enable_irq(drv_data->spi_irq);
+               return;
+       }
 
-                       drv_data->read(drv_data);
-                       if (drv_data->rx != drv_data->rx_end)
-                               tranf_success = 0;
-               }
+       /* IO mode */
+       dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
+
+       /* we always use SPI_WRITE mode. SPI_READ mode
+          seems to have problems with setting up the
+          output value in TDBR prior to the transfer. */
+       write_CTRL(drv_data, (cr | CFG_SPI_WRITE));
+
+       if (full_duplex) {
+               /* full duplex mode */
+               BUG_ON((drv_data->tx_end - drv_data->tx) !=
+                      (drv_data->rx_end - drv_data->rx));
+               dev_dbg(&drv_data->pdev->dev,
+                       "IO duplex: cr is 0x%x\n", cr);
+
+               drv_data->duplex(drv_data);
+
+               if (drv_data->tx != drv_data->tx_end)
+                       tranf_success = 0;
+       } else if (drv_data->tx != NULL) {
+               /* write only half duplex */
+               dev_dbg(&drv_data->pdev->dev,
+                       "IO write: cr is 0x%x\n", cr);
+
+               drv_data->write(drv_data);
+
+               if (drv_data->tx != drv_data->tx_end)
+                       tranf_success = 0;
+       } else if (drv_data->rx != NULL) {
+               /* read only half duplex */
+               dev_dbg(&drv_data->pdev->dev,
+                       "IO read: cr is 0x%x\n", cr);
+
+               drv_data->read(drv_data);
+               if (drv_data->rx != drv_data->rx_end)
+                       tranf_success = 0;
+       }
 
-               if (!tranf_success) {
-                       dev_dbg(&drv_data->pdev->dev,
-                               "IO write error!\n");
-                       message->state = ERROR_STATE;
-               } else {
-                       /* Update total byte transfered */
-                       message->actual_length += drv_data->len_in_bytes;
-                       /* Move to next transfer of this msg */
-                       message->state = bfin_spi_next_transfer(drv_data);
-                       if (drv_data->cs_change)
-                               bfin_spi_cs_deactive(drv_data, chip);
-               }
-               /* Schedule next transfer tasklet */
-               tasklet_schedule(&drv_data->pump_transfers);
+       if (!tranf_success) {
+               dev_dbg(&drv_data->pdev->dev,
+                       "IO write error!\n");
+               message->state = ERROR_STATE;
+       } else {
+               /* Update total byte transfered */
+               message->actual_length += drv_data->len_in_bytes;
+               /* Move to next transfer of this msg */
+               message->state = bfin_spi_next_transfer(drv_data);
+               if (drv_data->cs_change)
+                       bfin_spi_cs_deactive(drv_data, chip);
        }
+
+       /* Schedule next transfer tasklet */
+       tasklet_schedule(&drv_data->pump_transfers);
 }
 
 /* pop a msg from queue and kick off real transfer */
@@ -1047,6 +1151,7 @@ static int bfin_spi_setup(struct spi_device *spi)
                chip->cs_chg_udelay = chip_info->cs_chg_udelay;
                chip->cs_gpio = chip_info->cs_gpio;
                chip->idle_tx_val = chip_info->idle_tx_val;
+               chip->pio_interrupt = chip_info->pio_interrupt;
        }
 
        /* translate common spi framework into our register */
@@ -1096,6 +1201,11 @@ static int bfin_spi_setup(struct spi_device *spi)
                goto error;
        }
 
+       if (chip->enable_dma && chip->pio_interrupt) {
+               dev_err(&spi->dev, "enable_dma is set, "
+                               "do not set pio_interrupt\n");
+               goto error;
+       }
        /*
         * if any one SPI chip is registered and wants DMA, request the
         * DMA channel for it
@@ -1119,6 +1229,18 @@ static int bfin_spi_setup(struct spi_device *spi)
                dma_disable_irq(drv_data->dma_channel);
        }
 
+       if (chip->pio_interrupt && !drv_data->irq_requested) {
+               ret = request_irq(drv_data->spi_irq, bfin_spi_pio_irq_handler,
+                       IRQF_DISABLED, "BFIN_SPI", drv_data);
+               if (ret) {
+                       dev_err(&spi->dev, "Unable to register spi IRQ\n");
+                       goto error;
+               }
+               drv_data->irq_requested = 1;
+               /* we use write mode, spi irq has to be disabled here */
+               disable_irq(drv_data->spi_irq);
+       }
+
        if (chip->chip_select_num == 0) {
                ret = gpio_request(chip->cs_gpio, spi->modalias);
                if (ret) {
@@ -1328,11 +1450,19 @@ static int __init bfin_spi_probe(struct platform_device *pdev)
                goto out_error_ioremap;
        }
 
-       drv_data->dma_channel = platform_get_irq(pdev, 0);
-       if (drv_data->dma_channel < 0) {
+       res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+       if (res == NULL) {
                dev_err(dev, "No DMA channel specified\n");
                status = -ENOENT;
-               goto out_error_no_dma_ch;
+               goto out_error_free_io;
+       }
+       drv_data->dma_channel = res->start;
+
+       drv_data->spi_irq = platform_get_irq(pdev, 0);
+       if (drv_data->spi_irq < 0) {
+               dev_err(dev, "No spi pio irq specified\n");
+               status = -ENOENT;
+               goto out_error_free_io;
        }
 
        /* Initial and start queue */
@@ -1375,7 +1505,7 @@ static int __init bfin_spi_probe(struct platform_device *pdev)
 
 out_error_queue_alloc:
        bfin_spi_destroy_queue(drv_data);
-out_error_no_dma_ch:
+out_error_free_io:
        iounmap((void *) drv_data->regs_base);
 out_error_ioremap:
 out_error_get_res:
@@ -1407,6 +1537,11 @@ static int __devexit bfin_spi_remove(struct platform_device *pdev)
                        free_dma(drv_data->dma_channel);
        }
 
+       if (drv_data->irq_requested) {
+               free_irq(drv_data->spi_irq, drv_data);
+               drv_data->irq_requested = 0;
+       }
+
        /* Disconnect from the SPI framework */
        spi_unregister_master(drv_data->master);