Blackfin SPI Driver: add timeout while waiting for SPIF in dma irq handler
[safe/jmp/linux-2.6] / drivers / spi / spi_bfin5xx.c
index 6635e15..e706de1 100644 (file)
@@ -25,6 +25,7 @@
 #include <asm/dma.h>
 #include <asm/portmux.h>
 #include <asm/bfin5xx_spi.h>
+#include <asm/cacheflush.h>
 
 #define DRV_NAME       "bfin-spi"
 #define DRV_AUTHOR     "Bryan Wu, Luke Yang"
@@ -154,6 +155,9 @@ static u16 hz_to_spi_baud(u32 speed_hz)
        if ((sclk % (2 * speed_hz)) > 0)
                spi_baud++;
 
+       if (spi_baud < MIN_SPI_BAUD_VAL)
+               spi_baud = MIN_SPI_BAUD_VAL;
+
        return spi_baud;
 }
 
@@ -194,8 +198,6 @@ static void cs_deactive(struct driver_data *drv_data, struct chip_data *chip)
                udelay(chip->cs_chg_udelay);
 }
 
-#define MAX_SPI_SSEL   7
-
 /* stop controller and re-config current chip*/
 static void restore_state(struct driver_data *drv_data)
 {
@@ -555,8 +557,14 @@ static irqreturn_t dma_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;
+       unsigned long timeout;
+       unsigned short dmastat = get_dma_curr_irqstat(drv_data->dma_channel);
+       u16 spistat = read_STAT(drv_data);
+
+       dev_dbg(&drv_data->pdev->dev,
+               "in dma_irq_handler dmastat:0x%x spistat:0x%x\n",
+               dmastat, spistat);
 
-       dev_dbg(&drv_data->pdev->dev, "in dma_irq_handler\n");
        clear_dma_irqstat(drv_data->dma_channel);
 
        /* Wait for DMA to complete */
@@ -575,16 +583,30 @@ static irqreturn_t dma_irq_handler(int irq, void *dev_id)
                        cpu_relax();
        }
 
+       dev_dbg(&drv_data->pdev->dev,
+               "in dma_irq_handler dmastat:0x%x spistat:0x%x\n",
+               dmastat, read_STAT(drv_data));
+
+       timeout = jiffies + HZ;
        while (!(read_STAT(drv_data) & SPIF))
-               cpu_relax();
+               if (!time_before(jiffies, timeout)) {
+                       dev_warn(&drv_data->pdev->dev, "timeout waiting for SPIF");
+                       break;
+               } else
+                       cpu_relax();
 
-       msg->actual_length += drv_data->len_in_bytes;
+       if ((dmastat & DMA_ERR) && (spistat & RBSY)) {
+               msg->state = ERROR_STATE;
+               dev_err(&drv_data->pdev->dev, "dma receive: fifo/buffer overflow\n");
+       } else {
+               msg->actual_length += drv_data->len_in_bytes;
 
-       if (drv_data->cs_change)
-               cs_deactive(drv_data, chip);
+               if (drv_data->cs_change)
+                       cs_deactive(drv_data, chip);
 
-       /* Move to next transfer */
-       msg->state = next_transfer(drv_data);
+               /* Move to next transfer */
+               msg->state = next_transfer(drv_data);
+       }
 
        /* Schedule transfer tasklet */
        tasklet_schedule(&drv_data->pump_transfers);
@@ -608,6 +630,7 @@ static void pump_transfers(unsigned long data)
        u8 width;
        u16 cr, dma_width, dma_config;
        u32 tranf_success = 1;
+       u8 full_duplex = 0;
 
        /* Get current state information */
        message = drv_data->cur_msg;
