]> err.no Git - linux-2.6/blobdiff - drivers/mmc/host/s3cmci.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/roland...
[linux-2.6] / drivers / mmc / host / s3cmci.c
index c6a4d3cadf2bd1e3731d9ea961728454ec064ef4..be550c26da68d1cc82d2f25ac6880e84c6dae5b3 100644 (file)
@@ -21,6 +21,8 @@
 #include <asm/arch/regs-sdi.h>
 #include <asm/arch/regs-gpio.h>
 
+#include <asm/plat-s3c24xx/mci.h>
+
 #include "s3cmci.h"
 
 #define DRIVER_NAME "s3c-mci"
@@ -335,6 +337,8 @@ static void pio_tasklet(unsigned long data)
        struct s3cmci_host *host = (struct s3cmci_host *) data;
 
 
+       disable_irq(host->irq);
+
        if (host->pio_active == XFER_WRITE)
                do_pio_write(host);
 
@@ -349,12 +353,13 @@ static void pio_tasklet(unsigned long data)
                            (host->pio_active == XFER_READ) ? "read" : "write",
                            host->pio_count, host->pio_words);
 
-                       host->mrq->data->error = -EINVAL;
+                       if (host->mrq->data)
+                               host->mrq->data->error = -EINVAL;
                }
 
-               disable_irq(host->irq);
                finalize_request(host);
-       }
+       } else
+               enable_irq(host->irq);
 }
 
 /*
@@ -445,6 +450,7 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
        }
 
        if (mci_csta & S3C2410_SDICMDSTAT_CMDTIMEOUT) {
+               dbg(host, dbg_err, "CMDSTAT: error CMDTIMEOUT\n");
                cmd->error = -ETIMEDOUT;
                host->status = "error: command timeout";
                goto fail_transfer;
@@ -461,9 +467,19 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
 
        if (mci_csta & S3C2410_SDICMDSTAT_CRCFAIL) {
                if (cmd->flags & MMC_RSP_CRC) {
-                       cmd->error = -EILSEQ;
-                       host->status = "error: bad command crc";
-                       goto fail_transfer;
+                       if (host->mrq->cmd->flags & MMC_RSP_136) {
+                               dbg(host, dbg_irq,
+                                   "fixup: ignore CRC fail with long rsp\n");
+                       } else {
+                               /* note, we used to fail the transfer
+                                * here, but it seems that this is just
+                                * the hardware getting it wrong.
+                                *
+                                * cmd->error = -EILSEQ;
+                                * host->status = "error: bad command crc";
+                                * goto fail_transfer;
+                               */
+                       }
                }
 
                mci_cclear |= S3C2410_SDICMDSTAT_CRCFAIL;
@@ -490,12 +506,14 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
        /* Check for FIFO failure */
        if (host->is2440) {
                if (mci_fsta & S3C2440_SDIFSTA_FIFOFAIL) {
+                       dbg(host, dbg_err, "FIFO failure\n");
                        host->mrq->data->error = -EILSEQ;
                        host->status = "error: 2440 fifo failure";
                        goto fail_transfer;
                }
        } else {
                if (mci_dsta & S3C2410_SDIDSTA_FIFOFAIL) {
+                       dbg(host, dbg_err, "FIFO failure\n");
                        cmd->data->error = -EILSEQ;
                        host->status = "error:  fifo failure";
                        goto fail_transfer;
@@ -503,18 +521,21 @@ static irqreturn_t s3cmci_irq(int irq, void *dev_id)
        }
 
        if (mci_dsta & S3C2410_SDIDSTA_RXCRCFAIL) {
+               dbg(host, dbg_err, "bad data crc (outgoing)\n");
                cmd->data->error = -EILSEQ;
                host->status = "error: bad data crc (outgoing)";
                goto fail_transfer;
        }
 
        if (mci_dsta & S3C2410_SDIDSTA_CRCFAIL) {
+               dbg(host, dbg_err, "bad data crc (incoming)\n");
                cmd->data->error = -EILSEQ;
                host->status = "error: bad data crc (incoming)";
                goto fail_transfer;
        }
 
        if (mci_dsta & S3C2410_SDIDSTA_DATATIMEOUT) {
+               dbg(host, dbg_err, "data timeout\n");
                cmd->data->error = -ETIMEDOUT;
                host->status = "error: data timeout";
                goto fail_transfer;
@@ -569,7 +590,7 @@ static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
 
        dbg(host, dbg_irq, "card detect\n");
 
-       mmc_detect_change(host->mmc, 500);
+       mmc_detect_change(host->mmc, msecs_to_jiffies(500));
 
        return IRQ_HANDLED;
 }
@@ -620,7 +641,6 @@ out:
        spin_unlock_irqrestore(&host->complete_lock, iflags);
        return;
 
-
 fail_request:
        host->mrq->data->error = -EINVAL;
        host->complete_what = COMPLETION_FINALIZE;
@@ -667,7 +687,7 @@ static void finalize_request(struct s3cmci_host *host)
 
        /* Cleanup controller */
        writel(0, host->base + S3C2410_SDICMDARG);
-       writel(0, host->base + S3C2410_SDIDCON);
+       writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
        writel(0, host->base + S3C2410_SDICMDCON);
        writel(0, host->base + host->sdiimsk);
 
@@ -787,13 +807,24 @@ static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data)
                return 0;
        }
 
