spi_s3c24xx: add FIQ pseudo-DMA support
[linux-2.6-block.git] / drivers / spi / spi_s3c24xx.c
index 0be4da0917e0fecaba85dfeeb1d91c9ecc81bd9d..4b0d25b497acf453f8d0b0988248cb1c91321547 100644 (file)
@@ -1,7 +1,7 @@
 /* linux/drivers/spi/spi_s3c24xx.c
  *
  * Copyright (c) 2006 Ben Dooks
- * Copyright (c) 2006 Simtec Electronics
+ * Copyright 2006-2009 Simtec Electronics
  *     Ben Dooks <ben@simtec.co.uk>
  *
  * This program is free software; you can redistribute it and/or modify
 #include <plat/regs-spi.h>
 #include <mach/spi.h>
 
+#include <plat/fiq.h>
+#include <asm/fiq.h>
+
+#include "spi_s3c24xx_fiq.h"
+
+/**
+ * s3c24xx_spi_devstate - per device data
+ * @hz: Last frequency calculated for @sppre field.
+ * @mode: Last mode setting for the @spcon field.
+ * @spcon: Value to write to the SPCON register.
+ * @sppre: Value to write to the SPPRE register.
+ */
+struct s3c24xx_spi_devstate {
+       unsigned int    hz;
+       unsigned int    mode;
+       u8              spcon;
+       u8              sppre;
+};
+
+enum spi_fiq_mode {
+       FIQ_MODE_NONE   = 0,
+       FIQ_MODE_TX     = 1,
+       FIQ_MODE_RX     = 2,
+       FIQ_MODE_TXRX   = 3,
+};
+
 struct s3c24xx_spi {
        /* bitbang has to be first */
        struct spi_bitbang       bitbang;
@@ -38,6 +64,11 @@ struct s3c24xx_spi {
        int                      len;
        int                      count;
 
+       struct fiq_handler       fiq_handler;
+       enum spi_fiq_mode        fiq_mode;
+       unsigned char            fiq_inuse;
+       unsigned char            fiq_claimed;
+
        void                    (*set_cs)(struct s3c2410_spi_info *spi,
                                          int cs, int pol);
 
@@ -53,6 +84,7 @@ struct s3c24xx_spi {
        struct s3c2410_spi_info *pdata;
 };
 
+
 #define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT)
 #define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP)
 
@@ -68,43 +100,31 @@ static void s3c24xx_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol)
 
 static void s3c24xx_spi_chipsel(struct spi_device *spi, int value)
 {
+       struct s3c24xx_spi_devstate *cs = spi->controller_state;
        struct s3c24xx_spi *hw = to_hw(spi);
        unsigned int cspol = spi->mode & SPI_CS_HIGH ? 1 : 0;
-       unsigned int spcon;
+
+       /* change the chipselect state and the state of the spi engine clock */
 
        switch (value) {
        case BITBANG_CS_INACTIVE:
                hw->set_cs(hw->pdata, spi->chip_select, cspol^1);
+               writeb(cs->spcon, hw->regs + S3C2410_SPCON);
                break;
 
        case BITBANG_CS_ACTIVE:
-               spcon = readb(hw->regs + S3C2410_SPCON);
-
-               if (spi->mode & SPI_CPHA)
-                       spcon |= S3C2410_SPCON_CPHA_FMTB;
-               else
-                       spcon &= ~S3C2410_SPCON_CPHA_FMTB;
-
-               if (spi->mode & SPI_CPOL)
-                       spcon |= S3C2410_SPCON_CPOL_HIGH;
-               else
-                       spcon &= ~S3C2410_SPCON_CPOL_HIGH;
-
-               spcon |= S3C2410_SPCON_ENSCK;
-
-               /* write new configration */
-
-               writeb(spcon, hw->regs + S3C2410_SPCON);
+               writeb(cs->spcon | S3C2410_SPCON_ENSCK,
+                      hw->regs + S3C2410_SPCON);
                hw->set_cs(hw->pdata, spi->chip_select, cspol);
-
                break;
        }
 }
 
-static int s3c24xx_spi_setupxfer(struct spi_device *spi,
-                                struct spi_transfer *t)
+static int s3c24xx_spi_update_state(struct spi_device *spi,
+                                   struct spi_transfer *t)
 {
        struct s3c24xx_spi *hw = to_hw(spi);
+       struct s3c24xx_spi_devstate *cs = spi->controller_state;
        unsigned int bpw;
        unsigned int hz;
        unsigned int div;
@@ -124,53 +144,284 @@ static int s3c24xx_spi_setupxfer(struct spi_device *spi,
                return -EINVAL;
        }
 
-       clk = clk_get_rate(hw->clk);
-       div = DIV_ROUND_UP(clk, hz * 2) - 1;
+       if (spi->mode != cs->mode) {
+               u8 spcon = SPCON_DEFAULT | S3C2410_SPCON_ENSCK;
 
-       if (div > 255)
-               div = 255;
+               if (spi->mode & SPI_CPHA)
+                       spcon |= S3C2410_SPCON_CPHA_FMTB;
 
-       dev_dbg(&spi->dev, "setting pre-scaler to %d (wanted %d, got %ld)\n",
-               div, hz, clk / (2 * (div + 1)));
+               if (spi->mode & SPI_CPOL)
+                       spcon |= S3C2410_SPCON_CPOL_HIGH;
 
+               cs->mode = spi->mode;
+               cs->spcon = spcon;
+       }
 
-       writeb(div, hw->regs + S3C2410_SPPRE);
+       if (cs->hz != hz) {
+               clk = clk_get_rate(hw->clk);
+               div = DIV_ROUND_UP(clk, hz * 2) - 1;
 
-       spin_lock(&hw->bitbang.lock);
-       if (!hw->bitbang.busy) {
-               hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
-               /* need to ndelay for 0.5 clocktick ? */
+               if (div > 255)
+                       div = 255;
+
+               dev_dbg(&spi->dev, "pre-scaler=%d (wanted %d, got %ld)\n",
+                       div, hz, clk / (2 * (div + 1)));
+
+               cs->hz = hz;
+               cs->sppre = div;
        }
-       spin_unlock(&hw->bitbang.lock);
 
        return 0;
 }
 
+static int s3c24xx_spi_setupxfer(struct spi_device *spi,
+                                struct spi_transfer *t)
+{
+       struct s3c24xx_spi_devstate *cs = spi->controller_state;
+       struct s3c24xx_spi *hw = to_hw(spi);
+       int ret;
+
+       ret = s3c24xx_spi_update_state(spi, t);
+       if (!ret)
+               writeb(cs->sppre, hw->regs + S3C2410_SPPRE);
+
+       return ret;
+}
+
 static int s3c24xx_spi_setup(struct spi_device *spi)
 {
+       struct s3c24xx_spi_devstate *cs = spi->controller_state;
+       struct s3c24xx_spi *hw = to_hw(spi);
        int ret;
 
-       ret = s3c24xx_spi_setupxfer(spi, NULL);
-       if (ret < 0) {
-               dev_err(&spi->dev, "setupxfer returned %d\n", ret);
+       /* allocate settings on the first call */
+       if (!cs) {
+               cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL);
+               if (!cs) {
+                       dev_err(&spi->dev, "no memory for controller state\n");
+                       return -ENOMEM;
+               }
+
+               cs->spcon = SPCON_DEFAULT;
+               cs->hz = -1;
+               spi->controller_state = cs;
+       }
+
+       /* initialise the state from the device */
+       ret = s3c24xx_spi_update_state(spi, NULL);
+       if (ret)
                return ret;
+
+       spin_lock(&hw->bitbang.lock);
+       if (!hw->bitbang.busy) {
+               hw->bitbang.chipselect(spi, BITBANG_CS_INACTIVE);
+               /* need to ndelay for 0.5 clocktick ? */
        }
+       spin_unlock(&hw->bitbang.lock);
 
        return 0;
 }
 
+static void s3c24xx_spi_cleanup(struct spi_device *spi)
+{
+       kfree(spi->controller_state);
+}
+
 static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count)
 {
        return hw->tx ? hw->tx[count] : 0;
 }
 
+#ifdef CONFIG_SPI_S3C24XX_FIQ
+/* Support for FIQ based pseudo-DMA to improve the transfer speed.
+ *
+ * This code uses the assembly helper in spi_s3c24xx_spi.S which is
+ * used by the FIQ core to move data between main memory and the peripheral
+ * block. Since this is code running on the processor, there is no problem
+ * with cache coherency of the buffers, so we can use any buffer we like.
+ */
+
+/**
+ * struct spi_fiq_code - FIQ code and header
+ * @length: The length of the code fragment, excluding this header.
+ * @ack_offset: The offset from @data to the word to place the IRQ ACK bit at.
+ * @data: The code itself to install as a FIQ handler.
+ */
+struct spi_fiq_code {
+       u32     length;
+       u32     ack_offset;
+       u8      data[0];
+};
+
+extern struct spi_fiq_code s3c24xx_spi_fiq_txrx;
+extern struct spi_fiq_code s3c24xx_spi_fiq_tx;
+extern struct spi_fiq_code s3c24xx_spi_fiq_rx;
+
+/**
+ * ack_bit - turn IRQ into IRQ acknowledgement bit
+ * @irq: The interrupt number
+ *
+ * Returns the bit to write to the interrupt acknowledge register.
+ */
+static inline u32 ack_bit(unsigned int irq)
+{
+       return 1 << (irq - IRQ_EINT0);
+}
+
+/**
+ * s3c24xx_spi_tryfiq - attempt to claim and setup FIQ for transfer
+ * @hw: The hardware state.
+ *
+ * Claim the FIQ handler (only one can be active at any one time) and
+ * then setup the correct transfer code for this transfer.
+ *
+ * This call updates all the necessary state information if sucessful,
+ * so the caller does not need to do anything more than start the transfer
+ * as normal, since the IRQ will have been re-routed to the FIQ handler.
+*/
+void s3c24xx_spi_tryfiq(struct s3c24xx_spi *hw)
+{
+       struct pt_regs regs;
+       enum spi_fiq_mode mode;
+       struct spi_fiq_code *code;
+       int ret;
+
+       if (!hw->fiq_claimed) {
+               /* try and claim fiq if we haven't got it, and if not
+                * then return and simply use another transfer method */
+
+               ret = claim_fiq(&hw->fiq_handler);
+               if (ret)
+                       return;
+       }
+
+       if (hw->tx && !hw->rx)
+               mode = FIQ_MODE_TX;
+       else if (hw->rx && !hw->tx)
+               mode = FIQ_MODE_RX;
+       else
+               mode = FIQ_MODE_TXRX;
+
+       regs.uregs[fiq_rspi] = (long)hw->regs;
+       regs.uregs[fiq_rrx]  = (long)hw->rx;
+       regs.uregs[fiq_rtx]  = (long)hw->tx + 1;
+       regs.uregs[fiq_rcount] = hw->len - 1;
+       regs.uregs[fiq_rirq] = (long)S3C24XX_VA_IRQ;
+
+       set_fiq_regs(&regs);
+
+       if (hw->fiq_mode != mode) {
+               u32 *ack_ptr;
+
+               hw->fiq_mode = mode;
+
+               switch (mode) {
+               case FIQ_MODE_TX:
+                       code = &s3c24xx_spi_fiq_tx;
+                       break;
+               case FIQ_MODE_RX:
+                       code = &s3c24xx_spi_fiq_rx;
+                       break;
+               case FIQ_MODE_TXRX:
+                       code = &s3c24xx_spi_fiq_txrx;
+                       break;
+               default:
+                       code = NULL;
+               }
+
+               BUG_ON(!code);
+
+               ack_ptr = (u32 *)&code->data[code->ack_offset];
+               *ack_ptr = ack_bit(hw->irq);
+
+               set_fiq_handler(&code->data, code->length);
+       }
+
+       s3c24xx_set_fiq(hw->irq, true);
+
+       hw->fiq_mode = mode;
+       hw->fiq_inuse = 1;
+}
+
+/**
+ * s3c24xx_spi_fiqop - FIQ core code callback
+ * @pw: Data registered with the handler
+ * @release: Whether this is a release or a return.
+ *
+ * Called by the FIQ code when another module wants to use the FIQ, so
+ * return whether we are currently using this or not and then update our
+ * internal state.
+ */
+static int s3c24xx_spi_fiqop(void *pw, int release)
+{
+       struct s3c24xx_spi *hw = pw;
+       int ret = 0;
+
+       if (release) {
+               if (hw->fiq_inuse)
+                       ret = -EBUSY;
+
+               /* note, we do not need to unroute the FIQ, as the FIQ
+                * vector code de-routes it to signal the end of transfer */
+
+               hw->fiq_mode = FIQ_MODE_NONE;
+               hw->fiq_claimed = 0;
+       } else {
+               hw->fiq_claimed = 1;
+       }
+
+       return ret;
+}
+
+/**
+ * s3c24xx_spi_initfiq - setup the information for the FIQ core
+ * @hw: The hardware state.
+ *
+ * Setup the fiq_handler block to pass to the FIQ core.
+ */
+static inline void s3c24xx_spi_initfiq(struct s3c24xx_spi *hw)
+{
+       hw->fiq_handler.dev_id = hw;
+       hw->fiq_handler.name = dev_name(hw->dev);
+       hw->fiq_handler.fiq_op = s3c24xx_spi_fiqop;
+}
+
+/**
+ * s3c24xx_spi_usefiq - return if we should be using FIQ.
+ * @hw: The hardware state.
+ *
+ * Return true if the platform data specifies whether this channel is
+ * allowed to use the FIQ.
+ */
+static inline bool s3c24xx_spi_usefiq(struct s3c24xx_spi *hw)
+{
+       return hw->pdata->use_fiq;
+}
+
+/**
+ * s3c24xx_spi_usingfiq - return if channel is using FIQ
+ * @spi: The hardware state.
+ *
+ * Return whether the channel is currently using the FIQ (separate from
+ * whether the FIQ is claimed).
+ */
+static inline bool s3c24xx_spi_usingfiq(struct s3c24xx_spi *spi)
+{
+       return spi->fiq_inuse;
+}
+#else
+
+static inline void s3c24xx_spi_initfiq(struct s3c24xx_spi *s) { }
+static inline void s3c24xx_spi_tryfiq(struct s3c24xx_spi *s) { }
+static inline bool s3c24xx_spi_usefiq(struct s3c24xx_spi *s) { return false; }
+static inline bool s3c24xx_spi_usingfiq(struct s3c24xx_spi *s) { return false; }
+
+#endif /* CONFIG_SPI_S3C24XX_FIQ */
+
 static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 {
        struct s3c24xx_spi *hw = to_hw(spi);
 
-       dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n",
-               t->tx_buf, t->rx_buf, t->len);
-
        hw->tx = t->tx_buf;
        hw->rx = t->rx_buf;
        hw->len = t->len;
@@ -178,11 +429,14 @@ static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
 
        init_completion(&hw->done);
 
+       hw->fiq_inuse = 0;
+       if (s3c24xx_spi_usefiq(hw) && t->len >= 3)
+               s3c24xx_spi_tryfiq(hw);
+
        /* send the first byte */
        writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT);
 
        wait_for_completion(&hw->done);
