]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/mmc/host/s3cmci.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
[mv-sheeva.git] / drivers / mmc / host / s3cmci.c
index 8c2c8b456087c55bf65a2e5e033ca79d126ac58d..99b74a3510203a2e258d83c9f9f4d9aa4f9db816 100644 (file)
@@ -164,6 +164,40 @@ static void dbg_dumpregs(struct s3cmci_host *host, char *prefix) { }
 
 #endif /* CONFIG_MMC_DEBUG */
 
+/**
+ * s3cmci_host_usedma - return whether the host is using dma or pio
+ * @host: The host state
+ *
+ * Return true if the host is using DMA to transfer data, else false
+ * to use PIO mode. Will return static data depending on the driver
+ * configuration.
+ */
+static inline bool s3cmci_host_usedma(struct s3cmci_host *host)
+{
+#ifdef CONFIG_MMC_S3C_PIO
+       return false;
+#elif defined(CONFIG_MMC_S3C_DMA)
+       return true;
+#else
+       return host->dodma;
+#endif
+}
+
+/**
+ * s3cmci_host_canpio - return true if host has pio code available
+ *
+ * Return true if the driver has been compiled with the PIO support code
+ * available.
+ */
+static inline bool s3cmci_host_canpio(void)
+{
+#ifdef CONFIG_MMC_S3C_PIO
+       return true;
+#else
+       return false;
+#endif
+}
+
 static inline u32 enable_imask(struct s3cmci_host *host, u32 imask)
 {
        u32 newmask;
@@ -190,7 +224,33 @@ static inline u32 disable_imask(struct s3cmci_host *host, u32 imask)
 
 static inline void clear_imask(struct s3cmci_host *host)
 {
-       writel(0, host->base + host->sdiimsk);
+       u32 mask = readl(host->base + host->sdiimsk);
+
+       /* preserve the SDIO IRQ mask state */
+       mask &= S3C2410_SDIIMSK_SDIOIRQ;
+       writel(mask, host->base + host->sdiimsk);
+}
+
+/**
+ * s3cmci_check_sdio_irq - test whether the SDIO IRQ is being signalled
+ * @host: The host to check.
+ *
+ * Test to see if the SDIO interrupt is being signalled in case the
+ * controller has failed to re-detect a card interrupt. Read GPE8 and
+ * see if it is low and if so, signal a SDIO interrupt.
+ *
+ * This is currently called if a request is finished (we assume that the
+ * bus is now idle) and when the SDIO IRQ is enabled in case the IRQ is
+ * already being indicated.
+*/
+static void s3cmci_check_sdio_irq(struct s3cmci_host *host)
+{
+       if (host->sdio_irqen) {
+               if (gpio_get_value(S3C2410_GPE(8)) == 0) {
+                       printk(KERN_DEBUG "%s: signalling irq\n", __func__);
+                       mmc_signal_sdio_irq(host->mmc);
+               }
+       }
 }
 
 static inline int get_data_buffer(struct s3cmci_host *host,
@@ -238,6 +298,64 @@ static inline u32 fifo_free(struct s3cmci_host *host)
        return 63 - fifostat;
 }
 
+/**
+ * s3cmci_enable_irq - enable IRQ, after having disabled it.
+ * @host: The device state.
+ * @more: True if more IRQs are expected from transfer.
+ *
+ * Enable the main IRQ if needed after it has been disabled.
+ *
+ * The IRQ can be one of the following states:
+ *     - disabled during IDLE
+ *     - disabled whilst processing data
+ *     - enabled during transfer
+ *     - enabled whilst awaiting SDIO interrupt detection
+ */
+static void s3cmci_enable_irq(struct s3cmci_host *host, bool more)
+{
+       unsigned long flags;
+       bool enable = false;
+
+       local_irq_save(flags);
+
+       host->irq_enabled = more;
+       host->irq_disabled = false;
+
+       enable = more | host->sdio_irqen;
+
+       if (host->irq_state != enable) {
+               host->irq_state = enable;
+
+               if (enable)
+                       enable_irq(host->irq);
+               else
+                       disable_irq(host->irq);
+       }
+
+       local_irq_restore(flags);
+}
+
+/**
+ *
+ */
+static void s3cmci_disable_irq(struct s3cmci_host *host, bool transfer)
+{
+       unsigned long flags;
+
+       local_irq_save(flags);
+
+       //printk(KERN_DEBUG "%s: transfer %d\n", __func__, transfer);
+
+       host->irq_disabled = transfer;
+
+       if (transfer && host->irq_state) {
+               host->irq_state = false;
+               disable_irq(host->irq);
+       }
+
+       local_irq_restore(flags);
+}
+
 static void do_pio_read(struct s3cmci_host *host)
 {
        int res;
@@ -374,8 +492,7 @@ static void pio_tasklet(unsigned long data)
 {
        struct s3cmci_host *host = (struct s3cmci_host *) data;
 
-
-       disable_irq(host->irq);
+       s3cmci_disable_irq(host, true);
 
        if (host->pio_active == XFER_WRITE)
                do_pio_write(host);
@@ -395,9 +512,10 @@ static void pio_tasklet(unsigned long data)
                                host->mrq->data->error = -EINVAL;
                }
 
+               s3cmci_enable_irq(host, false);
                finalize_request(host);
        } else
-               enable_irq(host->irq);
+               s3cmci_enable_irq(host, true);
 }
 
 /*
@@ -432,17 +550,27 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
        struct s3cmci_host *host = dev_id;
        struct mmc_command *cmd;
        u32 mci_csta, mci_dsta, mci_fsta, mci_dcnt, mci_imsk;
-       u32 mci_cclear, mci_dclear;
+       u32 mci_cclear = 0, mci_dclear;
        unsigned long iflags;
 
+       mci_dsta = readl(host->base + S3C2410_SDIDSTA);
+       mci_imsk = readl(host->base + host->sdiimsk);
+
+       if (mci_dsta & S3C2410_SDIDSTA_SDIOIRQDETECT) {
+               if (mci_imsk & S3C2410_SDIIMSK_SDIOIRQ) {
+                       mci_dclear = S3C2410_SDIDSTA_SDIOIRQDETECT;
+                       writel(mci_dclear, host->base + S3C2410_SDIDSTA);
+
+                       mmc_signal_sdio_irq(host->mmc);
+                       return IRQ_HANDLED;
+               }
+       }
+
        spin_lock_irqsave(&host->complete_lock, iflags);
 
        mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
-       mci_dsta = readl(host->base + S3C2410_SDIDSTA);
        mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
        mci_fsta = readl(host->base + S3C2410_SDIFSTA);
-       mci_imsk = readl(host->base + host->sdiimsk);
-       mci_cclear = 0;
        mci_dclear = 0;
 
        if ((host->complete_what == COMPLETION_NONE) ||
@@ -466,7 +594,7 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
                goto irq_out;
        }
 
-       if (!host->dodma) {
+       if (!s3cmci_host_usedma(host)) {
                if ((host->pio_active == XFER_WRITE) &&
                    (mci_fsta & S3C2410_SDIFSTA_TFDET)) {
 
@@ -673,6 +801,7 @@ static void s3cmci_dma_done_callback(struct s3c2410_dma_chan *dma_ch,
        dbg(host, dbg_dma, "DMA FINISHED Size:%i DSTA:%08x DCNT:%08x\n",
                size, mci_dsta, mci_dcnt);
 
+       host->dma_complete = 1;
        host->complete_what = COMPLETION_FINALIZE;
 
 out:
@@ -702,8 +831,9 @@ static void finalize_request(struct s3cmci_host *host)
 
        if (cmd->data && (cmd->error == 0) &&
            (cmd->data->error == 0)) {
-               if (host->dodma && (!host->dma_complete)) {
-                       dbg(host, dbg_dma, "DMA Missing!\n");
+               if (s3cmci_host_usedma(host) && (!host->dma_complete)) {
+                       dbg(host, dbg_dma, "DMA Missing (%d)!\n",
+                           host->dma_complete);
                        return;
                }
        }
@@ -754,7 +884,7 @@ static void finalize_request(struct s3cmci_host *host)
        /* If we had an error while transfering data we flush the
         * DMA channel and the fifo to clear out any garbage. */
        if (mrq->data->error != 0) {
-               if (host->dodma)
+               if (s3cmci_host_usedma(host))
                        s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
 
                if (host->is2440) {
@@ -776,6 +906,8 @@ static void finalize_request(struct s3cmci_host *host)
 request_done:
        host->complete_what = COMPLETION_NONE;
        host->mrq = NULL;
+
+       s3cmci_check_sdio_irq(host);
        mmc_request_done(host->mmc, mrq);
 }
 
@@ -872,7 +1004,7 @@ static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
 
        dcon  = data->blocks & S3C2410_SDIDCON_BLKNUM_MASK;
 
-       if (host->dodma)
+       if (s3cmci_host_usedma(host))
                dcon |= S3C2410_SDIDCON_DMAEN;
 
        if (host->bus_width == MMC_BUS_WIDTH_4)
@@ -950,7 +1082,7 @@ static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data)
 static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
 {
        int dma_len, i;
-       int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
+       int rw = data->flags & MMC_DATA_WRITE;
 
        BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
 
@@ -958,7 +1090,7 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
        s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
 
        dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
-                            (rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+                            rw ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
 
        if (dma_len == 0)
                return -ENOMEM;
@@ -969,11 +1101,11 @@ static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
        for (i = 0; i < dma_len; i++) {
                int res;
 
-               dbg(host, dbg_dma, "enqueue %i:%u@%u\n", i,
-                       sg_dma_address(&data->sg[i]),
-                       sg_dma_len(&data->sg[i]));
+               dbg(host, dbg_dma, "enqueue %i: %08x@%u\n", i,
+                   sg_dma_address(&data->sg[i]),
+                   sg_dma_len(&data->sg[i]));
 
-               res = s3c2410_dma_enqueue(host->dma, (void *) host,
+               res = s3c2410_dma_enqueue(host->dma, host,
                                          sg_dma_address(&data->sg[i]),
                                          sg_dma_len(&data->sg[i]));
 
@@ -1018,7 +1150,7 @@ static void s3cmci_send_request(struct mmc_host *mmc)
                        return;
                }
 
-               if (host->dodma)
+               if (s3cmci_host_usedma(host))
                        res = s3cmci_prepare_dma(host, cmd->data);
                else
                        res = s3cmci_prepare_pio(host, cmd->data);
@@ -1037,7 +1169,7 @@ static void s3cmci_send_request(struct mmc_host *mmc)
        s3cmci_send_command(host, cmd);
 
        /* Enable Interrupt */
-       enable_irq(host->irq);
+       s3cmci_enable_irq(host, true);
 }
 
 static int s3cmci_card_present(struct mmc_host *mmc)
@@ -1167,7 +1299,7 @@ static int s3cmci_get_ro(struct mmc_host *mmc)
        struct s3c24xx_mci_pdata *pdata = host->pdata;
        int ret;
 
-       if (pdata->gpio_wprotect == 0)
+       if (pdata->no_wprotect)
                return 0;
 
        ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);
@@ -1178,11 +1310,52 @@ static int s3cmci_get_ro(struct mmc_host *mmc)
        return ret;
 }
 