+       if ((data->blksz & 3) != 0) {
+               /* We cannot deal with unaligned blocks with more than
+                * one block being transfered. */
+
+               if (data->blocks > 1)
+                       return -EINVAL;
+
+               /* No support yet for non-word block transfers. */
+               return -EINVAL;
+       }
+
        while (readl(host->base + S3C2410_SDIDSTA) &
               (S3C2410_SDIDSTA_TXDATAON | S3C2410_SDIDSTA_RXDATAON)) {
 
                dbg(host, dbg_err,
                    "mci_setup_data() transfer stillin progress.\n");
 
-               writel(0, host->base + S3C2410_SDIDCON);
+               writel(S3C2410_SDIDCON_STOP, host->base + S3C2410_SDIDCON);
                s3cmci_reset(host);
 
                if ((stoptries--) == 0) {
@@ -942,8 +973,9 @@ static void s3cmci_send_request(struct mmc_host *mmc)
                host->dcnt++;
 
                if (res) {
-                       cmd->error = -EINVAL;
-                       cmd->data->error = -EINVAL;
+                       dbg(host, dbg_err, "setup data error %d\n", res);
+                       cmd->error = res;
+                       cmd->data->error = res;
 
                        mmc_request_done(mmc, mrq);
                        return;
@@ -955,6 +987,7 @@ static void s3cmci_send_request(struct mmc_host *mmc)
                        res = s3cmci_prepare_pio(host, cmd->data);
 
                if (res) {
+                       dbg(host, dbg_err, "data prepare error %d\n", res);
                        cmd->error = res;
                        cmd->data->error = res;
 
@@ -970,6 +1003,18 @@ static void s3cmci_send_request(struct mmc_host *mmc)
        enable_irq(host->irq);
 }
 
+static int s3cmci_card_present(struct s3cmci_host *host)
+{
+       struct s3c24xx_mci_pdata *pdata = host->pdata;
+       int ret;
+
+       if (pdata->gpio_detect == 0)
+               return -ENOSYS;
+
+       ret = s3c2410_gpio_getpin(pdata->gpio_detect) ? 0 : 1;
+       return ret ^ pdata->detect_invert;
+}
+
 static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
 {
        struct s3cmci_host *host = mmc_priv(mmc);
@@ -978,7 +1023,12 @@ static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
        host->cmd_is_stop = 0;
        host->mrq = mrq;
 
-       s3cmci_send_request(mmc);
+       if (s3cmci_card_present(host) == 0) {
+               dbg(host, dbg_err, "%s: no medium present\n", __func__);
+               host->mrq->cmd->error = -ENOMEDIUM;
+               mmc_request_done(mmc, mrq);
+       } else
+               s3cmci_send_request(mmc);
 }
 
 static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
@@ -1000,6 +1050,9 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                s3c2410_gpio_cfgpin(S3C2410_GPE9, S3C2410_GPE9_SDDAT2);
                s3c2410_gpio_cfgpin(S3C2410_GPE10, S3C2410_GPE10_SDDAT3);
 
+               if (host->pdata->set_power)
+                       host->pdata->set_power(ios->power_mode, ios->vdd);
+
                if (!host->is2440)
                        mci_con |= S3C2410_SDICON_FIFORESET;
 
@@ -1013,6 +1066,9 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
                if (host->is2440)
                        mci_con |= S3C2440_SDICON_SDRESET;
 
+               if (host->pdata->set_power)
+                       host->pdata->set_power(ios->power_mode, ios->vdd);
+
                break;
        }
 
@@ -1061,9 +1117,32 @@ static void s3cmci_reset(struct s3cmci_host *host)
        writel(con, host->base + S3C2410_SDICON);
 }
 
+static int s3cmci_get_ro(struct mmc_host *mmc)
+{
+       struct s3cmci_host *host = mmc_priv(mmc);
+       struct s3c24xx_mci_pdata *pdata = host->pdata;
+       int ret;
+
+       if (pdata->gpio_wprotect == 0)
+               return 0;
+
+       ret = s3c2410_gpio_getpin(pdata->gpio_wprotect);
+
+       if (pdata->wprotect_invert)
+               ret = !ret;
+
+       return ret;
+}
+
 static struct mmc_host_ops s3cmci_ops = {
        .request        = s3cmci_request,
        .set_ios        = s3cmci_set_ios,
+       .get_ro         = s3cmci_get_ro,
+};
+
+static struct s3c24xx_mci_pdata s3cmci_def_pdata = {
+       /* This is currently here to avoid a number of if (host->pdata)
+        * checks. Any zero fields to ensure reaonable defaults are picked. */
 };
 
 static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
@@ -1083,6 +1162,12 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
        host->pdev      = pdev;
        host->is2440    = is2440;
 
+       host->pdata = pdev->dev.platform_data;
+       if (!host->pdata) {
+               pdev->dev.platform_data = &s3cmci_def_pdata;
+               host->pdata = &s3cmci_def_pdata;
+       }
+
        spin_lock_init(&host->complete_lock);
        tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
 
@@ -1101,7 +1186,6 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
        host->pio_active        = XFER_NONE;
 
        host->dma               = S3CMCI_DMA;
-       host->irq_cd            = IRQ_EINT2;
 
        host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!host->mem) {
@@ -1147,17 +1231,27 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
 
        disable_irq(host->irq);
 
-       s3c2410_gpio_cfgpin(S3C2410_GPF2, S3C2410_GPF2_EINT2);
-       set_irq_type(host->irq_cd, IRQT_BOTHEDGE);
+       host->irq_cd = s3c2410_gpio_getirq(host->pdata->gpio_detect);
 
-       if (request_irq(host->irq_cd, s3cmci_irq_cd, 0, DRIVER_NAME, host)) {
-               dev_err(&pdev->dev,
-                       "failed to request card detect interrupt.\n");
-               ret = -ENOENT;
-               goto probe_free_irq;
+       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_irq;
+               }
+       } else {
+               dev_warn(&pdev->dev, "host detect has no irq available\n");
+               s3c2410_gpio_cfgpin(host->pdata->gpio_detect,
+                                   S3C2410_GPIO_INPUT);
        }
 