-
        return hw->count;
 }
 
@@ -204,17 +458,27 @@ static irqreturn_t s3c24xx_spi_irq(int irq, void *dev)
                goto irq_done;
        }
 
-       hw->count++;
+       if (!s3c24xx_spi_usingfiq(hw)) {
+               hw->count++;
 
-       if (hw->rx)
-               hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);
+               if (hw->rx)
+                       hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT);
 
-       count++;
+               count++;
+
+               if (count < hw->len)
+                       writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);
+               else
+                       complete(&hw->done);
+       } else {
+               hw->count = hw->len;
+               hw->fiq_inuse = 0;
+
+               if (hw->rx)
+                       hw->rx[hw->len-1] = readb(hw->regs + S3C2410_SPRDAT);
 
-       if (count < hw->len)
-               writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT);
-       else
                complete(&hw->done);
+       }
 
  irq_done:
        return IRQ_HANDLED;
@@ -272,6 +536,10 @@ static int __init s3c24xx_spi_probe(struct platform_device *pdev)
        platform_set_drvdata(pdev, hw);
        init_completion(&hw->done);
 
+       /* initialise fiq handler */
+
+       s3c24xx_spi_initfiq(hw);
+
        /* setup the master state. */
 
        /* the spi->mode bits understood by this driver: */