+static void s3cmci_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+       struct s3cmci_host *host = mmc_priv(mmc);
+       unsigned long flags;
+       u32 con;
+
+       local_irq_save(flags);
+
+       con = readl(host->base + S3C2410_SDICON);
+       host->sdio_irqen = enable;
+
+       if (enable == host->sdio_irqen)
+               goto same_state;
+
+       if (enable) {
+               con |= S3C2410_SDICON_SDIOIRQ;
+               enable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
+
+               if (!host->irq_state && !host->irq_disabled) {
+                       host->irq_state = true;
+                       enable_irq(host->irq);
+               }
+       } else {
+               disable_imask(host, S3C2410_SDIIMSK_SDIOIRQ);
+               con &= ~S3C2410_SDICON_SDIOIRQ;
+
+               if (!host->irq_enabled && host->irq_state) {
+                       disable_irq_nosync(host->irq);
+                       host->irq_state = false;
+               }
+       }
+
+       writel(con, host->base + S3C2410_SDICON);
+
+ same_state:
+       local_irq_restore(flags);
+
+       s3cmci_check_sdio_irq(host);
+}
+
 static struct mmc_host_ops s3cmci_ops = {
        .request        = s3cmci_request,
        .set_ios        = s3cmci_set_ios,
        .get_ro         = s3cmci_get_ro,
        .get_cd         = s3cmci_card_present,
+       .enable_sdio_irq = s3cmci_enable_sdio_irq,
 };
 
 static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