-       if (s3c2410_dma_request(S3CMCI_DMA, &s3cmci_dma_client, NULL)) {
+       if (host->pdata->gpio_wprotect)
+               s3c2410_gpio_cfgpin(host->pdata->gpio_wprotect,
+                                   S3C2410_GPIO_INPUT);
+
+       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_irq_cd;
@@ -1180,11 +1274,14 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
        host->clk_rate = clk_get_rate(host->clk);
 
        mmc->ops        = &s3cmci_ops;
-       mmc->ocr_avail  = MMC_VDD_32_33;
+       mmc->ocr_avail  = MMC_VDD_32_33 | MMC_VDD_33_34;
        mmc->caps       = MMC_CAP_4_BIT_DATA;
        mmc->f_min      = host->clk_rate / (host->clk_div * 256);
        mmc->f_max      = host->clk_rate / host->clk_div;
 
+       if (host->pdata->ocr_avail)
+               mmc->ocr_avail = host->pdata->ocr_avail;
+
        mmc->max_blk_count      = 4095;
        mmc->max_blk_size       = 4095;
        mmc->max_req_size       = 4095 * 512;
@@ -1216,7 +1313,8 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
        clk_put(host->clk);
 
  probe_free_irq_cd:
-       free_irq(host->irq_cd, host);
+       if (host->irq_cd >= 0)
+               free_irq(host->irq_cd, host);
 
  probe_free_irq:
        free_irq(host->irq, host);
@@ -1233,19 +1331,30 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440)
        return ret;
 }
 