@@ -620,6 +643,7 @@ static void pump_transfers(unsigned long data)
 
         /* Handle for abort */
        if (message->state == ERROR_STATE) {
+               dev_dbg(&drv_data->pdev->dev, "transfer: we've hit an error\n");
                message->status = -EIO;
                giveback(drv_data);
                return;
@@ -627,6 +651,7 @@ static void pump_transfers(unsigned long data)
 
        /* Handle end of message */
        if (message->state == DONE_STATE) {
+               dev_dbg(&drv_data->pdev->dev, "transfer: all done!\n");
                message->status = 0;
                giveback(drv_data);
                return;
@@ -634,6 +659,7 @@ static void pump_transfers(unsigned long data)
 
        /* Delay if requested at end of transfer */
        if (message->state == RUNNING_STATE) {
+               dev_dbg(&drv_data->pdev->dev, "transfer: still running ...\n");
                previous = list_entry(transfer->transfer_list.prev,
                                      struct spi_transfer, transfer_list);
                if (previous->delay_usecs)
@@ -658,6 +684,7 @@ static void pump_transfers(unsigned long data)
        }
 
        if (transfer->rx_buf != NULL) {
+               full_duplex = transfer->tx_buf != NULL;
                drv_data->rx = transfer->rx_buf;
                drv_data->rx_end = drv_data->rx + transfer->len;
                dev_dbg(&drv_data->pdev->dev, "rx_buf is %p, rx_end is %p\n",
@@ -736,24 +763,26 @@ static void pump_transfers(unsigned long data)
                width, transfer->len);
 
        /*
-        * Try to map dma buffer and do a dma transfer if
-        * successful use different way to r/w according to
-        * drv_data->cur_chip->enable_dma
+        * Try to map dma buffer and do a dma transfer.  If successful use,
+        * different way to r/w according to the enable_dma settings and if
+        * we are not doing a full duplex transfer (since the hardware does
+        * not support full duplex DMA transfers).
         */
-       if (drv_data->cur_chip->enable_dma && drv_data->len > 6) {
+       if (!full_duplex && drv_data->cur_chip->enable_dma
+                               && drv_data->len > 6) {
+
+               unsigned long dma_start_addr, flags;
 
                disable_dma(drv_data->dma_channel);
                clear_dma_irqstat(drv_data->dma_channel);
-               bfin_spi_disable(drv_data);
 
                /* config dma channel */
                dev_dbg(&drv_data->pdev->dev, "doing dma transfer\n");
+               set_dma_x_count(drv_data->dma_channel, drv_data->len);
                if (width == CFG_SPI_WORDSIZE16) {
-                       set_dma_x_count(drv_data->dma_channel, drv_data->len);
                        set_dma_x_modify(drv_data->dma_channel, 2);
                        dma_width = WDSIZE_16;
                } else {
-                       set_dma_x_count(drv_data->dma_channel, drv_data->len);
                        set_dma_x_modify(drv_data->dma_channel, 1);
                        dma_width = WDSIZE_8;
                }
@@ -776,8 +805,7 @@ static void pump_transfers(unsigned long data)
                        enable_dma(drv_data->dma_channel);
 
                        /* start SPI transfer */
-                       write_CTRL(drv_data,
-                               (cr | CFG_SPI_DMAWRITE | BIT_CTL_ENABLE));
+                       write_CTRL(drv_data, cr | BIT_CTL_TIMOD_DMA_TX);
 
                        /* just return here, there can only be one transfer
                         * in this mode
@@ -788,47 +816,62 @@ static void pump_transfers(unsigned long data)
                }
 
                /* In dma mode, rx or tx must be NULL in one transfer */
+               dma_config = (RESTART | dma_width | DI_EN);
                if (drv_data->rx != NULL) {
                        /* set transfer mode, and enable SPI */
-                       dev_dbg(&drv_data->pdev->dev, "doing DMA in.\n");
+                       dev_dbg(&drv_data->pdev->dev, "doing DMA in to %p (size %zx)\n",
+                               drv_data->rx, drv_data->len_in_bytes);
+
+                       /* invalidate caches, if needed */
+                       if (bfin_addr_dcachable((unsigned long) drv_data->rx))
+                               invalidate_dcache_range((unsigned long) drv_data->rx,
+                                                       (unsigned long) (drv_data->rx +
+                                                       drv_data->len_in_bytes));
 
                        /* clear tx reg soformer data is not shifted out */
                        write_TDBR(drv_data, 0xFFFF);
 
-                       set_dma_x_count(drv_data->dma_channel, drv_data->len);
-
-                       /* start dma */
-                       dma_enable_irq(drv_data->dma_channel);
-                       dma_config = (WNR | RESTART | dma_width | DI_EN);
-                       set_dma_config(drv_data->dma_channel, dma_config);
-                       set_dma_start_addr(drv_data->dma_channel,
-                                       (unsigned long)drv_data->rx);
-                       enable_dma(drv_data->dma_channel);
-
-                       /* start SPI transfer */
-                       write_CTRL(drv_data,
-                               (cr | CFG_SPI_DMAREAD | BIT_CTL_ENABLE));
+                       dma_config |= WNR;
+                       dma_start_addr = (unsigned long)drv_data->rx;
+                       cr |= BIT_CTL_TIMOD_DMA_RX | BIT_CTL_SENDOPT;
 
                } else if (drv_data->tx != NULL) {
                        dev_dbg(&drv_data->pdev->dev, "doing DMA out.\n");
 
-                       /* start dma */
-                       dma_enable_irq(drv_data->dma_channel);
-                       dma_config = (RESTART | dma_width | DI_EN);
-                       set_dma_config(drv_data->dma_channel, dma_config);
-                       set_dma_start_addr(drv_data->dma_channel,
-                                       (unsigned long)drv_data->tx);
-                       enable_dma(drv_data->dma_channel);
+                       /* flush caches, if needed */
+                       if (bfin_addr_dcachable((unsigned long) drv_data->tx))
+                               flush_dcache_range((unsigned long) drv_data->tx,
+                                               (unsigned long) (drv_data->tx +
+                                               drv_data->len_in_bytes));
+
+                       dma_start_addr = (unsigned long)drv_data->tx;
+                       cr |= BIT_CTL_TIMOD_DMA_TX;
+
+               } else
+                       BUG();
+
+               /* oh man, here there be monsters ... and i dont mean the
+                * fluffy cute ones from pixar, i mean the kind that'll eat
+                * your data, kick your dog, and love it all.  do *not* try
+                * and change these lines unless you (1) heavily test DMA
+                * with SPI flashes on a loaded system (e.g. ping floods),
+                * (2) know just how broken the DMA engine interaction with
+                * the SPI peripheral is, and (3) have someone else to blame
+                * when you screw it all up anyways.
+                */
+               set_dma_start_addr(drv_data->dma_channel, dma_start_addr);
+               set_dma_config(drv_data->dma_channel, dma_config);
+               local_irq_save(flags);
+               enable_dma(drv_data->dma_channel);
+               write_CTRL(drv_data, cr);
+               dma_enable_irq(drv_data->dma_channel);
+               local_irq_restore(flags);
 
-                       /* start SPI transfer */
-                       write_CTRL(drv_data,
-                               (cr | CFG_SPI_DMAWRITE | BIT_CTL_ENABLE));
-               }
        } else {
                /* IO mode write then read */
                dev_dbg(&drv_data->pdev->dev, "doing IO transfer\n");
 
-               if (drv_data->tx != NULL && drv_data->rx != NULL) {
+               if (full_duplex) {
                        /* full duplex mode */
                        BUG_ON((drv_data->tx_end - drv_data->tx) !=
                               (drv_data->rx_end - drv_data->rx));
@@ -873,7 +916,7 @@ static void pump_transfers(unsigned long data)
                        message->state = ERROR_STATE;
                } else {
                        /* Update total byte transfered */
-                       message->actual_length += drv_data->len;
+                       message->actual_length += drv_data->len_in_bytes;
 
                        /* Move to next transfer of this msg */
                        message->state = next_transfer(drv_data);
@@ -972,7 +1015,7 @@ static int transfer(struct spi_device *spi, struct spi_message *msg)
 
 #define MAX_SPI_SSEL   7
 
-static u16 ssel[3][MAX_SPI_SSEL] = {
+static u16 ssel[][MAX_SPI_SSEL] = {
        {P_SPI0_SSEL1, P_SPI0_SSEL2, P_SPI0_SSEL3,
        P_SPI0_SSEL4, P_SPI0_SSEL5,
        P_SPI0_SSEL6, P_SPI0_SSEL7},
@@ -1056,13 +1099,13 @@ static int setup(struct spi_device *spi)
         */
        if (chip->enable_dma && !drv_data->dma_requested) {
                /* register dma irq handler */
-               if (request_dma(drv_data->dma_channel, "BF53x_SPI_DMA") < 0) {
+               if (request_dma(drv_data->dma_channel, "BFIN_SPI_DMA") < 0) {
                        dev_dbg(&spi->dev,
                                "Unable to request BlackFin SPI DMA channel\n");
                        return -ENODEV;
                }
                if (set_dma_callback(drv_data->dma_channel,
-                       (void *)dma_irq_handler, drv_data) < 0) {
+                   dma_irq_handler, drv_data) < 0) {
                        dev_dbg(&spi->dev, "Unable to set dma callback\n");
                        return -EPERM;
                }
@@ -1157,8 +1200,8 @@ static inline int init_queue(struct driver_data *drv_data)
 
        /* init messages workqueue */
        INIT_WORK(&drv_data->pump_messages, pump_messages);
-       drv_data->workqueue =
-           create_singlethread_workqueue(drv_data->master->dev.parent->bus_id);
+       drv_data->workqueue = create_singlethread_workqueue(
+                               dev_name(drv_data->master->dev.parent));
        if (drv_data->workqueue == NULL)
                return -EBUSY;
 
@@ -1396,7 +1439,7 @@ static int bfin5xx_spi_resume(struct platform_device *pdev)
 #define bfin5xx_spi_resume NULL
 #endif                         /* CONFIG_PM */
 
-MODULE_ALIAS("bfin-spi-master");       /* for platform bus hotplug */
+MODULE_ALIAS("platform:bfin-spi");
 static struct platform_driver bfin5xx_spi_driver = {
        .driver = {
                .name   = DRV_NAME,