@@ -1257,8 +1430,11 @@ static int s3cmci_state_show(struct seq_file *seq, void *v)
        seq_printf(seq, "Prescale = %d\n", host->prescaler);
        seq_printf(seq, "is2440 = %d\n", host->is2440);
        seq_printf(seq, "IRQ = %d\n", host->irq);
+       seq_printf(seq, "IRQ enabled = %d\n", host->irq_enabled);
+       seq_printf(seq, "IRQ disabled = %d\n", host->irq_disabled);
+       seq_printf(seq, "IRQ state = %d\n", host->irq_state);
        seq_printf(seq, "CD IRQ = %d\n", host->irq_cd);
-       seq_printf(seq, "Do DMA = %d\n", host->dodma);
+       seq_printf(seq, "Do DMA = %d\n", s3cmci_host_usedma(host));
        seq_printf(seq, "SDIIMSK at %d\n", host->sdiimsk);
        seq_printf(seq, "SDIDATA at %d\n", host->sdidata);
 
@@ -1419,11 +1595,12 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
                host->clk_div   = 2;
        }
 
-       host->dodma             = 0;
        host->complete_what     = COMPLETION_NONE;
        host->pio_active        = XFER_NONE;
 
-       host->dma               = S3CMCI_DMA;
+#ifdef CONFIG_MMC_S3C_PIODMA
+       host->dodma             = host->pdata->dma;
+#endif
 
        host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!host->mem) {
@@ -1468,31 +1645,36 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
         * ensure we don't lock the system with un-serviceable requests. */
 
        disable_irq(host->irq);
+       host->irq_state = false;
 
-       if (host->pdata->gpio_detect) {
+       if (!host->pdata->no_detect) {
                ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
                if (ret) {
                        dev_err(&pdev->dev, "failed to get detect gpio\n");
                        goto probe_free_irq;
                }
-       }
-
-       host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect);
 
-       if (host->irq_cd >= 0) {
-               if (request_irq(host->irq_cd, s3cmci_irq_cd,
-                               IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
-                               DRIVER_NAME, host)) {
-                       dev_err(&pdev->dev, "can't get card detect irq.\n");
-                       ret = -ENOENT;
-                       goto probe_free_gpio_cd;
+               host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect);
+
+               if (host->irq_cd >= 0) {
+                       if (request_irq(host->irq_cd, s3cmci_irq_cd,
+                                       IRQF_TRIGGER_RISING |
+                                       IRQF_TRIGGER_FALLING,
+                                       DRIVER_NAME, host)) {
+                               dev_err(&pdev->dev,
+                                       "can't get card detect irq.\n");
+                               ret = -ENOENT;
+                               goto probe_free_gpio_cd;
+                       }
+               } else {
+                       dev_warn(&pdev->dev,
+                                "host detect has no irq available\n");
+                       gpio_direction_input(host->pdata->gpio_detect);
                }
-       } else {
-               dev_warn(&pdev->dev, "host detect has no irq available\n");
-               gpio_direction_input(host->pdata->gpio_detect);
-       }
+       } else
+               host->irq_cd = -1;
 