+static void s3cmci_shutdown(struct platform_device *pdev)
+{
+       struct mmc_host *mmc = platform_get_drvdata(pdev);
+       struct s3cmci_host *host = mmc_priv(mmc);
+
+       if (host->irq_cd >= 0)
+               free_irq(host->irq_cd, host);
+
+       mmc_remove_host(mmc);
+       clk_disable(host->clk);
+}
+
 static int __devexit s3cmci_remove(struct platform_device *pdev)
 {
        struct mmc_host         *mmc  = platform_get_drvdata(pdev);
        struct s3cmci_host      *host = mmc_priv(mmc);
 
-       mmc_remove_host(mmc);
+       s3cmci_shutdown(pdev);
 
-       clk_disable(host->clk);
        clk_put(host->clk);
 
        tasklet_disable(&host->pio_tasklet);
+       s3c2410_dma_free(S3CMCI_DMA, &s3cmci_dma_client);
 
-       free_irq(host->irq_cd, host);
        free_irq(host->irq, host);
 
        iounmap(host->base);
@@ -1255,17 +1364,17 @@ static int __devexit s3cmci_remove(struct platform_device *pdev)
        return 0;
 }
 
-static int __devinit s3cmci_probe_2410(struct platform_device *dev)
+static int __devinit s3cmci_2410_probe(struct platform_device *dev)
 {
        return s3cmci_probe(dev, 0);
 }
 
-static int __devinit s3cmci_probe_2412(struct platform_device *dev)
+static int __devinit s3cmci_2412_probe(struct platform_device *dev)
 {
        return s3cmci_probe(dev, 1);
 }
 
-static int __devinit s3cmci_probe_2440(struct platform_device *dev)
+static int __devinit s3cmci_2440_probe(struct platform_device *dev)
 {
        return s3cmci_probe(dev, 1);
 }
@@ -1292,29 +1401,32 @@ static int s3cmci_resume(struct platform_device *dev)
 #endif /* CONFIG_PM */
 
 
-static struct platform_driver s3cmci_driver_2410 = {
+static struct platform_driver s3cmci_2410_driver = {
        .driver.name    = "s3c2410-sdi",
        .driver.owner   = THIS_MODULE,
-       .probe          = s3cmci_probe_2410,
+       .probe          = s3cmci_2410_probe,
        .remove         = __devexit_p(s3cmci_remove),
+       .shutdown       = s3cmci_shutdown,
        .suspend        = s3cmci_suspend,
        .resume         = s3cmci_resume,
 };
 
-static struct platform_driver s3cmci_driver_2412 = {
+static struct platform_driver s3cmci_2412_driver = {
        .driver.name    = "s3c2412-sdi",
        .driver.owner   = THIS_MODULE,
-       .probe          = s3cmci_probe_2412,
+       .probe          = s3cmci_2412_probe,
        .remove         = __devexit_p(s3cmci_remove),
+       .shutdown       = s3cmci_shutdown,
        .suspend        = s3cmci_suspend,
        .resume         = s3cmci_resume,
 };
 
-static struct platform_driver s3cmci_driver_2440 = {
+static struct platform_driver s3cmci_2440_driver = {
        .driver.name    = "s3c2440-sdi",
        .driver.owner   = THIS_MODULE,
-       .probe          = s3cmci_probe_2440,
+       .probe          = s3cmci_2440_probe,
        .remove         = __devexit_p(s3cmci_remove),
+       .shutdown       = s3cmci_shutdown,
        .suspend        = s3cmci_suspend,
        .resume         = s3cmci_resume,
 };
@@ -1322,17 +1434,17 @@ static struct platform_driver s3cmci_driver_2440 = {
 
 static int __init s3cmci_init(void)
 {
-       platform_driver_register(&s3cmci_driver_2410);
-       platform_driver_register(&s3cmci_driver_2412);
-       platform_driver_register(&s3cmci_driver_2440);
+       platform_driver_register(&s3cmci_2410_driver);
+       platform_driver_register(&s3cmci_2412_driver);
+       platform_driver_register(&s3cmci_2440_driver);
        return 0;
 }
 
 static void __exit s3cmci_exit(void)
 {
-       platform_driver_unregister(&s3cmci_driver_2410);
-       platform_driver_unregister(&s3cmci_driver_2412);
-       platform_driver_unregister(&s3cmci_driver_2440);
+       platform_driver_unregister(&s3cmci_2410_driver);
+       platform_driver_unregister(&s3cmci_2412_driver);
+       platform_driver_unregister(&s3cmci_2440_driver);
 }
 
 module_init(s3cmci_init);
@@ -1341,3 +1453,6 @@ module_exit(s3cmci_exit);
 MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver");
 MODULE_LICENSE("GPL v2");
 MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>");
+MODULE_ALIAS("platform:s3c2410-sdi");
+MODULE_ALIAS("platform:s3c2412-sdi");
+MODULE_ALIAS("platform:s3c2440-sdi");