diff options
author | Chris Metcalf <cmetcalf@tilera.com> | 2010-08-13 19:59:15 -0400 |
---|---|---|
committer | Chris Metcalf <cmetcalf@tilera.com> | 2010-08-13 19:59:15 -0400 |
commit | 7d72e6fa56c4100b9669efe0044f77ed9eb785a1 (patch) | |
tree | 5e90bf4969809a1ab20b97432b85be20ccfaa1f4 /drivers/mmc/host | |
parent | ba00376b0b13f234d839541a7b36a5bf5c2a4036 (diff) | |
parent | 2be1f3a73dd02e38e181cf5abacb3d45a6a2d6b8 (diff) |
Merge branch 'master' into for-linus
Diffstat (limited to 'drivers/mmc/host')
-rw-r--r-- | drivers/mmc/host/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mmc/host/Makefile | 5 | ||||
-rw-r--r-- | drivers/mmc/host/mmc_spi.c | 1 | ||||
-rw-r--r-- | drivers/mmc/host/msm_sdcc.c | 27 | ||||
-rw-r--r-- | drivers/mmc/host/msm_sdcc.h | 6 | ||||
-rw-r--r-- | drivers/mmc/host/omap_hsmmc.c | 47 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-cns3xxx.c | 97 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-of-core.c | 12 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-pci.c | 49 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-pltfm.c | 17 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-pltfm.h | 18 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci-s3c.c | 123 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.c | 53 | ||||
-rw-r--r-- | drivers/mmc/host/sdhci.h | 10 |
14 files changed, 416 insertions, 58 deletions
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index d25e22cee4c4..c997474c649f 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -121,6 +121,15 @@ config MMC_SDHCI_PLTFM If unsure, say N. +config MMC_SDHCI_CNS3XXX + bool "SDHCI support on the Cavium Networks CNS3xxx SoC" + depends on ARCH_CNS3XXX + depends on MMC_SDHCI_PLTFM + help + This selects the SDHCI support for CNS3xxx System-on-Chip devices. + + If unsure, say N. + config MMC_SDHCI_S3C tristate "SDHCI support on Samsung S3C SoC" depends on MMC_SDHCI && (PLAT_S3C24XX || PLAT_S3C64XX) diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index f4e53c98d944..fe0ba4e2b8b0 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -12,7 +12,6 @@ obj-$(CONFIG_MMC_IMX) += imxmmc.o obj-$(CONFIG_MMC_MXC) += mxcmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o -obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o obj-$(CONFIG_MMC_SDHCI_SPEAR) += sdhci-spear.o obj-$(CONFIG_MMC_WBSD) += wbsd.o @@ -38,6 +37,10 @@ obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o +obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-platform.o +sdhci-platform-y := sdhci-pltfm.o +sdhci-platform-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o + obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o sdhci-of-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c index 7b0f3ef50f96..1145ea0792e6 100644 --- a/drivers/mmc/host/mmc_spi.c +++ b/drivers/mmc/host/mmc_spi.c @@ -1536,6 +1536,7 @@ static int __devexit mmc_spi_remove(struct spi_device *spi) #if defined(CONFIG_OF) static struct of_device_id mmc_spi_of_match_table[] __devinitdata = { { .compatible = "mmc-spi-slot", }, + {}, }; #endif diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c index 24e09454e522..6824917f5c60 100644 --- a/drivers/mmc/host/msm_sdcc.c +++ b/drivers/mmc/host/msm_sdcc.c @@ -1057,26 +1057,10 @@ msmsdcc_init_dma(struct msmsdcc_host *host) return 0; } -#ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ -static void -do_resume_work(struct work_struct *work) -{ - struct msmsdcc_host *host = - container_of(work, struct msmsdcc_host, resume_task); - struct mmc_host *mmc = host->mmc; - - if (mmc) { - mmc_resume_host(mmc); - if (host->stat_irq) - enable_irq(host->stat_irq); - } -} -#endif - static int msmsdcc_probe(struct platform_device *pdev) { - struct mmc_platform_data *plat = pdev->dev.platform_data; + struct msm_mmc_platform_data *plat = pdev->dev.platform_data; struct msmsdcc_host *host; struct mmc_host *mmc; struct resource *cmd_irqres = NULL; @@ -1145,15 +1129,6 @@ msmsdcc_probe(struct platform_device *pdev) host->dmares = dmares; spin_lock_init(&host->lock); -#ifdef CONFIG_MMC_EMBEDDED_SDIO - if (plat->embedded_sdio) - mmc_set_embedded_sdio_data(mmc, - &plat->embedded_sdio->cis, - &plat->embedded_sdio->cccr, - plat->embedded_sdio->funcs, - plat->embedded_sdio->num_funcs); -#endif - /* * Setup DMA */ diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h index da0039c9285e..ff2b0f74f6f4 100644 --- a/drivers/mmc/host/msm_sdcc.h +++ b/drivers/mmc/host/msm_sdcc.h @@ -225,7 +225,7 @@ struct msmsdcc_host { u32 pwr; u32 saved_irq0mask; /* MMCIMASK0 reg value */ - struct mmc_platform_data *plat; + struct msm_mmc_platform_data *plat; struct timer_list timer; unsigned int oldstat; @@ -235,10 +235,6 @@ struct msmsdcc_host { int cmdpoll; struct msmsdcc_stats stats; -#ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ - struct work_struct resume_task; -#endif - /* Command parameters */ unsigned int cmd_timeout; unsigned int cmd_pio_irqmask; diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index b032828c6126..4a8776f8afdd 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -28,6 +28,7 @@ #include <linux/clk.h> #include <linux/mmc/host.h> #include <linux/mmc/core.h> +#include <linux/mmc/mmc.h> #include <linux/io.h> #include <linux/semaphore.h> #include <linux/gpio.h> @@ -78,6 +79,7 @@ #define INT_EN_MASK 0x307F0033 #define BWR_ENABLE (1 << 4) #define BRR_ENABLE (1 << 5) +#define DTO_ENABLE (1 << 20) #define INIT_STREAM (1 << 1) #define DP_SELECT (1 << 21) #define DDIR (1 << 4) @@ -523,7 +525,8 @@ static void omap_hsmmc_stop_clock(struct omap_hsmmc_host *host) dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n"); } -static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host) +static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host, + struct mmc_command *cmd) { unsigned int irq_mask; @@ -532,6 +535,10 @@ static void omap_hsmmc_enable_irq(struct omap_hsmmc_host *host) else irq_mask = INT_EN_MASK; + /* Disable timeout for erases */ + if (cmd->opcode == MMC_ERASE) + irq_mask &= ~DTO_ENABLE; + OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR); OMAP_HSMMC_WRITE(host->base, ISE, irq_mask); OMAP_HSMMC_WRITE(host->base, IE, irq_mask); @@ -782,7 +789,7 @@ omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd, mmc_hostname(host->mmc), cmd->opcode, cmd->arg); host->cmd = cmd; - omap_hsmmc_enable_irq(host); + omap_hsmmc_enable_irq(host, cmd); host->response_busy = 0; if (cmd->flags & MMC_RSP_PRESENT) { @@ -1273,8 +1280,11 @@ static void omap_hsmmc_dma_cb(int lch, u16 ch_status, void *cb_data) struct mmc_data *data = host->mrq->data; int dma_ch, req_in_progress; - if (ch_status & OMAP2_DMA_MISALIGNED_ERR_IRQ) - dev_dbg(mmc_dev(host->mmc), "MISALIGNED_ADRS_ERR\n"); + if (!(ch_status & OMAP_DMA_BLOCK_IRQ)) { + dev_warn(mmc_dev(host->mmc), "unexpected dma status %x\n", + ch_status); + return; + } spin_lock(&host->irq_lock); if (host->dma_ch < 0) { @@ -1598,6 +1608,14 @@ static int omap_hsmmc_get_ro(struct mmc_host *mmc) return mmc_slot(host).get_ro(host->dev, 0); } +static void omap_hsmmc_init_card(struct mmc_host *mmc, struct mmc_card *card) +{ + struct omap_hsmmc_host *host = mmc_priv(mmc); + + if (mmc_slot(host).init_card) + mmc_slot(host).init_card(card); +} + static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host) { u32 hctl, capa, value; @@ -1869,6 +1887,7 @@ static const struct mmc_host_ops omap_hsmmc_ops = { .set_ios = omap_hsmmc_set_ios, .get_cd = omap_hsmmc_get_cd, .get_ro = omap_hsmmc_get_ro, + .init_card = omap_hsmmc_init_card, /* NYET -- enable_sdio_irq */ }; @@ -1879,6 +1898,7 @@ static const struct mmc_host_ops omap_hsmmc_ps_ops = { .set_ios = omap_hsmmc_set_ios, .get_cd = omap_hsmmc_get_cd, .get_ro = omap_hsmmc_get_ro, + .init_card = omap_hsmmc_init_card, /* NYET -- enable_sdio_irq */ }; @@ -2094,12 +2114,25 @@ static int __init omap_hsmmc_probe(struct platform_device *pdev) mmc->max_seg_size = mmc->max_req_size; mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED | - MMC_CAP_WAIT_WHILE_BUSY; + MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_ERASE; - if (mmc_slot(host).wires >= 8) + switch (mmc_slot(host).wires) { + case 8: mmc->caps |= MMC_CAP_8_BIT_DATA; - else if (mmc_slot(host).wires >= 4) + /* Fall through */ + case 4: mmc->caps |= MMC_CAP_4_BIT_DATA; + break; + case 1: + /* Nothing to crib here */ + case 0: + /* Assuming nothing was given by board, Core use's 1-Bit */ + break; + default: + /* Completely unexpected.. Core goes with 1-Bit Width */ + dev_crit(mmc_dev(host->mmc), "Invalid width %d\n used!" + "using 1 instead\n", mmc_slot(host).wires); + } if (mmc_slot(host).nonremovable) mmc->caps |= MMC_CAP_NONREMOVABLE; diff --git a/drivers/mmc/host/sdhci-cns3xxx.c b/drivers/mmc/host/sdhci-cns3xxx.c new file mode 100644 index 000000000000..b7050b380d5f --- /dev/null +++ b/drivers/mmc/host/sdhci-cns3xxx.c @@ -0,0 +1,97 @@ +/* + * SDHCI support for CNS3xxx SoC + * + * Copyright 2008 Cavium Networks + * Copyright 2010 MontaVista Software, LLC. + * + * Authors: Scott Shu + * Anton Vorontsov <avorontsov@mvista.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/mmc/host.h> +#include <linux/sdhci-pltfm.h> +#include <mach/cns3xxx.h> +#include "sdhci.h" +#include "sdhci-pltfm.h" + +static unsigned int sdhci_cns3xxx_get_max_clk(struct sdhci_host *host) +{ + return 150000000; +} + +static void sdhci_cns3xxx_set_clock(struct sdhci_host *host, unsigned int clock) +{ + struct device *dev = mmc_dev(host->mmc); + int div = 1; + u16 clk; + unsigned long timeout; + + if (clock == host->clock) + return; + + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + + if (clock == 0) + goto out; + + while (host->max_clk / div > clock) { + /* + * On CNS3xxx divider grows linearly up to 4, and then + * exponentially up to 256. + */ + if (div < 4) + div += 1; + else if (div < 256) + div *= 2; + else + break; + } + + dev_dbg(dev, "desired SD clock: %d, actual: %d\n", + clock, host->max_clk / div); + + /* Divide by 3 is special. */ + if (div != 3) + div >>= 1; + + clk = div << SDHCI_DIVIDER_SHIFT; + clk |= SDHCI_CLOCK_INT_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + timeout = 20; + while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + dev_warn(dev, "clock is unstable"); + break; + } + timeout--; + mdelay(1); + } + + clk |= SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); +out: + host->clock = clock; +} + +static struct sdhci_ops sdhci_cns3xxx_ops = { + .get_max_clock = sdhci_cns3xxx_get_max_clk, + .set_clock = sdhci_cns3xxx_set_clock, +}; + +struct sdhci_pltfm_data sdhci_cns3xxx_pdata = { + .ops = &sdhci_cns3xxx_ops, + .quirks = SDHCI_QUIRK_BROKEN_DMA | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_INVERTED_WRITE_PROTECT | + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN | + SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | + SDHCI_QUIRK_NONSTANDARD_CLOCK, +}; diff --git a/drivers/mmc/host/sdhci-of-core.c b/drivers/mmc/host/sdhci-of-core.c index a2e9820cd42f..c51b71174c1d 100644 --- a/drivers/mmc/host/sdhci-of-core.c +++ b/drivers/mmc/host/sdhci-of-core.c @@ -85,14 +85,14 @@ void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg) #ifdef CONFIG_PM -static int sdhci_of_suspend(struct of_device *ofdev, pm_message_t state) +static int sdhci_of_suspend(struct platform_device *ofdev, pm_message_t state) { struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); return mmc_suspend_host(host->mmc); } -static int sdhci_of_resume(struct of_device *ofdev) +static int sdhci_of_resume(struct platform_device *ofdev) { struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); @@ -115,7 +115,7 @@ static bool __devinit sdhci_of_wp_inverted(struct device_node *np) return machine_is(mpc837x_rdb) || machine_is(mpc837x_mds); } -static int __devinit sdhci_of_probe(struct of_device *ofdev, +static int __devinit sdhci_of_probe(struct platform_device *ofdev, const struct of_device_id *match) { struct device_node *np = ofdev->dev.of_node; @@ -154,6 +154,10 @@ static int __devinit sdhci_of_probe(struct of_device *ofdev, host->ops = &sdhci_of_data->ops; } + if (of_get_property(np, "sdhci,auto-cmd12", NULL)) + host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12; + + if (of_get_property(np, "sdhci,1-bit-only", NULL)) host->quirks |= SDHCI_QUIRK_FORCE_1_BIT_DATA; @@ -179,7 +183,7 @@ err_addr_map: return ret; } -static int __devexit sdhci_of_remove(struct of_device *ofdev) +static int __devexit sdhci_of_remove(struct platform_device *ofdev) { struct sdhci_host *host = dev_get_drvdata(&ofdev->dev); diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 65483fdea45b..e8aa99deae9a 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -17,6 +17,7 @@ #include <linux/pci.h> #include <linux/dma-mapping.h> #include <linux/slab.h> +#include <linux/device.h> #include <linux/mmc/host.h> @@ -84,7 +85,30 @@ static int ricoh_probe(struct sdhci_pci_chip *chip) if (chip->pdev->subsystem_vendor == PCI_VENDOR_ID_SAMSUNG || chip->pdev->subsystem_vendor == PCI_VENDOR_ID_SONY) chip->quirks |= SDHCI_QUIRK_NO_CARD_NO_RESET; + return 0; +} + +static int ricoh_mmc_probe_slot(struct sdhci_pci_slot *slot) +{ + slot->host->caps = + ((0x21 << SDHCI_TIMEOUT_CLK_SHIFT) + & SDHCI_TIMEOUT_CLK_MASK) | + + ((0x21 << SDHCI_CLOCK_BASE_SHIFT) + & SDHCI_CLOCK_BASE_MASK) | + SDHCI_TIMEOUT_CLK_UNIT | + SDHCI_CAN_VDD_330 | + SDHCI_CAN_DO_SDMA; + return 0; +} + +static int ricoh_mmc_resume(struct sdhci_pci_chip *chip) +{ + /* Apply a delay to allow controller to settle */ + /* Otherwise it becomes confused if card state changed + during suspend */ + msleep(500); return 0; } @@ -95,6 +119,15 @@ static const struct sdhci_pci_fixes sdhci_ricoh = { SDHCI_QUIRK_CLOCK_BEFORE_RESET, }; +static const struct sdhci_pci_fixes sdhci_ricoh_mmc = { + .probe_slot = ricoh_mmc_probe_slot, + .resume = ricoh_mmc_resume, + .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_CLOCK_BEFORE_RESET | + SDHCI_QUIRK_NO_CARD_NO_RESET | + SDHCI_QUIRK_MISSING_CAPS +}; + static const struct sdhci_pci_fixes sdhci_ene_712 = { .quirks = SDHCI_QUIRK_SINGLE_POWER_WRITE | SDHCI_QUIRK_BROKEN_DMA, @@ -374,6 +407,22 @@ static const struct pci_device_id pci_ids[] __devinitdata = { }, { + .vendor = PCI_VENDOR_ID_RICOH, + .device = 0x843, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_ricoh_mmc, + }, + + { + .vendor = PCI_VENDOR_ID_RICOH, + .device = 0xe822, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .driver_data = (kernel_ulong_t)&sdhci_ricoh_mmc, + }, + + { .vendor = PCI_VENDOR_ID_ENE, .device = PCI_DEVICE_ID_ENE_CB712_SD, .subvendor = PCI_ANY_ID, diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index b6ee0d719698..e045e3c61dde 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -24,6 +24,7 @@ #include <linux/delay.h> #include <linux/highmem.h> +#include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/mmc/host.h> @@ -32,6 +33,7 @@ #include <linux/sdhci-pltfm.h> #include "sdhci.h" +#include "sdhci-pltfm.h" /*****************************************************************************\ * * @@ -51,10 +53,14 @@ static struct sdhci_ops sdhci_pltfm_ops = { static int __devinit sdhci_pltfm_probe(struct platform_device *pdev) { struct sdhci_pltfm_data *pdata = pdev->dev.platform_data; + const struct platform_device_id *platid = platform_get_device_id(pdev); struct sdhci_host *host; struct resource *iomem; int ret; + if (!pdata && platid && platid->driver_data) + pdata = (void *)platid->driver_data; + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!iomem) { ret = -ENOMEM; @@ -150,6 +156,15 @@ static int __devexit sdhci_pltfm_remove(struct platform_device *pdev) return 0; } +static const struct platform_device_id sdhci_pltfm_ids[] = { + { "sdhci", }, +#ifdef CONFIG_MMC_SDHCI_CNS3XXX + { "sdhci-cns3xxx", (kernel_ulong_t)&sdhci_cns3xxx_pdata }, +#endif + { }, +}; +MODULE_DEVICE_TABLE(platform, sdhci_pltfm_ids); + static struct platform_driver sdhci_pltfm_driver = { .driver = { .name = "sdhci", @@ -157,6 +172,7 @@ static struct platform_driver sdhci_pltfm_driver = { }, .probe = sdhci_pltfm_probe, .remove = __devexit_p(sdhci_pltfm_remove), + .id_table = sdhci_pltfm_ids, }; /*****************************************************************************\ @@ -181,4 +197,3 @@ module_exit(sdhci_drv_exit); MODULE_DESCRIPTION("Secure Digital Host Controller Interface platform driver"); MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>"); MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:sdhci"); diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h new file mode 100644 index 000000000000..900f32902f73 --- /dev/null +++ b/drivers/mmc/host/sdhci-pltfm.h @@ -0,0 +1,18 @@ +/* + * Copyright 2010 MontaVista Software, LLC. + * + * Author: Anton Vorontsov <avorontsov@ru.mvista.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _DRIVERS_MMC_SDHCI_PLTFM_H +#define _DRIVERS_MMC_SDHCI_PLTFM_H + +#include <linux/sdhci-pltfm.h> + +extern struct sdhci_pltfm_data sdhci_cns3xxx_pdata; + +#endif /* _DRIVERS_MMC_SDHCI_PLTFM_H */ diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index ad30f074ee15..0a7f2614c6f0 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -18,6 +18,7 @@ #include <linux/slab.h> #include <linux/clk.h> #include <linux/io.h> +#include <linux/gpio.h> #include <linux/mmc/host.h> @@ -44,6 +45,8 @@ struct sdhci_s3c { struct resource *ioarea; struct s3c_sdhci_platdata *pdata; unsigned int cur_clk; + int ext_cd_irq; + int ext_cd_gpio; struct clk *clk_io; struct clk *clk_bus[MAX_BUS_CLK]; @@ -110,11 +113,6 @@ static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) return max; } -static unsigned int sdhci_s3c_get_timeout_clk(struct sdhci_host *host) -{ - return sdhci_s3c_get_max_clk(host) / 1000000; -} - /** * sdhci_s3c_consider_clock - consider one the bus clocks for current setting * @ourhost: Our SDHCI instance. @@ -188,7 +186,6 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) ourhost->cur_clk = best_src; host->max_clk = clk_get_rate(clk); - host->timeout_clk = sdhci_s3c_get_timeout_clk(host); ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; @@ -209,12 +206,93 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) } } +/** + * sdhci_s3c_get_min_clock - callback to get minimal supported clock value + * @host: The SDHCI host being queried + * + * To init mmc host properly a minimal clock value is needed. For high system + * bus clock's values the standard formula gives values out of allowed range. + * The clock still can be set to lower values, if clock source other then + * system bus is selected. +*/ +static unsigned int sdhci_s3c_get_min_clock(struct sdhci_host *host) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + unsigned int delta, min = UINT_MAX; + int src; + + for (src = 0; src < MAX_BUS_CLK; src++) { + delta = sdhci_s3c_consider_clock(ourhost, src, 0); + if (delta == UINT_MAX) + continue; + /* delta is a negative value in this case */ + if (-delta < min) + min = -delta; + } + return min; +} + static struct sdhci_ops sdhci_s3c_ops = { .get_max_clock = sdhci_s3c_get_max_clk, - .get_timeout_clock = sdhci_s3c_get_timeout_clk, .set_clock = sdhci_s3c_set_clock, + .get_min_clock = sdhci_s3c_get_min_clock, }; +static void sdhci_s3c_notify_change(struct platform_device *dev, int state) +{ + struct sdhci_host *host = platform_get_drvdata(dev); + if (host) { + mutex_lock(&host->lock); + if (state) { + dev_dbg(&dev->dev, "card inserted.\n"); + host->flags &= ~SDHCI_DEVICE_DEAD; + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + } else { + dev_dbg(&dev->dev, "card removed.\n"); + host->flags |= SDHCI_DEVICE_DEAD; + host->quirks &= ~SDHCI_QUIRK_BROKEN_CARD_DETECTION; + } + sdhci_card_detect(host); + mutex_unlock(&host->lock); + } +} + +static irqreturn_t sdhci_s3c_gpio_card_detect_thread(int irq, void *dev_id) +{ + struct sdhci_s3c *sc = dev_id; + int status = gpio_get_value(sc->ext_cd_gpio); + if (sc->pdata->ext_cd_gpio_invert) + status = !status; + sdhci_s3c_notify_change(sc->pdev, status); + return IRQ_HANDLED; +} + +static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc) +{ + struct s3c_sdhci_platdata *pdata = sc->pdata; + struct device *dev = &sc->pdev->dev; + + if (gpio_request(pdata->ext_cd_gpio, "SDHCI EXT CD") == 0) { + sc->ext_cd_gpio = pdata->ext_cd_gpio; + sc->ext_cd_irq = gpio_to_irq(pdata->ext_cd_gpio); + if (sc->ext_cd_irq && + request_threaded_irq(sc->ext_cd_irq, NULL, + sdhci_s3c_gpio_card_detect_thread, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(dev), sc) == 0) { + int status = gpio_get_value(sc->ext_cd_gpio); + if (pdata->ext_cd_gpio_invert) + status = !status; + sdhci_s3c_notify_change(sc->pdev, status); + } else { + dev_warn(dev, "cannot request irq for card detect\n"); + sc->ext_cd_irq = 0; + } + } else { + dev_err(dev, "cannot request gpio for card detect\n"); + } +} + static int __devinit sdhci_s3c_probe(struct platform_device *pdev) { struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; @@ -252,6 +330,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) sc->host = host; sc->pdev = pdev; sc->pdata = pdata; + sc->ext_cd_gpio = -1; /* invalid gpio number */ platform_set_drvdata(pdev, host); @@ -318,6 +397,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) /* Setup quirks for the controller */ host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; + host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT; #ifndef CONFIG_MMC_SDHCI_S3C_DMA @@ -332,15 +412,34 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) * SDHCI block, or a missing configuration that needs to be set. */ host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ; + if (pdata->cd_type == S3C_SDHCI_CD_NONE || + pdata->cd_type == S3C_SDHCI_CD_PERMANENT) + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + + if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT) + host->mmc->caps = MMC_CAP_NONREMOVABLE; + host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | SDHCI_QUIRK_32BIT_DMA_SIZE); + /* HSMMC on Samsung SoCs uses SDCLK as timeout clock */ + host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK; + ret = sdhci_add_host(host); if (ret) { dev_err(dev, "sdhci_add_host() failed\n"); goto err_add_host; } + /* The following two methods of card detection might call + sdhci_s3c_notify_change() immediately, so they can be called + only after sdhci_add_host(). Setup errors are ignored. */ + if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_init) + pdata->ext_cd_init(&sdhci_s3c_notify_change); + if (pdata->cd_type == S3C_SDHCI_CD_GPIO && + gpio_is_valid(pdata->ext_cd_gpio)) + sdhci_s3c_setup_card_detect_gpio(sc); + return 0; err_add_host: @@ -365,10 +464,20 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) static int __devexit sdhci_s3c_remove(struct platform_device *pdev) { + struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_s3c *sc = sdhci_priv(host); int ptr; + if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup) + pdata->ext_cd_cleanup(&sdhci_s3c_notify_change); + + if (sc->ext_cd_irq) + free_irq(sc->ext_cd_irq, sc); + + if (gpio_is_valid(sc->ext_cd_gpio)) + gpio_free(sc->ext_cd_gpio); + sdhci_remove_host(host, 1); for (ptr = 0; ptr < 3; ptr++) { diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index c6d1bd8d4ac4..785512133b50 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -19,6 +19,7 @@ #include <linux/dma-mapping.h> #include <linux/slab.h> #include <linux/scatterlist.h> +#include <linux/regulator/consumer.h> #include <linux/leds.h> @@ -817,8 +818,12 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host, WARN_ON(!host->data); mode = SDHCI_TRNS_BLK_CNT_EN; - if (data->blocks > 1) - mode |= SDHCI_TRNS_MULTI; + if (data->blocks > 1) { + if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12) + mode |= SDHCI_TRNS_MULTI | SDHCI_TRNS_ACMD12; + else + mode |= SDHCI_TRNS_MULTI; + } if (data->flags & MMC_DATA_READ) mode |= SDHCI_TRNS_READ; if (host->flags & SDHCI_REQ_USE_DMA) @@ -1108,6 +1113,12 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) #ifndef SDHCI_USE_LEDS_CLASS sdhci_activate_led(host); #endif + if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12) { + if (mrq->stop) { + mrq->data->stop = NULL; + mrq->stop = NULL; + } + } host->mrq = mrq; @@ -1159,6 +1170,11 @@ static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) ctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + if (ios->bus_width == MMC_BUS_WIDTH_8) + ctrl |= SDHCI_CTRL_8BITBUS; + else + ctrl &= ~SDHCI_CTRL_8BITBUS; + if (ios->bus_width == MMC_BUS_WIDTH_4) ctrl |= SDHCI_CTRL_4BITBUS; else @@ -1603,7 +1619,10 @@ int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state) free_irq(host->irq, host); - return 0; + if (host->vmmc) + ret = regulator_disable(host->vmmc); + + return ret; } EXPORT_SYMBOL_GPL(sdhci_suspend_host); @@ -1612,6 +1631,13 @@ int sdhci_resume_host(struct sdhci_host *host) { int ret; + if (host->vmmc) { + int ret = regulator_enable(host->vmmc); + if (ret) + return ret; + } + + if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) host->ops->enable_dma(host); @@ -1687,7 +1713,8 @@ int sdhci_add_host(struct sdhci_host *host) host->version); } - caps = sdhci_readl(host, SDHCI_CAPABILITIES); + caps = (host->quirks & SDHCI_QUIRK_MISSING_CAPS) ? host->caps : + sdhci_readl(host, SDHCI_CAPABILITIES); if (host->quirks & SDHCI_QUIRK_FORCE_DMA) host->flags |= SDHCI_USE_SDMA; @@ -1785,13 +1812,12 @@ int sdhci_add_host(struct sdhci_host *host) * Set host parameters. */ mmc->ops = &sdhci_ops; - if (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK && - host->ops->set_clock && host->ops->get_min_clock) + if (host->ops->get_min_clock) mmc->f_min = host->ops->get_min_clock(host); else mmc->f_min = host->max_clk / 256; mmc->f_max = host->max_clk; - mmc->caps = MMC_CAP_SDIO_IRQ; + mmc->caps |= MMC_CAP_SDIO_IRQ; if (!(host->quirks & SDHCI_QUIRK_FORCE_1_BIT_DATA)) mmc->caps |= MMC_CAP_4_BIT_DATA; @@ -1884,6 +1910,14 @@ int sdhci_add_host(struct sdhci_host *host) if (ret) goto untasklet; + host->vmmc = regulator_get(mmc_dev(mmc), "vmmc"); + if (IS_ERR(host->vmmc)) { + printk(KERN_INFO "%s: no vmmc regulator found\n", mmc_hostname(mmc)); + host->vmmc = NULL; + } else { + regulator_enable(host->vmmc); + } + sdhci_init(host, 0); #ifdef CONFIG_MMC_DEBUG @@ -1968,6 +2002,11 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); + if (host->vmmc) { + regulator_disable(host->vmmc); + regulator_put(host->vmmc); + } + kfree(host->adma_desc); kfree(host->align_buffer); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index c8468134adc9..036cfae76368 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -72,6 +72,7 @@ #define SDHCI_CTRL_ADMA1 0x08 #define SDHCI_CTRL_ADMA32 0x10 #define SDHCI_CTRL_ADMA64 0x18 +#define SDHCI_CTRL_8BITBUS 0x20 #define SDHCI_POWER_CONTROL 0x29 #define SDHCI_POWER_ON 0x01 @@ -240,12 +241,18 @@ struct sdhci_host { #define SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN (1<<25) /* Controller cannot support End Attribute in NOP ADMA descriptor */ #define SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC (1<<26) +/* Controller is missing device caps. Use caps provided by host */ +#define SDHCI_QUIRK_MISSING_CAPS (1<<27) +/* Controller uses Auto CMD12 command to stop the transfer */ +#define SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 (1<<28) int irq; /* Device IRQ */ void __iomem * ioaddr; /* Mapped address */ const struct sdhci_ops *ops; /* Low level hw interface */ + struct regulator *vmmc; /* Power regulator */ + /* Internal data */ struct mmc_host *mmc; /* MMC structure */ u64 dma_mask; /* custom DMA mask */ @@ -292,6 +299,8 @@ struct sdhci_host { struct timer_list timer; /* Timer for timeouts */ + unsigned int caps; /* Alternative capabilities */ + unsigned long private[0] ____cacheline_aligned; }; @@ -407,6 +416,7 @@ static inline void *sdhci_priv(struct sdhci_host *host) return (void *)host->private; } +extern void sdhci_card_detect(struct sdhci_host *host); extern int sdhci_add_host(struct sdhci_host *host); extern void sdhci_remove_host(struct sdhci_host *host, int dead); |