-       if (host->pdata->gpio_wprotect) {
+       if (!host->pdata->no_wprotect) {
                ret = gpio_request(host->pdata->gpio_wprotect, "s3cmci wp");
                if (ret) {
                        dev_err(&pdev->dev, "failed to get writeprotect\n");
@@ -1502,10 +1684,21 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
                gpio_direction_input(host->pdata->gpio_wprotect);
        }
 
-       if (s3c2410_dma_request(S3CMCI_DMA, &s3cmci_dma_client, NULL) < 0) {
-               dev_err(&pdev->dev, "unable to get DMA channel.\n");
-               ret = -EBUSY;
-               goto probe_free_gpio_wp;
+       /* depending on the dma state, get a dma channel to use. */
+
+       if (s3cmci_host_usedma(host)) {
+               host->dma = s3c2410_dma_request(DMACH_SDI, &s3cmci_dma_client,
+                                               host);
+               if (host->dma < 0) {
+                       dev_err(&pdev->dev, "cannot get DMA channel.\n");
+                       if (!s3cmci_host_canpio()) {
+                               ret = -EBUSY;
+                               goto probe_free_gpio_wp;
+                       } else {
+                               dev_warn(&pdev->dev, "falling back to PIO.\n");
+                               host->dodma = 0;
+                       }
+               }
        }
 
        host->clk = clk_get(&pdev->dev, "sdi");
@@ -1513,7 +1706,7 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
                dev_err(&pdev->dev, "failed to find clock source.\n");
                ret = PTR_ERR(host->clk);
                host->clk = NULL;
-               goto probe_free_host;
+               goto probe_free_dma;
        }
 
        ret = clk_enable(host->clk);
@@ -1526,7 +1719,11 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
 
        mmc->ops        = &s3cmci_ops;
        mmc->ocr_avail  = MMC_VDD_32_33 | MMC_VDD_33_34;
+#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
+       mmc->caps       = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
+#else
        mmc->caps       = MMC_CAP_4_BIT_DATA;
+#endif
        mmc->f_min      = host->clk_rate / (host->clk_div * 256);
        mmc->f_max      = host->clk_rate / host->clk_div;
 
@@ -1561,7 +1758,9 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
        s3cmci_debugfs_attach(host);
 
        platform_set_drvdata(pdev, mmc);
-       dev_info(&pdev->dev, "initialisation done.\n");
+       dev_info(&pdev->dev, "%s - using %s, %s SDIO IRQ\n", mmc_hostname(mmc),
+                s3cmci_host_usedma(host) ? "dma" : "pio",
+                mmc->caps & MMC_CAP_SDIO_IRQ ? "hw" : "sw");
 
        return 0;
 
@@ -1574,12 +1773,16 @@ static int __devinit s3cmci_probe(struct platform_device *pdev)
  clk_free:
        clk_put(host->clk);
 
+ probe_free_dma:
+       if (s3cmci_host_usedma(host))
+               s3c2410_dma_free(host->dma, &s3cmci_dma_client);
+
  probe_free_gpio_wp:
-       if (host->pdata->gpio_wprotect)
+       if (!host->pdata->no_wprotect)
                gpio_free(host->pdata->gpio_wprotect);
 
  probe_free_gpio_cd:
-       if (host->pdata->gpio_detect)
+       if (!host->pdata->no_detect)
                gpio_free(host->pdata->gpio_detect);
 
  probe_free_irq_cd:
@@ -1632,14 +1835,16 @@ static int __devexit s3cmci_remove(struct platform_device *pdev)
        clk_put(host->clk);
 
        tasklet_disable(&host->pio_tasklet);
-       s3c2410_dma_free(S3CMCI_DMA, &s3cmci_dma_client);
+
+       if (s3cmci_host_usedma(host))
+               s3c2410_dma_free(host->dma, &s3cmci_dma_client);
 
        free_irq(host->irq, host);
 
-       if (pd->gpio_wprotect)
+       if (!pd->no_wprotect)
                gpio_free(pd->gpio_wprotect);
 
-       if (pd->gpio_detect)
+       if (!pd->no_detect)
                gpio_free(pd->gpio_detect);
 
        for (i = S3C2410_GPE(5); i <= S3C2410_GPE(10); i++)