@@ -286,7 +554,9 @@ static int __init s3c24xx_spi_probe(struct platform_device *pdev)
        hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
        hw->bitbang.chipselect     = s3c24xx_spi_chipsel;
        hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;
-       hw->bitbang.master->setup  = s3c24xx_spi_setup;
+
+       hw->master->setup  = s3c24xx_spi_setup;
+       hw->master->cleanup = s3c24xx_spi_cleanup;
 
        dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);
 
@@ -299,7 +569,7 @@ static int __init s3c24xx_spi_probe(struct platform_device *pdev)
                goto err_no_iores;
        }
 
-       hw->ioarea = request_mem_region(res->start, (res->end - res->start)+1,
+       hw->ioarea = request_mem_region(res->start, resource_size(res),
                                        pdev->name);
 
        if (hw->ioarea == NULL) {
@@ -308,7 +578,7 @@ static int __init s3c24xx_spi_probe(struct platform_device *pdev)
                goto err_no_iores;
        }
 
-       hw->regs = ioremap(res->start, (res->end - res->start)+1);
+       hw->regs = ioremap(res->start, resource_size(res));
        if (hw->regs == NULL) {
                dev_err(&pdev->dev, "Cannot map IO\n");
                err = -ENXIO;
@@ -418,9 +688,9 @@ static int __exit s3c24xx_spi_remove(struct platform_device *dev)
 
 #ifdef CONFIG_PM
 
-static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg)
+static int s3c24xx_spi_suspend(struct device *dev)
 {
-       struct s3c24xx_spi *hw = platform_get_drvdata(pdev);
+       struct s3c24xx_spi *hw = platform_get_drvdata(to_platform_device(dev));
 
        if (hw->pdata && hw->pdata->gpio_setup)
                hw->pdata->gpio_setup(hw->pdata, 0);
@@ -429,27 +699,31 @@ static int s3c24xx_spi_suspend(struct platform_device *pdev, pm_message_t msg)
        return 0;
 }
 
-static int s3c24xx_spi_resume(struct platform_device *pdev)
+static int s3c24xx_spi_resume(struct device *dev)
 {
-       struct s3c24xx_spi *hw = platform_get_drvdata(pdev);
+       struct s3c24xx_spi *hw = platform_get_drvdata(to_platform_device(dev));
 
        s3c24xx_spi_initialsetup(hw);
        return 0;
 }
 
+static struct dev_pm_ops s3c24xx_spi_pmops = {
+       .suspend        = s3c24xx_spi_suspend,
+       .resume         = s3c24xx_spi_resume,
+};
+
+#define S3C24XX_SPI_PMOPS &s3c24xx_spi_pmops
 #else
-#define s3c24xx_spi_suspend NULL
-#define s3c24xx_spi_resume  NULL
-#endif
+#define S3C24XX_SPI_PMOPS NULL
+#endif /* CONFIG_PM */
 
 MODULE_ALIAS("platform:s3c2410-spi");
 static struct platform_driver s3c24xx_spi_driver = {
        .remove         = __exit_p(s3c24xx_spi_remove),
-       .suspend        = s3c24xx_spi_suspend,
-       .resume         = s3c24xx_spi_resume,
        .driver         = {
                .name   = "s3c2410-spi",
                .owner  = THIS_MODULE,
+               .pm     = S3C24XX_SPI_PMOPS,
        },
 };