From e6027b467cd5b117b9ae2957c197a7cda9f5b932 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Sat, 28 Jul 2012 12:07:34 +0200 Subject: mmc: pxa-mci: add DT bindings Signed-off-by: Daniel Mack Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/pxa-mmc.txt b/Documentation/devicetree/bindings/mmc/pxa-mmc.txt new file mode 100644 index 0000000..b7025de --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/pxa-mmc.txt @@ -0,0 +1,25 @@ +* PXA MMC drivers + +Driver bindings for the PXA MCI (MMC/SDIO) interfaces + +Required properties: +- compatible: Should be "marvell,pxa-mmc". +- vmmc-supply: A regulator for VMMC + +Optional properties: +- marvell,detect-delay-ms: sets the detection delay timeout in ms. +- marvell,gpio-power: GPIO spec for the card power enable pin + +This file documents differences between the core properties in mmc.txt +and the properties used by the pxa-mmc driver. + +Examples: + +mmc0: mmc@41100000 { + compatible = "marvell,pxa-mmc"; + reg = <0x41100000 0x1000>; + interrupts = <23>; + cd-gpios = <&gpio 23 0>; + wp-gpios = <&gpio 24 0>; +}; + diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index cb2dc0e..11df800 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include #include @@ -573,6 +576,50 @@ static irqreturn_t pxamci_detect_irq(int irq, void *devid) return IRQ_HANDLED; } +#ifdef CONFIG_OF +static const struct of_device_id pxa_mmc_dt_ids[] = { + { .compatible = "marvell,pxa-mmc" }, + { } +}; + +MODULE_DEVICE_TABLE(of, pxa_mmc_dt_ids); + +static int __devinit pxamci_of_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct pxamci_platform_data *pdata; + u32 tmp; + + if (!np) + return 0; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->gpio_card_detect = + of_get_named_gpio(np, "cd-gpios", 0); + pdata->gpio_card_ro = + of_get_named_gpio(np, "wp-gpios", 0); + + /* pxa-mmc specific */ + pdata->gpio_power = + of_get_named_gpio(np, "pxa-mmc,gpio-power", 0); + + if (of_property_read_u32(np, "pxa-mmc,detect-delay-ms", &tmp) == 0) + pdata->detect_delay_ms = tmp; + + pdev->dev.platform_data = pdata; + + return 0; +} +#else +static int __devinit pxamci_of_init(struct platform_device *pdev) +{ + return 0; +} +#endif + static int pxamci_probe(struct platform_device *pdev) { struct mmc_host *mmc; @@ -580,6 +627,10 @@ static int pxamci_probe(struct platform_device *pdev) struct resource *r, *dmarx, *dmatx; int ret, irq, gpio_cd = -1, gpio_ro = -1, gpio_power = -1; + ret = pxamci_of_init(pdev); + if (ret) + return ret; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); irq = platform_get_irq(pdev, 0); if (!r || irq < 0) @@ -866,6 +917,7 @@ static struct platform_driver pxamci_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pxa_mmc_dt_ids), #ifdef CONFIG_PM .pm = &pxamci_pm_ops, #endif -- cgit v0.10.2 From 137ccd46c5efaed6a8118cce3db2cbb64350113b Mon Sep 17 00:00:00 2001 From: Jerry Huang Date: Thu, 8 Mar 2012 11:25:02 +0800 Subject: mmc: eSDHC: Add ADMA mode support The register of eSDHC Host Controller Capabilities is not compatible with standard SDHC register, and eSDHC cannot support End Attribute in NOP ADMA descriptor. With this patch eSDHC can works in ADMA mode and performance can be improved. Signed-off-by: Jerry Huang Signed-off-by: Pan Jiafei Signed-off-by: Jason Jin Acked-by: Anton Vorontsov Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c index f8eb1fb..ae5fcbf 100644 --- a/drivers/mmc/host/sdhci-of-esdhc.c +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -21,6 +21,32 @@ #include "sdhci-pltfm.h" #include "sdhci-esdhc.h" +#define VENDOR_V_22 0x12 +static u32 esdhc_readl(struct sdhci_host *host, int reg) +{ + u32 ret; + + ret = in_be32(host->ioaddr + reg); + /* + * The bit of ADMA flag in eSDHC is not compatible with standard + * SDHC register, so set fake flag SDHCI_CAN_DO_ADMA2 when ADMA is + * supported by eSDHC. + * And for many FSL eSDHC controller, the reset value of field + * SDHCI_CAN_DO_ADMA1 is one, but some of them can't support ADMA, + * only these vendor version is greater than 2.2/0x12 support ADMA. + * For FSL eSDHC, must aligned 4-byte, so use 0xFC to read the + * the verdor version number, oxFE is SDHCI_HOST_VERSION. + */ + if ((reg == SDHCI_CAPABILITIES) && (ret & SDHCI_CAN_DO_ADMA1)) { + u32 tmp = in_be32(host->ioaddr + SDHCI_SLOT_INT_STATUS); + tmp = (tmp & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT; + if (tmp > VENDOR_V_22) + ret |= SDHCI_CAN_DO_ADMA2; + } + + return ret; +} + static u16 esdhc_readw(struct sdhci_host *host, int reg) { u16 ret; @@ -144,7 +170,7 @@ static void esdhc_of_resume(struct sdhci_host *host) #endif static struct sdhci_ops sdhci_esdhc_ops = { - .read_l = sdhci_be32bs_readl, + .read_l = esdhc_readl, .read_w = esdhc_readw, .read_b = esdhc_readb, .write_l = sdhci_be32bs_writel, @@ -161,9 +187,13 @@ static struct sdhci_ops sdhci_esdhc_ops = { }; static struct sdhci_pltfm_data sdhci_esdhc_pdata = { - /* card detection could be handled via GPIO */ + /* + * card detection could be handled via GPIO + * eSDHC cannot support End Attribute in NOP ADMA descriptor + */ .quirks = ESDHC_DEFAULT_QUIRKS | SDHCI_QUIRK_BROKEN_CARD_DETECTION - | SDHCI_QUIRK_NO_CARD_NO_RESET, + | SDHCI_QUIRK_NO_CARD_NO_RESET + | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, .ops = &sdhci_esdhc_ops, }; -- cgit v0.10.2 From 6231f3de1332b2a8a90e0c598ab6acc8f1eff7c1 Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Mon, 23 Jul 2012 15:56:23 -0700 Subject: mmc: sdhci: Add regulator support for vccq (voltage regualor) On some systems the host controller does not support vccq signaling. This is supplied by a dedicated regulator (vqmmc). Add support for this regulator. Signed-off-by: Philip Rakity Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 9a11dc3..828ac6c 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1597,57 +1597,65 @@ static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) spin_unlock_irqrestore(&host->lock, flags); } -static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, - struct mmc_ios *ios) +static int sdhci_do_3_3v_signal_voltage_switch(struct sdhci_host *host, + u16 ctrl) { - u8 pwr; - u16 clk, ctrl; - u32 present_state; + int ret; - /* - * Signal Voltage Switching is only applicable for Host Controllers - * v3.00 and above. - */ - if (host->version < SDHCI_SPEC_300) - return 0; + /* Set 1.8V Signal Enable in the Host Control2 register to 0 */ + ctrl &= ~SDHCI_CTRL_VDD_180; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); - /* - * We first check whether the request is to set signalling voltage - * to 3.3V. If so, we change the voltage to 3.3V and return quickly. - */ + if (host->vqmmc) { + ret = regulator_set_voltage(host->vqmmc, 3300000, 3300000); + if (ret) { + pr_warning("%s: Switching to 3.3V signalling voltage " + " failed\n", mmc_hostname(host->mmc)); + return -EIO; + } + } + /* Wait for 5ms */ + usleep_range(5000, 5500); + + /* 3.3V regulator output should be stable within 5 ms */ ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) { - /* Set 1.8V Signal Enable in the Host Control2 register to 0 */ - ctrl &= ~SDHCI_CTRL_VDD_180; - sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + if (!(ctrl & SDHCI_CTRL_VDD_180)) + return 0; - /* Wait for 5ms */ - usleep_range(5000, 5500); + pr_warning("%s: 3.3V regulator output did not became stable\n", + mmc_hostname(host->mmc)); - /* 3.3V regulator output should be stable within 5 ms */ - ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); - if (!(ctrl & SDHCI_CTRL_VDD_180)) - return 0; - else { - pr_info(DRIVER_NAME ": Switching to 3.3V " - "signalling voltage failed\n"); - return -EIO; - } - } else if (!(ctrl & SDHCI_CTRL_VDD_180) && - (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180)) { - /* Stop SDCLK */ - clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); - clk &= ~SDHCI_CLOCK_CARD_EN; - sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + return -EIO; +} - /* Check whether DAT[3:0] is 0000 */ - present_state = sdhci_readl(host, SDHCI_PRESENT_STATE); - if (!((present_state & SDHCI_DATA_LVL_MASK) >> - SDHCI_DATA_LVL_SHIFT)) { - /* - * Enable 1.8V Signal Enable in the Host Control2 - * register - */ +static int sdhci_do_1_8v_signal_voltage_switch(struct sdhci_host *host, + u16 ctrl) +{ + u8 pwr; + u16 clk; + u32 present_state; + int ret; + + /* Stop SDCLK */ + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + clk &= ~SDHCI_CLOCK_CARD_EN; + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + + /* Check whether DAT[3:0] is 0000 */ + present_state = sdhci_readl(host, SDHCI_PRESENT_STATE); + if (!((present_state & SDHCI_DATA_LVL_MASK) >> + SDHCI_DATA_LVL_SHIFT)) { + /* + * Enable 1.8V Signal Enable in the Host Control2 + * register + */ + if (host->vqmmc) + ret = regulator_set_voltage(host->vqmmc, + 1800000, 1800000); + else + ret = 0; + + if (!ret) { ctrl |= SDHCI_CTRL_VDD_180; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); @@ -1656,7 +1664,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); if (ctrl & SDHCI_CTRL_VDD_180) { - /* Provide SDCLK again and wait for 1ms*/ + /* Provide SDCLK again and wait for 1ms */ clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); clk |= SDHCI_CLOCK_CARD_EN; sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); @@ -1673,29 +1681,55 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, return 0; } } + } - /* - * If we are here, that means the switch to 1.8V signaling - * failed. We power cycle the card, and retry initialization - * sequence by setting S18R to 0. - */ - pwr = sdhci_readb(host, SDHCI_POWER_CONTROL); - pwr &= ~SDHCI_POWER_ON; - sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); - if (host->vmmc) - regulator_disable(host->vmmc); + /* + * If we are here, that means the switch to 1.8V signaling + * failed. We power cycle the card, and retry initialization + * sequence by setting S18R to 0. + */ + pwr = sdhci_readb(host, SDHCI_POWER_CONTROL); + pwr &= ~SDHCI_POWER_ON; + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); + if (host->vmmc) + regulator_disable(host->vmmc); - /* Wait for 1ms as per the spec */ - usleep_range(1000, 1500); - pwr |= SDHCI_POWER_ON; - sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); - if (host->vmmc) - regulator_enable(host->vmmc); + /* Wait for 1ms as per the spec */ + usleep_range(1000, 1500); + pwr |= SDHCI_POWER_ON; + sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); + if (host->vmmc) + regulator_enable(host->vmmc); - pr_info(DRIVER_NAME ": Switching to 1.8V signalling " - "voltage failed, retrying with S18R set to 0\n"); - return -EAGAIN; - } else + pr_warning("%s: Switching to 1.8V signalling voltage failed, " + "retrying with S18R set to 0\n", mmc_hostname(host->mmc)); + + return -EAGAIN; +} + +static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, + struct mmc_ios *ios) +{ + u16 ctrl; + + /* + * Signal Voltage Switching is only applicable for Host Controllers + * v3.00 and above. + */ + if (host->version < SDHCI_SPEC_300) + return 0; + + /* + * We first check whether the request is to set signalling voltage + * to 3.3V. If so, we change the voltage to 3.3V and return quickly. + */ + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) + return sdhci_do_3_3v_signal_voltage_switch(host, ctrl); + else if (!(ctrl & SDHCI_CTRL_VDD_180) && + (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_180)) + return sdhci_do_1_8v_signal_voltage_switch(host, ctrl); + else /* No signal voltage switch required */ return 0; } @@ -2802,6 +2836,18 @@ int sdhci_add_host(struct sdhci_host *host) !(host->mmc->caps & MMC_CAP_NONREMOVABLE)) mmc->caps |= MMC_CAP_NEEDS_POLL; + /* If vqmmc regulator and no 1.8V signalling, then there's no UHS */ + host->vqmmc = regulator_get(mmc_dev(mmc), "vqmmc"); + if (IS_ERR(host->vqmmc)) { + pr_info("%s: no vqmmc regulator found\n", mmc_hostname(mmc)); + host->vqmmc = NULL; + } + else if (regulator_is_supported_voltage(host->vqmmc, 1800000, 1800000)) + regulator_enable(host->vqmmc); + else + caps[1] &= ~(SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | + SDHCI_SUPPORT_DDR50); + /* Any UHS-I mode in caps implies SDR12 and SDR25 support. */ if (caps[1] & (SDHCI_SUPPORT_SDR104 | SDHCI_SUPPORT_SDR50 | SDHCI_SUPPORT_DDR50)) @@ -3122,6 +3168,11 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) if (host->vmmc) regulator_put(host->vmmc); + if (host->vqmmc) { + regulator_disable(host->vqmmc); + regulator_put(host->vqmmc); + } + kfree(host->adma_desc); kfree(host->align_buffer); diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index ac83b10..fa8529a 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -97,7 +97,8 @@ struct sdhci_host { const struct sdhci_ops *ops; /* Low level hw interface */ - struct regulator *vmmc; /* Power regulator */ + struct regulator *vmmc; /* Power regulator (vmmc) */ + struct regulator *vqmmc; /* Signaling regulator (vccq) */ /* Internal data */ struct mmc_host *mmc; /* MMC structure */ -- cgit v0.10.2 From 77dcb3f4c344d4c9619803848f1aba4d271dac7b Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Mon, 23 Jul 2012 17:25:18 -0700 Subject: mmc: sdhci: vmmc regulator should be explicitly enabled/disabled The vmmc regulator should not rely on the platform code to enable it. Expliciitly enable and disable the regulator inside the driver. Signed-off-by: Philip Rakity Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 828ac6c..d98b199 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2908,7 +2908,8 @@ int sdhci_add_host(struct sdhci_host *host) if (IS_ERR(host->vmmc)) { pr_info("%s: no vmmc regulator found\n", mmc_hostname(mmc)); host->vmmc = NULL; - } + } else + regulator_enable(host->vmmc); #ifdef CONFIG_REGULATOR if (host->vmmc) { @@ -3165,8 +3166,10 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); - if (host->vmmc) + if (host->vmmc) { + regulator_disable(host->vmmc); regulator_put(host->vmmc); + } if (host->vqmmc) { regulator_disable(host->vqmmc); -- cgit v0.10.2 From e919fd200033e80b26f152d22c00a8fae7f8d548 Mon Sep 17 00:00:00 2001 From: Ludovic Desroches Date: Tue, 24 Jul 2012 15:30:03 +0200 Subject: mmc: atmel-mci: add device tree support Signed-off-by: Ludovic Desroches Acked-by: Jean-Christophe PLAGNIOL-VILLARD Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/atmel-hsmci.txt b/Documentation/devicetree/bindings/mmc/atmel-hsmci.txt new file mode 100644 index 0000000..0a85c70 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/atmel-hsmci.txt @@ -0,0 +1,68 @@ +* Atmel High Speed MultiMedia Card Interface + +This controller on atmel products provides an interface for MMC, SD and SDIO +types of memory cards. + +This file documents differences between the core properties described +by mmc.txt and the properties used by the atmel-mci driver. + +1) MCI node + +Required properties: +- compatible: should be "atmel,hsmci" +- #address-cells: should be one. The cell is the slot id. +- #size-cells: should be zero. +- at least one slot node + +The node contains child nodes for each slot that the platform uses + +Example MCI node: + +mmc0: mmc@f0008000 { + compatible = "atmel,hsmci"; + reg = <0xf0008000 0x600>; + interrupts = <12 4>; + #address-cells = <1>; + #size-cells = <0>; + + [ child node definitions...] +}; + +2) slot nodes + +Required properties: +- reg: should contain the slot id. +- bus-width: number of data lines connected to the controller + +Optional properties: +- cd-gpios: specify GPIOs for card detection +- cd-inverted: invert the value of external card detect gpio line +- wp-gpios: specify GPIOs for write protection + +Example slot node: + +slot@0 { + reg = <0>; + bus-width = <4>; + cd-gpios = <&pioD 15 0> + cd-inverted; +}; + +Example full MCI node: +mmc0: mmc@f0008000 { + compatible = "atmel,hsmci"; + reg = <0xf0008000 0x600>; + interrupts = <12 4>; + #address-cells = <1>; + #size-cells = <0>; + slot@0 { + reg = <0>; + bus-width = <4>; + cd-gpios = <&pioD 15 0> + cd-inverted; + }; + slot@1 { + reg = <1>; + bus-width = <4>; + }; +}; diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index a53c7c4..8c72828 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -500,6 +503,70 @@ err: dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n"); } +#if defined(CONFIG_OF) +static const struct of_device_id atmci_dt_ids[] = { + { .compatible = "atmel,hsmci" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmci_dt_ids); + +static struct mci_platform_data __devinit* +atmci_of_init(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *cnp; + struct mci_platform_data *pdata; + u32 slot_id; + + if (!np) { + dev_err(&pdev->dev, "device node not found\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "could not allocate memory for pdata\n"); + return ERR_PTR(-ENOMEM); + } + + for_each_child_of_node(np, cnp) { + if (of_property_read_u32(cnp, "reg", &slot_id)) { + dev_warn(&pdev->dev, "reg property is missing for %s\n", + cnp->full_name); + continue; + } + + if (slot_id >= ATMCI_MAX_NR_SLOTS) { + dev_warn(&pdev->dev, "can't have more than %d slots\n", + ATMCI_MAX_NR_SLOTS); + break; + } + + if (of_property_read_u32(cnp, "bus-width", + &pdata->slot[slot_id].bus_width)) + pdata->slot[slot_id].bus_width = 1; + + pdata->slot[slot_id].detect_pin = + of_get_named_gpio(cnp, "cd-gpios", 0); + + pdata->slot[slot_id].detect_is_active_high = + of_property_read_bool(cnp, "cd-inverted"); + + pdata->slot[slot_id].wp_pin = + of_get_named_gpio(cnp, "wp-gpios", 0); + } + + return pdata; +} +#else /* CONFIG_OF */ +static inline struct mci_platform_data* +atmci_of_init(struct platform_device *dev) +{ + return ERR_PTR(-EINVAL); +} +#endif + static inline unsigned int atmci_get_version(struct atmel_mci *host) { return atmci_readl(host, ATMCI_VERSION) & 0x00000fff; @@ -2046,6 +2113,13 @@ static int __init atmci_init_slot(struct atmel_mci *host, slot->sdc_reg = sdc_reg; slot->sdio_irq = sdio_irq; + dev_dbg(&mmc->class_dev, + "slot[%u]: bus_width=%u, detect_pin=%d, " + "detect_is_active_high=%s, wp_pin=%d\n", + id, slot_data->bus_width, slot_data->detect_pin, + slot_data->detect_is_active_high ? "true" : "false", + slot_data->wp_pin); + mmc->ops = &atmci_ops; mmc->f_min = DIV_ROUND_UP(host->bus_hz, 512); mmc->f_max = host->bus_hz / 2; @@ -2268,8 +2342,14 @@ static int __init atmci_probe(struct platform_device *pdev) if (!regs) return -ENXIO; pdata = pdev->dev.platform_data; - if (!pdata) - return -ENXIO; + if (!pdata) { + pdata = atmci_of_init(pdev); + if (IS_ERR(pdata)) { + dev_err(&pdev->dev, "platform data not available\n"); + return PTR_ERR(pdata); + } + } + irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; @@ -2487,6 +2567,7 @@ static struct platform_driver atmci_driver = { .driver = { .name = "atmel_mci", .pm = ATMCI_PM_OPS, + .of_match_table = of_match_ptr(atmci_dt_ids), }, }; -- cgit v0.10.2 From 164378efe7612ae1506d1a3b21fd37abf9909a5c Mon Sep 17 00:00:00 2001 From: Chao Xie Date: Tue, 31 Jul 2012 14:35:25 +0800 Subject: mmc: sdhci-pxav2, sdhci-pxav3: use clk_prepare/unprepare APIs Prepare the clock before enabling it. Signed-off-by: Chao Xie Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-pxav2.c b/drivers/mmc/host/sdhci-pxav2.c index b6ee885..8e63a9c 100644 --- a/drivers/mmc/host/sdhci-pxav2.c +++ b/drivers/mmc/host/sdhci-pxav2.c @@ -197,7 +197,7 @@ static int __devinit sdhci_pxav2_probe(struct platform_device *pdev) goto err_clk_get; } pltfm_host->clk = clk; - clk_enable(clk); + clk_prepare_enable(clk); host->quirks = SDHCI_QUIRK_BROKEN_ADMA | SDHCI_QUIRK_BROKEN_TIMEOUT_VAL @@ -239,7 +239,7 @@ static int __devinit sdhci_pxav2_probe(struct platform_device *pdev) return 0; err_add_host: - clk_disable(clk); + clk_disable_unprepare(clk); clk_put(clk); err_clk_get: sdhci_pltfm_free(pdev); @@ -255,7 +255,7 @@ static int __devexit sdhci_pxav2_remove(struct platform_device *pdev) sdhci_remove_host(host, 1); - clk_disable(pltfm_host->clk); + clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); sdhci_pltfm_free(pdev); kfree(pxa); diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index 07fe383..a553b18 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -231,14 +231,14 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) pltfm_host = sdhci_priv(host); pltfm_host->priv = pxa; - clk = clk_get(dev, "PXA-SDHCLK"); + clk = clk_get(dev, NULL); if (IS_ERR(clk)) { dev_err(dev, "failed to get io clock\n"); ret = PTR_ERR(clk); goto err_clk_get; } pltfm_host->clk = clk; - clk_enable(clk); + clk_prepare_enable(clk); host->quirks = SDHCI_QUIRK_BROKEN_TIMEOUT_VAL | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC @@ -283,7 +283,7 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) return 0; err_add_host: - clk_disable(clk); + clk_disable_unprepare(clk); clk_put(clk); err_clk_get: sdhci_pltfm_free(pdev); @@ -299,7 +299,7 @@ static int __devexit sdhci_pxav3_remove(struct platform_device *pdev) sdhci_remove_host(host, 1); - clk_disable(pltfm_host->clk); + clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); sdhci_pltfm_free(pdev); kfree(pxa); -- cgit v0.10.2 From 4ee7ed0de9929f6f194d7a0ae867371ea47866fe Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Tue, 31 Jul 2012 10:12:59 +0200 Subject: mmc: sdhci-dove: DT support for sdhci-dove This patch adds device tree support and binding documentation for sdhci-dove. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/sdhci-dove.txt b/Documentation/devicetree/bindings/mmc/sdhci-dove.txt new file mode 100644 index 0000000..ae9aab9 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/sdhci-dove.txt @@ -0,0 +1,14 @@ +* Marvell sdhci-dove controller + +This file documents differences between the core properties in mmc.txt +and the properties used by the sdhci-pxav2 and sdhci-pxav3 drivers. + +- compatible: Should be "marvell,dove-sdhci". + +Example: + +sdio0: sdio@92000 { + compatible = "marvell,dove-sdhci"; + reg = <0x92000 0x100>; + interrupts = <35>; +}; diff --git a/drivers/mmc/host/sdhci-dove.c b/drivers/mmc/host/sdhci-dove.c index a6e53a1..90140eb 100644 --- a/drivers/mmc/host/sdhci-dove.c +++ b/drivers/mmc/host/sdhci-dove.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -126,11 +127,18 @@ static int __devexit sdhci_dove_remove(struct platform_device *pdev) return sdhci_pltfm_unregister(pdev); } +static const struct of_device_id sdhci_dove_of_match_table[] __devinitdata = { + { .compatible = "marvell,dove-sdhci", }, + {} +}; +MODULE_DEVICE_TABLE(of, sdhci_dove_of_match_table); + static struct platform_driver sdhci_dove_driver = { .driver = { .name = "sdhci-dove", .owner = THIS_MODULE, .pm = SDHCI_PLTFM_PMOPS, + .of_match_table = of_match_ptr(sdhci_dove_of_match_table), }, .probe = sdhci_dove_probe, .remove = __devexit_p(sdhci_dove_remove), -- cgit v0.10.2 From 5fdb4505f65227c127092772ccf9d460d69f4bc2 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Tue, 24 Jul 2012 17:06:10 +0200 Subject: mmc: vub300: add missing usb_free_urb Add missing usb_free_urb on failure path after usb_alloc_urb. A simplified version of the semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @km exists@ local idexpression e; expression e1,e2,e3; type T,T1; identifier f; @@ * e = usb_alloc_urb(...) ... when any when != e = e1 when != e1 = (T)e when != e1(...,(T)e,...) when != &e->f if(...) { ... when != e2(...,(T1)e,...) when != e3 = e when forall ( return <+...e...+>; | * return ...; ) } // Signed-off-by: Julia Lawall Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c index 3135a1a..6c39bf1 100644 --- a/drivers/mmc/host/vub300.c +++ b/drivers/mmc/host/vub300.c @@ -2358,9 +2358,9 @@ error5: * which is contained at the end of struct mmc */ error4: - usb_free_urb(command_out_urb); -error1: usb_free_urb(command_res_urb); +error1: + usb_free_urb(command_out_urb); error0: return retval; } -- cgit v0.10.2 From 1fdc90fb8fb752d4d8d2602115cf4bd071374068 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Wed, 8 Aug 2012 00:06:00 -0400 Subject: mmc: omap_hsmmc: Handle failure of regulator_get better. 1/ If regulator_get fails, return an error. This is important if it failed with EPROBE_DEFER, as the probe needs to be deferred. 2/ Don't set .set_power until the regulator has been found, or the deferred probe will not bother calling omap_hsmmc_reg_get(). Signed-off-by: NeilBrown Acked-by: Rajendra Nayak Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 3a09f93..e180735 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -301,12 +301,12 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) struct regulator *reg; int ocr_value = 0; - mmc_slot(host).set_power = omap_hsmmc_set_power; - reg = regulator_get(host->dev, "vmmc"); if (IS_ERR(reg)) { dev_dbg(host->dev, "vmmc regulator missing\n"); + return PTR_ERR(reg); } else { + mmc_slot(host).set_power = omap_hsmmc_set_power; host->vcc = reg; ocr_value = mmc_regulator_get_ocrmask(reg); if (!mmc_slot(host).ocr_mask) { -- cgit v0.10.2 From 109ec8c396eae4d54e14be61719184ec36704d15 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Wed, 8 Aug 2012 12:16:08 +0800 Subject: mmc: bfin_sdh: Update blackfin sd driver email in MAINTAINERS. Signed-off-by: Sonic Zhang Signed-off-by: Chris Ball diff --git a/MAINTAINERS b/MAINTAINERS index 94b823f..72c2681 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1513,7 +1513,7 @@ S: Supported F: drivers/rtc/rtc-bfin.c BLACKFIN SDH DRIVER -M: Cliff Cai +M: Sonic Zhang L: uclinux-dist-devel@blackfin.uclinux.org W: http://blackfin.uclinux.org S: Supported -- cgit v0.10.2 From 0462566b1e3129b2920b63b62a653b70a1bc36fa Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 7 Aug 2012 11:10:12 +0800 Subject: mmc: bfin_sdh: limit mmc DMA segment len to 1 for bf518. bf518 RSI dma doesn't work in array mode. Signed-off-by: Sonic Zhang Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/bfin_sdh.c b/drivers/mmc/host/bfin_sdh.c index a17dd73..56a1093 100644 --- a/drivers/mmc/host/bfin_sdh.c +++ b/drivers/mmc/host/bfin_sdh.c @@ -462,7 +462,11 @@ static int __devinit sdh_probe(struct platform_device *pdev) } mmc->ops = &sdh_ops; +#if defined(CONFIG_BF54x) mmc->max_segs = 32; +#elif defined(CONFIG_BF51x) + mmc->max_segs = 1; +#endif mmc->max_seg_size = 1 << 16; mmc->max_blk_size = 1 << 11; mmc->max_blk_count = 1 << 11; -- cgit v0.10.2 From 4ffdcf0469f1e7e47476ee410e8dfbc6aba61b65 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Wed, 8 Aug 2012 00:13:32 -0400 Subject: mmc: bfin_sdh: Add support for new RSI controller in bf60x In BF60x RSI controller: 1) MMR read/write width differs. 2) PWR and CTL MMRs are merged to together. 3) ROD and PD_DAT3 bit masks are obsolete. 4) New RSI block size MMR is defined. 5) The definition of DMA descriptor set size is changed. 6) set_ios should powers up controller in 2 steps. In addition, this patch cleans up the spin locks. Signed-off-by: Sonic Zhang Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/bfin_sdh.c b/drivers/mmc/host/bfin_sdh.c index 56a1093..f47526f 100644 --- a/drivers/mmc/host/bfin_sdh.c +++ b/drivers/mmc/host/bfin_sdh.c @@ -24,9 +24,7 @@ #include #include -#if defined(CONFIG_BF51x) -#define bfin_read_SDH_PWR_CTL bfin_read_RSI_PWR_CTL -#define bfin_write_SDH_PWR_CTL bfin_write_RSI_PWR_CTL +#if defined(CONFIG_BF51x) || defined(__ADSPBF60x__) #define bfin_read_SDH_CLK_CTL bfin_read_RSI_CLK_CTL #define bfin_write_SDH_CLK_CTL bfin_write_RSI_CLK_CTL #define bfin_write_SDH_ARGUMENT bfin_write_RSI_ARGUMENT @@ -45,8 +43,16 @@ #define bfin_write_SDH_E_STATUS bfin_write_RSI_E_STATUS #define bfin_read_SDH_STATUS bfin_read_RSI_STATUS #define bfin_write_SDH_MASK0 bfin_write_RSI_MASK0 +#define bfin_write_SDH_E_MASK bfin_write_RSI_E_MASK #define bfin_read_SDH_CFG bfin_read_RSI_CFG #define bfin_write_SDH_CFG bfin_write_RSI_CFG +# if defined(__ADSPBF60x__) +# define bfin_read_SDH_BLK_SIZE bfin_read_RSI_BLKSZ +# define bfin_write_SDH_BLK_SIZE bfin_write_RSI_BLKSZ +# else +# define bfin_read_SDH_PWR_CTL bfin_read_RSI_PWR_CTL +# define bfin_write_SDH_PWR_CTL bfin_write_RSI_PWR_CTL +# endif #endif struct sdh_host { @@ -62,6 +68,7 @@ struct sdh_host { dma_addr_t sg_dma; int dma_len; + unsigned long sclk; unsigned int imask; unsigned int power_mode; unsigned int clk_div; @@ -127,11 +134,15 @@ static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data) /* Only supports power-of-2 block size */ if (data->blksz & (data->blksz - 1)) return -EINVAL; +#ifndef RSI_BLKSZ data_ctl |= ((ffs(data->blksz) - 1) << 4); +#else + bfin_write_SDH_BLK_SIZE(data->blksz); +#endif bfin_write_SDH_DATA_CTL(data_ctl); /* the time of a host clock period in ns */ - cycle_ns = 1000000000 / (get_sclk() / (2 * (host->clk_div + 1))); + cycle_ns = 1000000000 / (host->sclk / (2 * (host->clk_div + 1))); timeout = data->timeout_ns / cycle_ns; timeout += data->timeout_clks; bfin_write_SDH_DATA_TIMER(timeout); @@ -145,8 +156,13 @@ static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data) sdh_enable_stat_irq(host, (DAT_CRC_FAIL | DAT_TIME_OUT | DAT_END)); host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma_dir); -#if defined(CONFIG_BF54x) - dma_cfg |= DMAFLOW_ARRAY | NDSIZE_5 | RESTART | WDSIZE_32 | DMAEN; +#if defined(CONFIG_BF54x) || defined(CONFIG_BF60x) + dma_cfg |= DMAFLOW_ARRAY | RESTART | WDSIZE_32 | DMAEN; +# ifdef RSI_BLKSZ + dma_cfg |= PSIZE_32 | NDSIZE_3; +# else + dma_cfg |= NDSIZE_5; +# endif { struct scatterlist *sg; int i; @@ -156,7 +172,7 @@ static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data) host->sg_cpu[i].x_count = sg_dma_len(sg) / 4; host->sg_cpu[i].x_modify = 4; dev_dbg(mmc_dev(host->mmc), "%d: start_addr:0x%lx, " - "cfg:0x%x, x_count:0x%x, x_modify:0x%x\n", + "cfg:0x%lx, x_count:0x%lx, x_modify:0x%lx\n", i, host->sg_cpu[i].start_addr, host->sg_cpu[i].cfg, host->sg_cpu[i].x_count, host->sg_cpu[i].x_modify); @@ -172,6 +188,7 @@ static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data) set_dma_curr_desc_addr(host->dma_ch, (unsigned long *)host->sg_dma); set_dma_x_count(host->dma_ch, 0); set_dma_x_modify(host->dma_ch, 0); + SSYNC(); set_dma_config(host->dma_ch, dma_cfg); #elif defined(CONFIG_BF51x) /* RSI DMA doesn't work in array mode */ @@ -179,6 +196,7 @@ static int sdh_setup_data(struct sdh_host *host, struct mmc_data *data) set_dma_start_addr(host->dma_ch, sg_dma_address(&data->sg[0])); set_dma_x_count(host->dma_ch, length / 4); set_dma_x_modify(host->dma_ch, 4); + SSYNC(); set_dma_config(host->dma_ch, dma_cfg); #endif bfin_write_SDH_DATA_CTL(bfin_read_SDH_DATA_CTL() | DTX_DMA_E | DTX_E); @@ -296,7 +314,6 @@ static int sdh_data_done(struct sdh_host *host, unsigned int stat) else data->bytes_xfered = 0; - sdh_disable_stat_irq(host, DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | RX_OVERRUN | TX_UNDERRUN); bfin_write_SDH_STATUS_CLR(DAT_END_STAT | DAT_TIMEOUT_STAT | \ DAT_CRC_FAIL_STAT | DAT_BLK_END_STAT | RX_OVERRUN | TX_UNDERRUN); bfin_write_SDH_DATA_CTL(0); @@ -321,74 +338,115 @@ static void sdh_request(struct mmc_host *mmc, struct mmc_request *mrq) dev_dbg(mmc_dev(host->mmc), "%s enter, mrp:%p, cmd:%p\n", __func__, mrq, mrq->cmd); WARN_ON(host->mrq != NULL); + spin_lock(&host->lock); host->mrq = mrq; host->data = mrq->data; if (mrq->data && mrq->data->flags & MMC_DATA_READ) { ret = sdh_setup_data(host, mrq->data); if (ret) - return; + goto data_err; } sdh_start_cmd(host, mrq->cmd); +data_err: + spin_unlock(&host->lock); } static void sdh_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct sdh_host *host; - unsigned long flags; u16 clk_ctl = 0; +#ifndef RSI_BLKSZ u16 pwr_ctl = 0; +#endif u16 cfg; host = mmc_priv(mmc); - spin_lock_irqsave(&host->lock, flags); - if (ios->clock) { - unsigned long sys_clk, ios_clk; - unsigned char clk_div; - ios_clk = 2 * ios->clock; - sys_clk = get_sclk(); - clk_div = sys_clk / ios_clk; - if (sys_clk % ios_clk == 0) - clk_div -= 1; - clk_div = min_t(unsigned char, clk_div, 0xFF); - clk_ctl |= clk_div; - clk_ctl |= CLK_E; - host->clk_div = clk_div; - } else - sdh_stop_clock(host); - - if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) -#ifdef CONFIG_SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND - pwr_ctl |= ROD_CTL; -#else - pwr_ctl |= SD_CMD_OD | ROD_CTL; -#endif + spin_lock(&host->lock); - if (ios->bus_width == MMC_BUS_WIDTH_4) { - cfg = bfin_read_SDH_CFG(); + cfg = bfin_read_SDH_CFG(); + cfg |= MWE; + switch (ios->bus_width) { + case MMC_BUS_WIDTH_4: +#ifndef RSI_BLKSZ cfg &= ~PD_SDDAT3; +#endif cfg |= PUP_SDDAT3; /* Enable 4 bit SDIO */ - cfg |= (SD4E | MWE); - bfin_write_SDH_CFG(cfg); - clk_ctl |= WIDE_BUS; - } else { - cfg = bfin_read_SDH_CFG(); - cfg |= MWE; - bfin_write_SDH_CFG(cfg); + cfg |= SD4E; + clk_ctl |= WIDE_BUS_4; + break; + case MMC_BUS_WIDTH_8: +#ifndef RSI_BLKSZ + cfg &= ~PD_SDDAT3; +#endif + cfg |= PUP_SDDAT3; + /* Disable 4 bit SDIO */ + cfg &= ~SD4E; + clk_ctl |= BYTE_BUS_8; + break; + default: + cfg &= ~PUP_SDDAT3; + /* Disable 4 bit SDIO */ + cfg &= ~SD4E; } - bfin_write_SDH_CLK_CTL(clk_ctl); - host->power_mode = ios->power_mode; - if (ios->power_mode == MMC_POWER_ON) +#ifndef RSI_BLKSZ + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) { + pwr_ctl |= ROD_CTL; +# ifndef CONFIG_SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND + pwr_ctl |= SD_CMD_OD; +# endif + } + + if (ios->power_mode != MMC_POWER_OFF) pwr_ctl |= PWR_ON; + else + pwr_ctl &= ~PWR_ON; bfin_write_SDH_PWR_CTL(pwr_ctl); +#else +# ifndef CONFIG_SDH_BFIN_MISSING_CMD_PULLUP_WORKAROUND + if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) + cfg |= SD_CMD_OD; + else + cfg &= ~SD_CMD_OD; +# endif + + + if (ios->power_mode != MMC_POWER_OFF) + cfg |= PWR_ON; + else + cfg &= ~PWR_ON; + + bfin_write_SDH_CFG(cfg); +#endif SSYNC(); - spin_unlock_irqrestore(&host->lock, flags); + if (ios->power_mode == MMC_POWER_ON && ios->clock) { + unsigned char clk_div; + clk_div = (get_sclk() / ios->clock - 1) / 2; + clk_div = min_t(unsigned char, clk_div, 0xFF); + clk_ctl |= clk_div; + clk_ctl |= CLK_E; + host->clk_div = clk_div; + bfin_write_SDH_CLK_CTL(clk_ctl); + + } else + sdh_stop_clock(host); + + /* set up sdh interrupt mask*/ + if (ios->power_mode == MMC_POWER_ON) + bfin_write_SDH_MASK0(DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | + RX_OVERRUN | TX_UNDERRUN | CMD_SENT | CMD_RESP_END | + CMD_TIME_OUT | CMD_CRC_FAIL); + else + bfin_write_SDH_MASK0(0); + SSYNC(); + + spin_unlock(&host->lock); dev_dbg(mmc_dev(host->mmc), "SDH: clk_div = 0x%x actual clock:%ld expected clock:%d\n", host->clk_div, @@ -405,7 +463,7 @@ static irqreturn_t sdh_dma_irq(int irq, void *devid) { struct sdh_host *host = devid; - dev_dbg(mmc_dev(host->mmc), "%s enter, irq_stat: 0x%04x\n", __func__, + dev_dbg(mmc_dev(host->mmc), "%s enter, irq_stat: 0x%04lx\n", __func__, get_dma_curr_irqstat(host->dma_ch)); clear_dma_irqstat(host->dma_ch); SSYNC(); @@ -420,6 +478,9 @@ static irqreturn_t sdh_stat_irq(int irq, void *devid) int handled = 0; dev_dbg(mmc_dev(host->mmc), "%s enter\n", __func__); + + spin_lock(&host->lock); + status = bfin_read_SDH_E_STATUS(); if (status & SD_CARD_DET) { mmc_detect_change(host->mmc, 0); @@ -437,6 +498,8 @@ static irqreturn_t sdh_stat_irq(int irq, void *devid) if (status & (DAT_END | DAT_TIME_OUT | DAT_CRC_FAIL | RX_OVERRUN | TX_UNDERRUN)) handled |= sdh_data_done(host, status); + spin_unlock(&host->lock); + dev_dbg(mmc_dev(host->mmc), "%s exit\n\n", __func__); return IRQ_RETVAL(handled); @@ -462,12 +525,16 @@ static int __devinit sdh_probe(struct platform_device *pdev) } mmc->ops = &sdh_ops; -#if defined(CONFIG_BF54x) - mmc->max_segs = 32; -#elif defined(CONFIG_BF51x) +#if defined(CONFIG_BF51x) mmc->max_segs = 1; +#else + mmc->max_segs = PAGE_SIZE / sizeof(struct dma_desc_array); #endif +#ifdef RSI_BLKSZ + mmc->max_seg_size = -1; +#else mmc->max_seg_size = 1 << 16; +#endif mmc->max_blk_size = 1 << 11; mmc->max_blk_count = 1 << 11; mmc->max_req_size = PAGE_SIZE; @@ -477,6 +544,7 @@ static int __devinit sdh_probe(struct platform_device *pdev) mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_NEEDS_POLL; host = mmc_priv(mmc); host->mmc = mmc; + host->sclk = get_sclk(); spin_lock_init(&host->lock); host->irq = drv_data->irq_int0; @@ -501,7 +569,6 @@ static int __devinit sdh_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, mmc); - mmc_add_host(mmc); ret = request_irq(host->irq, sdh_stat_irq, 0, "SDH Status IRQ", host); if (ret) { @@ -522,12 +589,13 @@ static int __devinit sdh_probe(struct platform_device *pdev) bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN); SSYNC(); - /* Disable card inserting detection pin. set MMC_CAP_NEES_POLL, and + /* Disable card inserting detection pin. set MMC_CAP_NEEDS_POLL, and * mmc stack will do the detection. */ bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3)); SSYNC(); + mmc_add_host(mmc); return 0; out4: @@ -575,7 +643,11 @@ static int sdh_suspend(struct platform_device *dev, pm_message_t state) if (mmc) ret = mmc_suspend_host(mmc); +#ifndef RSI_BLKSZ bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() & ~PWR_ON); +#else + bfin_write_SDH_CFG(bfin_read_SDH_CFG() & ~PWR_ON); +#endif peripheral_free_list(drv_data->pin_req); return ret; @@ -593,12 +665,16 @@ static int sdh_resume(struct platform_device *dev) return ret; } - bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() | PWR_ON); #if defined(CONFIG_BF54x) /* Secure Digital Host shares DMA with Nand controller */ bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1); #endif +#ifndef RSI_BLKSZ + bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() | PWR_ON); bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN); +#else + bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN | PWR_ON); +#endif SSYNC(); bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3)); -- cgit v0.10.2 From c5e4a2e4c6fe47d88a1b172e54ed47a463f0ce45 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Wed, 8 Aug 2012 00:14:26 -0400 Subject: mmc: bfin_sdh: Remove redundant suspend and resume setting. mmc core suspend and resume functions had already called sdh_set_ios with proper flags. Signed-off-by: Sonic Zhang Signed-off-by: Bob Liu Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/bfin_sdh.c b/drivers/mmc/host/bfin_sdh.c index f47526f..7776614 100644 --- a/drivers/mmc/host/bfin_sdh.c +++ b/drivers/mmc/host/bfin_sdh.c @@ -643,11 +643,6 @@ static int sdh_suspend(struct platform_device *dev, pm_message_t state) if (mmc) ret = mmc_suspend_host(mmc); -#ifndef RSI_BLKSZ - bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() & ~PWR_ON); -#else - bfin_write_SDH_CFG(bfin_read_SDH_CFG() & ~PWR_ON); -#endif peripheral_free_list(drv_data->pin_req); return ret; @@ -669,16 +664,6 @@ static int sdh_resume(struct platform_device *dev) /* Secure Digital Host shares DMA with Nand controller */ bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1); #endif -#ifndef RSI_BLKSZ - bfin_write_SDH_PWR_CTL(bfin_read_SDH_PWR_CTL() | PWR_ON); - bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN); -#else - bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN | PWR_ON); -#endif - SSYNC(); - - bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3)); - SSYNC(); if (mmc) ret = mmc_resume_host(mmc); -- cgit v0.10.2 From 7a8a43c46a4c91475ff93eb00897cbbc5ec98874 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Wed, 8 Aug 2012 00:15:20 -0400 Subject: mmc: bfin_sdh: Reset SD controller when resume from power saving mode. Without reset, SD controller may stay in disable mode, SD card can't be detected when resume from power saving mode. Signed-off-by: Sonic Zhang Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/bfin_sdh.c b/drivers/mmc/host/bfin_sdh.c index 7776614..b9b463e 100644 --- a/drivers/mmc/host/bfin_sdh.c +++ b/drivers/mmc/host/bfin_sdh.c @@ -505,6 +505,23 @@ static irqreturn_t sdh_stat_irq(int irq, void *devid) return IRQ_RETVAL(handled); } +static void sdh_reset(void) +{ +#if defined(CONFIG_BF54x) + /* Secure Digital Host shares DMA with Nand controller */ + bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1); +#endif + + bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN); + SSYNC(); + + /* Disable card inserting detection pin. set MMC_CAP_NEEDS_POLL, and + * mmc stack will do the detection. + */ + bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3)); + SSYNC(); +} + static int __devinit sdh_probe(struct platform_device *pdev) { struct mmc_host *mmc; @@ -581,19 +598,8 @@ static int __devinit sdh_probe(struct platform_device *pdev) dev_err(&pdev->dev, "unable to request peripheral pins\n"); goto out4; } -#if defined(CONFIG_BF54x) - /* Secure Digital Host shares DMA with Nand controller */ - bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1); -#endif - bfin_write_SDH_CFG(bfin_read_SDH_CFG() | CLKS_EN); - SSYNC(); - - /* Disable card inserting detection pin. set MMC_CAP_NEEDS_POLL, and - * mmc stack will do the detection. - */ - bfin_write_SDH_CFG((bfin_read_SDH_CFG() & 0x1F) | (PUP_SDDAT | PUP_SDDAT3)); - SSYNC(); + sdh_reset(); mmc_add_host(mmc); return 0; @@ -660,10 +666,7 @@ static int sdh_resume(struct platform_device *dev) return ret; } -#if defined(CONFIG_BF54x) - /* Secure Digital Host shares DMA with Nand controller */ - bfin_write_DMAC1_PERIMUX(bfin_read_DMAC1_PERIMUX() | 0x1); -#endif + sdh_reset(); if (mmc) ret = mmc_resume_host(mmc); -- cgit v0.10.2 From 211d4fe580735bab850d31f9e4c0a62b47a38bb2 Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Tue, 7 Aug 2012 19:24:45 +0530 Subject: mmc: core: Add TRANsfer state to non-HPI state HPI can be issued only in programming state to bring the card to transfer state. If the card is already in transfer state, doing a HPI is redundant. Fix this by adding transfer state to the list of exceptions to doing HPI and return without error. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 8ac5246..835c9f0 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -424,8 +424,9 @@ int mmc_interrupt_hpi(struct mmc_card *card) case R1_STATE_IDLE: case R1_STATE_READY: case R1_STATE_STBY: + case R1_STATE_TRAN: /* - * In idle states, HPI is not needed and the caller + * In idle and transfer states, HPI is not needed and the caller * can issue the next intended command immediately */ goto out; -- cgit v0.10.2 From 6780162c0097915dbc4e8fc11af662a270093d85 Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Tue, 7 Aug 2012 19:29:06 +0530 Subject: mmc: debugfs: Print ext_csd in ascending order ext_csd exported through debugfs is printed in reverse order (from byte 511 to 0), which causes confusion. Fix the for loop to print ext_csd in natural order. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 9ab5b17..d96c643 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -281,7 +281,7 @@ static int mmc_ext_csd_open(struct inode *inode, struct file *filp) if (err) goto out_free; - for (i = 511; i >= 0; i--) + for (i = 0; i < 512; i++) n += sprintf(buf + n, "%02x", ext_csd[i]); n += sprintf(buf + n, "\n"); BUG_ON(n != EXT_CSD_STR_LEN); -- cgit v0.10.2 From 2976837c6ea8c852389b99e6e1a6f91ccd21fba1 Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Tue, 7 Aug 2012 18:56:35 +0530 Subject: mmc: omap: remove unused variables and includes Get rid of some unnecessary includes in the driver and a few unused variables. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index a5999a7..921cf3a 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -27,18 +27,10 @@ #include #include #include -#include #include -#include -#include - -#include #include -#include #include -#include -#include #define OMAP_MMC_REG_CMD 0x00 #define OMAP_MMC_REG_ARGL 0x01 @@ -107,7 +99,6 @@ struct mmc_omap_slot { u16 saved_con; u16 bus_mode; unsigned int fclk_freq; - unsigned powered:1; struct tasklet_struct cover_tasklet; struct timer_list cover_timer; @@ -139,7 +130,6 @@ struct mmc_omap_host { unsigned int phys_base; int irq; unsigned char bus_mode; - unsigned char hw_bus_mode; unsigned int reg_shift; struct work_struct cmd_abort_work; -- cgit v0.10.2 From 75d569d343bdb759105477ea9876cbd62e43d103 Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Tue, 7 Aug 2012 19:03:01 +0530 Subject: mmc: omap: fix mmc_omap_report_irq to use dev_dbg macros The function mmc_omap_report_irq uses raw printks and the actual output was disabled by a static variable. Make the function use dev_vdbg macro and use it under the standard CONFIG_MMC_DEBUG flag. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 921cf3a..48ad361 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -687,22 +687,29 @@ mmc_omap_xfer_data(struct mmc_omap_host *host, int write) host->buffer += nwords; } -static inline void mmc_omap_report_irq(u16 status) +#ifdef CONFIG_MMC_DEBUG +static void mmc_omap_report_irq(struct mmc_omap_host *host, u16 status) { static const char *mmc_omap_status_bits[] = { "EOC", "CD", "CB", "BRS", "EOFB", "DTO", "DCRC", "CTO", "CCRC", "CRW", "AF", "AE", "OCRB", "CIRQ", "CERR" }; - int i, c = 0; + int i; + char res[64], *buf = res; + + buf += sprintf(buf, "MMC IRQ 0x%x:", status); for (i = 0; i < ARRAY_SIZE(mmc_omap_status_bits); i++) - if (status & (1 << i)) { - if (c) - printk(" "); - printk("%s", mmc_omap_status_bits[i]); - c++; - } + if (status & (1 << i)) + buf += sprintf(buf, " %s", mmc_omap_status_bits[i]); + dev_vdbg(mmc_dev(host->mmc), "%s\n", res); +} +#else +static void mmc_omap_report_irq(struct mmc_omap_host *host, u16 status) +{ } +#endif + static irqreturn_t mmc_omap_irq(int irq, void *dev_id) { @@ -736,12 +743,10 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) cmd = host->cmd->opcode; else cmd = -1; -#ifdef CONFIG_MMC_DEBUG dev_dbg(mmc_dev(host->mmc), "MMC IRQ %04x (CMD %d): ", status, cmd); - mmc_omap_report_irq(status); - printk("\n"); -#endif + mmc_omap_report_irq(host, status); + if (host->total_bytes_left) { if ((status & OMAP_MMC_STAT_A_FULL) || (status & OMAP_MMC_STAT_END_OF_DATA)) -- cgit v0.10.2 From 8986d31bf5bd3c2c06c1fe0dc18f8ea50e9f2a50 Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Tue, 7 Aug 2012 19:10:38 +0530 Subject: mmc: omap_hsmmc: remove unused vars and includes Some straight forward cleanup of unnecessary #include's and host variables. Some of the verbose and redundant debug messages are converted to use dev_vdbg. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index e180735..da4f5a7 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -162,8 +161,6 @@ struct omap_hsmmc_host { unsigned int dma_sg_idx; unsigned char bus_mode; unsigned char power_mode; - u32 *buffer; - u32 bytesleft; int suspended; int irq; int use_dma, dma_ch; @@ -172,7 +169,6 @@ struct omap_hsmmc_host { int slot_id; int response_busy; int context_loss; - int vdd; int protect_card; int reqs_blocked; int use_reg; @@ -496,7 +492,7 @@ static void omap_hsmmc_set_clock(struct omap_hsmmc_host *host) unsigned long regval; unsigned long timeout; - dev_dbg(mmc_dev(host->mmc), "Set clock to %uHz\n", ios->clock); + dev_vdbg(mmc_dev(host->mmc), "Set clock to %uHz\n", ios->clock); omap_hsmmc_stop_clock(host); @@ -746,7 +742,7 @@ omap_hsmmc_start_command(struct omap_hsmmc_host *host, struct mmc_command *cmd, { int cmdreg = 0, resptype = 0, cmdtype = 0; - dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n", + dev_vdbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n", mmc_hostname(host->mmc), cmd->opcode, cmd->arg); host->cmd = cmd; @@ -935,7 +931,7 @@ static void omap_hsmmc_dbg_report_irq(struct omap_hsmmc_host *host, u32 status) buf += len; } - dev_dbg(mmc_dev(host->mmc), "%s\n", res); + dev_vdbg(mmc_dev(host->mmc), "%s\n", res); } #else static inline void omap_hsmmc_dbg_report_irq(struct omap_hsmmc_host *host, @@ -997,7 +993,7 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) } data = host->data; - dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status); + dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status); if (status & ERR) { omap_hsmmc_dbg_report_irq(host, status); @@ -1502,12 +1498,10 @@ static void omap_hsmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) case MMC_POWER_OFF: mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0); - host->vdd = 0; break; case MMC_POWER_UP: mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd); - host->vdd = ios->vdd; break; case MMC_POWER_ON: do_send_init_stream = 1; -- cgit v0.10.2 From 6c31b2150ff96755d24e0ab6d6fea08a7bf5c44c Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Wed, 8 Aug 2012 14:26:52 +0530 Subject: mmc: omap_hsmmc: remove access to SYSCONFIG register SYSCONFIG register of HSMMC IP is managed by the omap hwmod abstraction layer. Resetting the IP and configuring the correct SYSCONFIG mode is centrally managed by hwmod. Remove code which manipulates IP reset and SYSCONFIG directly in the driver. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index da4f5a7..4bc55ac 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -44,7 +44,6 @@ #include /* OMAP HSMMC Host Controller Registers */ -#define OMAP_HSMMC_SYSCONFIG 0x0010 #define OMAP_HSMMC_SYSSTATUS 0x0014 #define OMAP_HSMMC_CON 0x002C #define OMAP_HSMMC_BLK 0x0104 @@ -576,21 +575,8 @@ static int omap_hsmmc_context_restore(struct omap_hsmmc_host *host) if (host->context_loss == context_loss) return 1; - /* Wait for hardware reset */ - timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); - while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE - && time_before(jiffies, timeout)) - ; - - /* Do software reset */ - OMAP_HSMMC_WRITE(host->base, SYSCONFIG, SOFTRESET); - timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS); - while ((OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) != RESETDONE - && time_before(jiffies, timeout)) - ; - - OMAP_HSMMC_WRITE(host->base, SYSCONFIG, - OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE); + if (!OMAP_HSMMC_READ(host->base, SYSSTATUS) & RESETDONE) + return 1; if (host->pdata->controller_flags & OMAP_HSMMC_SUPPORTS_DUAL_VOLT) { if (host->power_mode != MMC_POWER_OFF && @@ -1593,10 +1579,6 @@ static void omap_hsmmc_conf_bus_power(struct omap_hsmmc_host *host) value = OMAP_HSMMC_READ(host->base, CAPA); OMAP_HSMMC_WRITE(host->base, CAPA, value | capa); - /* Set the controller to AUTO IDLE mode */ - value = OMAP_HSMMC_READ(host->base, SYSCONFIG); - OMAP_HSMMC_WRITE(host->base, SYSCONFIG, value | AUTOIDLE); - /* Set SD bus power bit */ set_sd_bus_power(host); } @@ -1654,8 +1636,6 @@ static int omap_hsmmc_regs_show(struct seq_file *s, void *data) pm_runtime_get_sync(host->dev); - seq_printf(s, "SYSCONFIG:\t0x%08x\n", - OMAP_HSMMC_READ(host->base, SYSCONFIG)); seq_printf(s, "CON:\t\t0x%08x\n", OMAP_HSMMC_READ(host->base, CON)); seq_printf(s, "HCTL:\t\t0x%08x\n", -- cgit v0.10.2 From 1f6b9fa40e76fffaaa0b3bd6a0bfdcf1cdc06efa Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Wed, 8 Aug 2012 15:44:29 +0530 Subject: mmc: omap_hsmmc: consolidate flush posted writes for HSMMC IRQs Flushing spurious IRQs from HSMMC IP is done twice in omap_hsmmc_irq and omap_hsmmc_do_irq. Consolidate them to one location. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 4bc55ac..20453c8 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -969,15 +969,6 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) struct mmc_data *data; int end_cmd = 0, end_trans = 0; - if (!host->req_in_progress) { - do { - OMAP_HSMMC_WRITE(host->base, STAT, status); - /* Flush posted write */ - status = OMAP_HSMMC_READ(host->base, STAT); - } while (status & INT_EN_MASK); - return; - } - data = host->data; dev_vdbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status); @@ -1028,8 +1019,6 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) } } - OMAP_HSMMC_WRITE(host->base, STAT, status); - if (end_cmd || ((status & CC) && host->cmd)) omap_hsmmc_cmd_done(host, host->cmd); if ((end_trans || (status & TC)) && host->mrq) @@ -1045,11 +1034,13 @@ static irqreturn_t omap_hsmmc_irq(int irq, void *dev_id) int status; status = OMAP_HSMMC_READ(host->base, STAT); - do { + while (status & INT_EN_MASK && host->req_in_progress) { omap_hsmmc_do_irq(host, status); + /* Flush posted write */ + OMAP_HSMMC_WRITE(host->base, STAT, status); status = OMAP_HSMMC_READ(host->base, STAT); - } while (status & INT_EN_MASK); + } return IRQ_HANDLED; } -- cgit v0.10.2 From ae4bf788ee9bf7c2d51b0309117d1fcccbdd50a2 Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Thu, 9 Aug 2012 20:36:07 +0530 Subject: mmc: omap_hsmmc: consolidate error report handling of HSMMC IRQ Consolidate the duplicated code around the handling of CMD_TIMEOUT, CMD_CRC, DATA_TIMEOUT, DATA_CRC and CARD_ERR handling into a single function. This generally shrinks code bloat, but is also required for implementing software based guard timers. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 20453c8..9afdd20 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -964,6 +964,18 @@ static inline void omap_hsmmc_reset_controller_fsm(struct omap_hsmmc_host *host, __func__); } +static void hsmmc_command_incomplete(struct omap_hsmmc_host *host, int err) +{ + omap_hsmmc_reset_controller_fsm(host, SRC); + host->cmd->error = err; + + if (host->data) { + omap_hsmmc_reset_controller_fsm(host, SRD); + omap_hsmmc_dma_cleanup(host, err); + } + +} + static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) { struct mmc_data *data; @@ -974,48 +986,15 @@ static void omap_hsmmc_do_irq(struct omap_hsmmc_host *host, int status) if (status & ERR) { omap_hsmmc_dbg_report_irq(host, status); - if ((status & CMD_TIMEOUT) || - (status & CMD_CRC)) { - if (host->cmd) { - if (status & CMD_TIMEOUT) { - omap_hsmmc_reset_controller_fsm(host, - SRC); - host->cmd->error = -ETIMEDOUT; - } else { - host->cmd->error = -EILSEQ; - } - end_cmd = 1; - } - if (host->data || host->response_busy) { - if (host->data) - omap_hsmmc_dma_cleanup(host, - -ETIMEDOUT); - host->response_busy = 0; - omap_hsmmc_reset_controller_fsm(host, SRD); - } - } - if ((status & DATA_TIMEOUT) || - (status & DATA_CRC)) { - if (host->data || host->response_busy) { - int err = (status & DATA_TIMEOUT) ? - -ETIMEDOUT : -EILSEQ; - - if (host->data) - omap_hsmmc_dma_cleanup(host, err); - else - host->mrq->cmd->error = err; - host->response_busy = 0; - omap_hsmmc_reset_controller_fsm(host, SRD); - end_trans = 1; - } - } - if (status & CARD_ERR) { - dev_dbg(mmc_dev(host->mmc), - "Ignoring card err CMD%d\n", host->cmd->opcode); - if (host->cmd) - end_cmd = 1; - if (host->data) - end_trans = 1; + if (status & (CMD_TIMEOUT | DATA_TIMEOUT)) + hsmmc_command_incomplete(host, -ETIMEDOUT); + else if (status & (CMD_CRC | DATA_CRC)) + hsmmc_command_incomplete(host, -EILSEQ); + + end_cmd = 1; + if (host->data || host->response_busy) { + end_trans = 1; + host->response_busy = 0; } } -- cgit v0.10.2 From 0a4585c634534ca5a6d5e60b4d2850b4efedc85e Mon Sep 17 00:00:00 2001 From: Venkatraman S Date: Fri, 17 Aug 2012 23:59:53 +0530 Subject: mmc: omap_hsmmc: Move to Maintained state in MAINTAINERS I can continue to look after this driver. Signed-off-by: Venkatraman S Acked-by: Felipe Balbi Signed-off-by: Chris Ball diff --git a/MAINTAINERS b/MAINTAINERS index 72c2681..75e3c3e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4933,8 +4933,10 @@ S: Maintained F: drivers/mmc/host/omap.c OMAP HS MMC SUPPORT +M: Venkatraman S +L: linux-mmc@vger.kernel.org L: linux-omap@vger.kernel.org -S: Orphan +S: Maintained F: drivers/mmc/host/omap_hsmmc.c OMAP RANDOM NUMBER GENERATOR SUPPORT -- cgit v0.10.2 From abe1e05da365350ac282ba5f6831aae13d97074e Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Wed, 22 Aug 2012 13:21:01 -0400 Subject: mmc: dt: Add card-detection properties to core binding. Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/mmc.txt b/Documentation/devicetree/bindings/mmc/mmc.txt index 8a6811f..8e2e0ba 100644 --- a/Documentation/devicetree/bindings/mmc/mmc.txt +++ b/Documentation/devicetree/bindings/mmc/mmc.txt @@ -9,12 +9,17 @@ Interpreted by the OF core: Required properties: - bus-width: Number of data lines, can be <1>, <4>, or <8> +Card detection: +If no property below is supplied, standard SDHCI card detect is used. +Only one of the properties in this section should be supplied: + - broken-cd: There is no card detection available; polling must be used. + - cd-gpios: Specify GPIOs for card detection, see gpio binding + - non-removable: non-removable slot (like eMMC); assume always present. + Optional properties: -- cd-gpios: Specify GPIOs for card detection, see gpio binding - wp-gpios: Specify GPIOs for write protection, see gpio binding - cd-inverted: when present, polarity on the cd gpio line is inverted - wp-inverted: when present, polarity on the wp gpio line is inverted -- non-removable: non-removable slot (like eMMC) - max-frequency: maximum operating clock frequency Example: -- cgit v0.10.2 From b37f9d98f3d28ad98cc6c1894159312914f49190 Mon Sep 17 00:00:00 2001 From: Joseph Lo Date: Fri, 17 Aug 2012 15:04:31 +0800 Subject: mmc: tegra: convert to device tree support only When compiling a kernel that supports only Tegra30 and not Tegra20, the SDHCI driver will fail to compile since the of_match_device() failure fallback code unconditinally references soc_data_tegra20, which is only compiled in when Tegra20 support is enabled. In Kernel 3.7, both Tegra20 and Tegra30 will only support booting using device tree, Hence, there is never a need to fall back to using soc_data_tegra20 if of_mach_device() fails. Instead, make this case an error. This removes the reference to soc_data_tegra20, and hence solves the compile failure. Signed-off-by: Joseph Lo Tested-by: Stephen Warren Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-tegra.c b/drivers/mmc/host/sdhci-tegra.c index 0810ccc..436186c 100644 --- a/drivers/mmc/host/sdhci-tegra.c +++ b/drivers/mmc/host/sdhci-tegra.c @@ -257,10 +257,9 @@ static int __devinit sdhci_tegra_probe(struct platform_device *pdev) int rc; match = of_match_device(sdhci_tegra_dt_match, &pdev->dev); - if (match) - soc_data = match->data; - else - soc_data = &soc_data_tegra20; + if (!match) + return -EINVAL; + soc_data = match->data; host = sdhci_pltfm_init(pdev, soc_data->pdata); if (IS_ERR(host)) -- cgit v0.10.2 From 36c179a98a0755b98153e8f4ae08243fbfcd18ff Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Thu, 23 Aug 2012 20:31:48 +0900 Subject: mmc: dw_mmc: fixed a section mismatch in dw_mci_init_slot Fixed the below message. WARNING: drivers/mmc/host/built-in.o(.text+0x5ee8): Section mismatch in reference from the function dw_mci_probe() to the function .init.text:dw_mci_init_slot() The function dw_mci_probe() references the function __init dw_mci_init_slot(). This is often because dw_mci_probe lacks a __init annotation or the annotation of dw_mci_init_slot is wrong Signed-off-by: Jaehoon Chung Signed-off-by: Kyungmin Park Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index 92ec3eb..a54d3a3 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -21,7 +21,7 @@ #include #include "dw_mmc.h" -static int dw_mci_pltfm_probe(struct platform_device *pdev) +static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) { struct dw_mci *host; struct resource *regs; @@ -62,7 +62,7 @@ err_free: return ret; } -static int __exit dw_mci_pltfm_remove(struct platform_device *pdev) +static int __devexit dw_mci_pltfm_remove(struct platform_device *pdev) { struct dw_mci *host = platform_get_drvdata(pdev); diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index af40d22..36f98c0 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1767,7 +1767,7 @@ static void dw_mci_work_routine_card(struct work_struct *work) } } -static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id) +static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) { struct mmc_host *mmc; struct dw_mci_slot *slot; -- cgit v0.10.2 From 3339d1e33185798a45dbdb5ea6c0bec1c27ca5fd Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Thu, 23 Aug 2012 13:40:55 +0200 Subject: mmc: core: Do not rescan non-removable devices If MMC_CAP_NONREMOVABLE is set, only issue a detect job on init. Signed-off-by: Johan Rudholm Acked-by: Ulf Hansson Acked-by: Linus Walleij Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 835c9f0..af2c4d2 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2053,6 +2053,11 @@ void mmc_rescan(struct work_struct *work) if (host->rescan_disable) return; + /* If there is a non-removable card registered, only scan once */ + if ((host->caps & MMC_CAP_NONREMOVABLE) && host->rescan_entered) + return; + host->rescan_entered = 1; + mmc_bus_get(host); /* diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index f578a71..d5d9bd4 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -300,6 +300,7 @@ struct mmc_host { #endif int rescan_disable; /* disable card detection */ + int rescan_entered; /* used with nonremovable devices */ struct mmc_card *card; /* device attached to this host */ -- cgit v0.10.2 From 2ad0b2494f83d87046db2c4fe6256546d44b3160 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Wed, 29 Aug 2012 14:35:06 +0900 Subject: mmc: sdhci-s3c: fix checkpatch error and warnings This patch fixes the checkpatch error and warnings listed below: ERROR: return is not a function, parentheses are not required WARNING: Prefer pr_err(... to printk(KERN_ERR, ... WARNING: quoted string split across lines WARNING: line over 80 characters WARNING: braces {} are not necessary for single statement blocks Signed-off-by: Jingoo Han Acked-by: Jaehoon Chung Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index a50c205..00969ba 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -166,7 +166,7 @@ static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", src, rate, wanted, rate / div); - return (wanted - (rate / div)); + return wanted - (rate / div); } /** @@ -288,6 +288,7 @@ static unsigned int sdhci_cmu_get_min_clock(struct sdhci_host *host) static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) { struct sdhci_s3c *ourhost = to_s3c(host); + struct device *dev = &ourhost->pdev->dev; unsigned long timeout; u16 clk = 0; @@ -309,8 +310,8 @@ static void sdhci_cmu_set_clock(struct sdhci_host *host, unsigned int clock) while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) { if (timeout == 0) { - printk(KERN_ERR "%s: Internal clock never " - "stabilised.\n", mmc_hostname(host->mmc)); + dev_err(dev, "%s: Internal clock never stabilised.\n", + mmc_hostname(host->mmc)); return; } timeout--; @@ -404,7 +405,9 @@ static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc) 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 | IRQF_ONESHOT, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, dev_name(dev), sc) == 0) { int status = gpio_get_value(sc->ext_cd_gpio); if (pdata->ext_cd_gpio_invert) @@ -486,9 +489,8 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) snprintf(name, 14, "mmc_busclk.%d", ptr); clk = clk_get(dev, name); - if (IS_ERR(clk)) { + if (IS_ERR(clk)) continue; - } clks++; sc->clk_bus[ptr] = clk; -- cgit v0.10.2 From 6f478825158cbfa76bb976e7c9841456b9721a14 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 27 Aug 2012 11:53:42 +0530 Subject: mmc: mmc_spi: Use module_spi_driver module_spi_driver makes the code simpler by eliminating module_init and module_exit calls. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c index 273306c..a600eab 100644 --- a/drivers/mmc/host/mmc_spi.c +++ b/drivers/mmc/host/mmc_spi.c @@ -1532,20 +1532,7 @@ static struct spi_driver mmc_spi_driver = { .remove = __devexit_p(mmc_spi_remove), }; - -static int __init mmc_spi_init(void) -{ - return spi_register_driver(&mmc_spi_driver); -} -module_init(mmc_spi_init); - - -static void __exit mmc_spi_exit(void) -{ - spi_unregister_driver(&mmc_spi_driver); -} -module_exit(mmc_spi_exit); - +module_spi_driver(mmc_spi_driver); MODULE_AUTHOR("Mike Lavender, David Brownell, " "Hans-Peter Nilsson, Jan Nikitenko"); -- cgit v0.10.2 From acc696463d1aa8db0c9d20f567789e0cb2ae1de0 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 27 Aug 2012 11:57:02 +0530 Subject: mmc: sdhci-pci: Use module_pci_driver module_pci_driver makes the code simpler by eliminating module_init and module_exit calls. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 504da71..3f0794e 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -1476,24 +1476,7 @@ static struct pci_driver sdhci_driver = { }, }; -/*****************************************************************************\ - * * - * Driver init/exit * - * * -\*****************************************************************************/ - -static int __init sdhci_drv_init(void) -{ - return pci_register_driver(&sdhci_driver); -} - -static void __exit sdhci_drv_exit(void) -{ - pci_unregister_driver(&sdhci_driver); -} - -module_init(sdhci_drv_init); -module_exit(sdhci_drv_exit); +module_pci_driver(sdhci_driver); MODULE_AUTHOR("Pierre Ossman "); MODULE_DESCRIPTION("Secure Digital Host Controller Interface PCI driver"); -- cgit v0.10.2 From 350e3e00fbebf6e01855e0096b7fd41b2479e858 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 27 Aug 2012 11:59:17 +0530 Subject: mmc: dw_mmc-pci: Use module_pci_driver module_pci_driver makes the code simpler by eliminating module_init and module_exit calls. Signed-off-by: Sachin Kamat Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc-pci.c b/drivers/mmc/host/dw_mmc-pci.c index dc0d25a..f5ab03d 100644 --- a/drivers/mmc/host/dw_mmc-pci.c +++ b/drivers/mmc/host/dw_mmc-pci.c @@ -140,18 +140,7 @@ static struct pci_driver dw_mci_pci_driver = { }, }; -static int __init dw_mci_init(void) -{ - return pci_register_driver(&dw_mci_pci_driver); -} - -static void __exit dw_mci_exit(void) -{ - pci_unregister_driver(&dw_mci_pci_driver); -} - -module_init(dw_mci_init); -module_exit(dw_mci_exit); +module_pci_driver(dw_mci_pci_driver); MODULE_DESCRIPTION("DW Multimedia Card PCI Interface driver"); MODULE_AUTHOR("Shashidhar Hiremath "); -- cgit v0.10.2 From 0d4de8f5de2a642353834b183b16b367d39ff893 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 27 Aug 2012 12:12:11 +0530 Subject: mmc: via-sdmmc: Use module_pci_driver module_pci_driver makes the code simpler by eliminating module_init and module_exit calls. Signed-off-by: Sachin Kamat Acked-by: Harald Welte Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/via-sdmmc.c b/drivers/mmc/host/via-sdmmc.c index 4b83c43..f18bece 100644 --- a/drivers/mmc/host/via-sdmmc.c +++ b/drivers/mmc/host/via-sdmmc.c @@ -1337,21 +1337,7 @@ static struct pci_driver via_sd_driver = { .resume = via_sd_resume, }; -static int __init via_sd_drv_init(void) -{ - pr_info(DRV_NAME ": VIA SD/MMC Card Reader driver " - "(C) 2008 VIA Technologies, Inc.\n"); - - return pci_register_driver(&via_sd_driver); -} - -static void __exit via_sd_drv_exit(void) -{ - pci_unregister_driver(&via_sd_driver); -} - -module_init(via_sd_drv_init); -module_exit(via_sd_drv_exit); +module_pci_driver(via_sd_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("VIA Technologies Inc."); -- cgit v0.10.2 From 15e8a8e42966162c207bb97ed55c803bc437eeae Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Sun, 9 Sep 2012 22:56:48 -0400 Subject: mmc: slot-gpio: Fix missing assignment to ctx->ro_gpio mmc_gpio_request_ro() doesn't store the requested gpio in ctx->ro_gpio. As a result, subsequent calls to mmc_gpio_get_ro() will always fail with -ENOSYS because the gpio number isn't available to that function. Cc: stable Acked-by: Guennadi Liakhovetski Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/slot-gpio.c b/drivers/mmc/core/slot-gpio.c index 0582429..08c6b3d 100644 --- a/drivers/mmc/core/slot-gpio.c +++ b/drivers/mmc/core/slot-gpio.c @@ -100,7 +100,13 @@ int mmc_gpio_request_ro(struct mmc_host *host, unsigned int gpio) ctx = host->slot.handler_priv; - return gpio_request_one(gpio, GPIOF_DIR_IN, ctx->ro_label); + ret = gpio_request_one(gpio, GPIOF_DIR_IN, ctx->ro_label); + if (ret < 0) + return ret; + + ctx->ro_gpio = gpio; + + return 0; } EXPORT_SYMBOL(mmc_gpio_request_ro); -- cgit v0.10.2 From 8f63795c60ef5bc3dbfcbf19c1ac64ed79d23c62 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Wed, 19 Sep 2012 16:29:12 +0800 Subject: mmc: sdhci-pxav3: dt: Support "cd-gpios" property Tested on OLPC XO-4/MMP3, where the card detection pin for one of the controllers is a sideband GPIO. The third cell in the cd-gpios property controls whether the GPIO is active high/active low. (Also, pass host_caps2 through from platdata to the mmc host.) Acked-by: Haojian Zhuang [kliu5@marvell.com: Compile fix] Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index a553b18..c5ff124 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -24,12 +24,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "sdhci.h" #include "sdhci-pltfm.h" @@ -182,6 +184,7 @@ static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev) struct device_node *np = dev->of_node; u32 bus_width; u32 clk_delay_cycles; + enum of_gpio_flags gpio_flags; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) @@ -198,6 +201,10 @@ static struct sdhci_pxa_platdata *pxav3_get_mmc_pdata(struct device *dev) if (clk_delay_cycles > 0) pdata->clk_delay_cycles = clk_delay_cycles; + pdata->ext_cd_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &gpio_flags); + if (gpio_flags != OF_GPIO_ACTIVE_LOW) + pdata->host_caps2 |= MMC_CAP2_CD_ACTIVE_HIGH; + return pdata; } #else @@ -266,8 +273,19 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) host->quirks |= pdata->quirks; if (pdata->host_caps) host->mmc->caps |= pdata->host_caps; + if (pdata->host_caps2) + host->mmc->caps2 |= pdata->host_caps2; if (pdata->pm_caps) host->mmc->pm_caps |= pdata->pm_caps; + + if (gpio_is_valid(pdata->ext_cd_gpio)) { + ret = mmc_gpio_request_cd(host->mmc, pdata->ext_cd_gpio); + if (ret) { + dev_err(mmc_dev(host->mmc), + "failed to allocate card detect gpio\n"); + goto err_cd_req; + } + } } host->ops = &pxav3_sdhci_ops; @@ -285,6 +303,8 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) err_add_host: clk_disable_unprepare(clk); clk_put(clk); + mmc_gpio_free_cd(host->mmc); +err_cd_req: err_clk_get: sdhci_pltfm_free(pdev); kfree(pxa); @@ -296,11 +316,16 @@ static int __devexit sdhci_pxav3_remove(struct platform_device *pdev) struct sdhci_host *host = platform_get_drvdata(pdev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_pxa *pxa = pltfm_host->priv; + struct sdhci_pxa_platdata *pdata = pdev->dev.platform_data; sdhci_remove_host(host, 1); clk_disable_unprepare(pltfm_host->clk); clk_put(pltfm_host->clk); + + if (gpio_is_valid(pdata->ext_cd_gpio)) + mmc_gpio_free_cd(host->mmc); + sdhci_pltfm_free(pdev); kfree(pxa); diff --git a/include/linux/platform_data/pxa_sdhci.h b/include/linux/platform_data/pxa_sdhci.h index 51ad099..59acd98 100644 --- a/include/linux/platform_data/pxa_sdhci.h +++ b/include/linux/platform_data/pxa_sdhci.h @@ -49,6 +49,7 @@ struct sdhci_pxa_platdata { bool ext_cd_gpio_invert; unsigned int max_speed; unsigned int host_caps; + unsigned int host_caps2; unsigned int quirks; unsigned int pm_caps; }; -- cgit v0.10.2 From 1a41313e7f8c5b724a2e3ff66558dbe811844423 Mon Sep 17 00:00:00 2001 From: Kyungsik Lee Date: Fri, 3 Aug 2012 00:58:03 +0000 Subject: mmc: core: Remove bounce buffer in mmc_send_cxd_data() It is expected that Extended CSD register (the size of this register is larger than CID/CSD) will be referenced more frequently as more fields have been added to Extended CSD and it seems that it is not a good option to double the memory used. This patch is intended to avoid the use of bounce buffer for reading Extended CSD register in mmc_send_cxd_data(). It will provide a better performance gain by removing memcpy() overhead for a half KiB and a redundant bounce buffer allocated repeatedly at the cost of providing DMA-capable buffer from upper caller (but on-stack buffer is allowed with no performance gain). Signed-off-by: Kyungsik Lee Reviewed-by: Venkatraman S Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 0ed2cc5..225371a 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -230,6 +230,10 @@ mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode) return 0; } +/* + * NOTE: void *buf, caller for the buf is required to use DMA-capable + * buffer or on-stack buffer (with some overhead in callee). + */ static int mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, u32 opcode, void *buf, unsigned len) @@ -239,13 +243,19 @@ mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, struct mmc_data data = {0}; struct scatterlist sg; void *data_buf; + int is_on_stack; - /* dma onto stack is unsafe/nonportable, but callers to this - * routine normally provide temporary on-stack buffers ... - */ - data_buf = kmalloc(len, GFP_KERNEL); - if (data_buf == NULL) - return -ENOMEM; + is_on_stack = object_is_on_stack(buf); + if (is_on_stack) { + /* + * dma onto stack is unsafe/nonportable, but callers to this + * routine normally provide temporary on-stack buffers ... + */ + data_buf = kmalloc(len, GFP_KERNEL); + if (!data_buf) + return -ENOMEM; + } else + data_buf = buf; mrq.cmd = &cmd; mrq.data = &data; @@ -280,8 +290,10 @@ mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, mmc_wait_for_req(host, &mrq); - memcpy(buf, data_buf, len); - kfree(data_buf); + if (is_on_stack) { + memcpy(buf, data_buf, len); + kfree(data_buf); + } if (cmd.error) return cmd.error; @@ -294,24 +306,32 @@ mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, int mmc_send_csd(struct mmc_card *card, u32 *csd) { int ret, i; + u32 *csd_tmp; if (!mmc_host_is_spi(card->host)) return mmc_send_cxd_native(card->host, card->rca << 16, csd, MMC_SEND_CSD); - ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd, 16); + csd_tmp = kmalloc(16, GFP_KERNEL); + if (!csd_tmp) + return -ENOMEM; + + ret = mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd_tmp, 16); if (ret) - return ret; + goto err; for (i = 0;i < 4;i++) - csd[i] = be32_to_cpu(csd[i]); + csd[i] = be32_to_cpu(csd_tmp[i]); - return 0; +err: + kfree(csd_tmp); + return ret; } int mmc_send_cid(struct mmc_host *host, u32 *cid) { int ret, i; + u32 *cid_tmp; if (!mmc_host_is_spi(host)) { if (!host->card) @@ -320,14 +340,20 @@ int mmc_send_cid(struct mmc_host *host, u32 *cid) cid, MMC_SEND_CID); } - ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid, 16); + cid_tmp = kmalloc(16, GFP_KERNEL); + if (!cid_tmp) + return -ENOMEM; + + ret = mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid_tmp, 16); if (ret) - return ret; + goto err; for (i = 0;i < 4;i++) - cid[i] = be32_to_cpu(cid[i]); + cid[i] = be32_to_cpu(cid_tmp[i]); - return 0; +err: + kfree(cid_tmp); + return ret; } int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) -- cgit v0.10.2 From d8e2ac330f65bcf47e8894fe5331a7e8ee019c06 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Thu, 9 Aug 2012 09:32:21 +0000 Subject: mmc: sdio: Fix PM_SLEEP related build warnings Power management callbacks defined by SIMPLE_DEV_PM_OPS are only used if the PM_SLEEP Kconfig symbol has been defined. If not, the compiler will complain about them being unused. However, since the callback for this driver doesn't do anything it can just as well be dropped. Signed-off-by: Thierry Reding Acked-by: Rafael J. Wysocki Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 236842e..6bf6879 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -193,14 +193,7 @@ static int sdio_bus_remove(struct device *dev) } #ifdef CONFIG_PM - -static int pm_no_operation(struct device *dev) -{ - return 0; -} - static const struct dev_pm_ops sdio_bus_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(pm_no_operation, pm_no_operation) SET_RUNTIME_PM_OPS( pm_generic_runtime_suspend, pm_generic_runtime_resume, -- cgit v0.10.2 From 5413da811fbb11bc0482c92cbb8415073591d462 Mon Sep 17 00:00:00 2001 From: Matt Porter Date: Thu, 23 Aug 2012 01:09:35 +0000 Subject: mmc: davinci_mmc: convert to DMA engine API Removes use of the DaVinci EDMA private DMA API and replaces it with use of the DMA engine API. Signed-off-by: Matt Porter Tested-by: Koen Kooi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/davinci_mmc.c b/drivers/mmc/host/davinci_mmc.c index 7cf6c62..c5e1eeb 100644 --- a/drivers/mmc/host/davinci_mmc.c +++ b/drivers/mmc/host/davinci_mmc.c @@ -30,11 +30,12 @@ #include #include #include +#include #include +#include #include #include -#include /* * Register Definitions @@ -200,21 +201,13 @@ struct mmc_davinci_host { u32 bytes_left; u32 rxdma, txdma; + struct dma_chan *dma_tx; + struct dma_chan *dma_rx; bool use_dma; bool do_dma; bool sdio_int; bool active_request; - /* Scatterlist DMA uses one or more parameter RAM entries: - * the main one (associated with rxdma or txdma) plus zero or - * more links. The entries for a given transfer differ only - * by memory buffer (address, length) and link field. - */ - struct edmacc_param tx_template; - struct edmacc_param rx_template; - unsigned n_link; - u32 links[MAX_NR_SG - 1]; - /* For PIO we walk scatterlists one segment at a time. */ unsigned int sg_len; struct scatterlist *sg; @@ -410,153 +403,74 @@ static void mmc_davinci_start_command(struct mmc_davinci_host *host, static void davinci_abort_dma(struct mmc_davinci_host *host) { - int sync_dev; + struct dma_chan *sync_dev; if (host->data_dir == DAVINCI_MMC_DATADIR_READ) - sync_dev = host->rxdma; + sync_dev = host->dma_rx; else - sync_dev = host->txdma; - - edma_stop(sync_dev); - edma_clean_channel(sync_dev); -} - -static void -mmc_davinci_xfer_done(struct mmc_davinci_host *host, struct mmc_data *data); - -static void mmc_davinci_dma_cb(unsigned channel, u16 ch_status, void *data) -{ - if (DMA_COMPLETE != ch_status) { - struct mmc_davinci_host *host = data; - - /* Currently means: DMA Event Missed, or "null" transfer - * request was seen. In the future, TC errors (like bad - * addresses) might be presented too. - */ - dev_warn(mmc_dev(host->mmc), "DMA %s error\n", - (host->data->flags & MMC_DATA_WRITE) - ? "write" : "read"); - host->data->error = -EIO; - mmc_davinci_xfer_done(host, host->data); - } -} - -/* Set up tx or rx template, to be modified and updated later */ -static void __init mmc_davinci_dma_setup(struct mmc_davinci_host *host, - bool tx, struct edmacc_param *template) -{ - unsigned sync_dev; - const u16 acnt = 4; - const u16 bcnt = rw_threshold >> 2; - const u16 ccnt = 0; - u32 src_port = 0; - u32 dst_port = 0; - s16 src_bidx, dst_bidx; - s16 src_cidx, dst_cidx; - - /* - * A-B Sync transfer: each DMA request is for one "frame" of - * rw_threshold bytes, broken into "acnt"-size chunks repeated - * "bcnt" times. Each segment needs "ccnt" such frames; since - * we tell the block layer our mmc->max_seg_size limit, we can - * trust (later) that it's within bounds. - * - * The FIFOs are read/written in 4-byte chunks (acnt == 4) and - * EDMA will optimize memory operations to use larger bursts. - */ - if (tx) { - sync_dev = host->txdma; - - /* src_prt, ccnt, and link to be set up later */ - src_bidx = acnt; - src_cidx = acnt * bcnt; - - dst_port = host->mem_res->start + DAVINCI_MMCDXR; - dst_bidx = 0; - dst_cidx = 0; - } else { - sync_dev = host->rxdma; - - src_port = host->mem_res->start + DAVINCI_MMCDRR; - src_bidx = 0; - src_cidx = 0; - - /* dst_prt, ccnt, and link to be set up later */ - dst_bidx = acnt; - dst_cidx = acnt * bcnt; - } - - /* - * We can't use FIFO mode for the FIFOs because MMC FIFO addresses - * are not 256-bit (32-byte) aligned. So we use INCR, and the W8BIT - * parameter is ignored. - */ - edma_set_src(sync_dev, src_port, INCR, W8BIT); - edma_set_dest(sync_dev, dst_port, INCR, W8BIT); + sync_dev = host->dma_tx; - edma_set_src_index(sync_dev, src_bidx, src_cidx); - edma_set_dest_index(sync_dev, dst_bidx, dst_cidx); - - edma_set_transfer_params(sync_dev, acnt, bcnt, ccnt, 8, ABSYNC); - - edma_read_slot(sync_dev, template); - - /* don't bother with irqs or chaining */ - template->opt |= EDMA_CHAN_SLOT(sync_dev) << 12; + dmaengine_terminate_all(sync_dev); } -static void mmc_davinci_send_dma_request(struct mmc_davinci_host *host, +static int mmc_davinci_send_dma_request(struct mmc_davinci_host *host, struct mmc_data *data) { - struct edmacc_param *template; - int channel, slot; - unsigned link; - struct scatterlist *sg; - unsigned sg_len; - unsigned bytes_left = host->bytes_left; - const unsigned shift = ffs(rw_threshold) - 1; + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + int ret = 0; if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) { - template = &host->tx_template; - channel = host->txdma; + struct dma_slave_config dma_tx_conf = { + .direction = DMA_MEM_TO_DEV, + .dst_addr = host->mem_res->start + DAVINCI_MMCDXR, + .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .dst_maxburst = + rw_threshold / DMA_SLAVE_BUSWIDTH_4_BYTES, + }; + chan = host->dma_tx; + dmaengine_slave_config(host->dma_tx, &dma_tx_conf); + + desc = dmaengine_prep_slave_sg(host->dma_tx, + data->sg, + host->sg_len, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_dbg(mmc_dev(host->mmc), + "failed to allocate DMA TX descriptor"); + ret = -1; + goto out; + } } else { - template = &host->rx_template; - channel = host->rxdma; - } - - /* We know sg_len and ccnt will never be out of range because - * we told the mmc layer which in turn tells the block layer - * to ensure that it only hands us one scatterlist segment - * per EDMA PARAM entry. Update the PARAM - * entries needed for each segment of this scatterlist. - */ - for (slot = channel, link = 0, sg = data->sg, sg_len = host->sg_len; - sg_len-- != 0 && bytes_left; - sg = sg_next(sg), slot = host->links[link++]) { - u32 buf = sg_dma_address(sg); - unsigned count = sg_dma_len(sg); - - template->link_bcntrld = sg_len - ? (EDMA_CHAN_SLOT(host->links[link]) << 5) - : 0xffff; - - if (count > bytes_left) - count = bytes_left; - bytes_left -= count; - - if (host->data_dir == DAVINCI_MMC_DATADIR_WRITE) - template->src = buf; - else - template->dst = buf; - template->ccnt = count >> shift; - - edma_write_slot(slot, template); + struct dma_slave_config dma_rx_conf = { + .direction = DMA_DEV_TO_MEM, + .src_addr = host->mem_res->start + DAVINCI_MMCDRR, + .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .src_maxburst = + rw_threshold / DMA_SLAVE_BUSWIDTH_4_BYTES, + }; + chan = host->dma_rx; + dmaengine_slave_config(host->dma_rx, &dma_rx_conf); + + desc = dmaengine_prep_slave_sg(host->dma_rx, + data->sg, + host->sg_len, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_dbg(mmc_dev(host->mmc), + "failed to allocate DMA RX descriptor"); + ret = -1; + goto out; + } } - if (host->version == MMC_CTLR_VERSION_2) - edma_clear_event(channel); + dmaengine_submit(desc); + dma_async_issue_pending(chan); - edma_start(channel); +out: + return ret; } static int mmc_davinci_start_dma_transfer(struct mmc_davinci_host *host, @@ -564,6 +478,7 @@ static int mmc_davinci_start_dma_transfer(struct mmc_davinci_host *host, { int i; int mask = rw_threshold - 1; + int ret = 0; host->sg_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, ((data->flags & MMC_DATA_WRITE) @@ -583,70 +498,48 @@ static int mmc_davinci_start_dma_transfer(struct mmc_davinci_host *host, } host->do_dma = 1; - mmc_davinci_send_dma_request(host, data); + ret = mmc_davinci_send_dma_request(host, data); - return 0; + return ret; } static void __init_or_module davinci_release_dma_channels(struct mmc_davinci_host *host) { - unsigned i; - if (!host->use_dma) return; - for (i = 0; i < host->n_link; i++) - edma_free_slot(host->links[i]); - - edma_free_channel(host->txdma); - edma_free_channel(host->rxdma); + dma_release_channel(host->dma_tx); + dma_release_channel(host->dma_rx); } static int __init davinci_acquire_dma_channels(struct mmc_davinci_host *host) { - u32 link_size; - int r, i; - - /* Acquire master DMA write channel */ - r = edma_alloc_channel(host->txdma, mmc_davinci_dma_cb, host, - EVENTQ_DEFAULT); - if (r < 0) { - dev_warn(mmc_dev(host->mmc), "alloc %s channel err %d\n", - "tx", r); - return r; - } - mmc_davinci_dma_setup(host, true, &host->tx_template); - - /* Acquire master DMA read channel */ - r = edma_alloc_channel(host->rxdma, mmc_davinci_dma_cb, host, - EVENTQ_DEFAULT); - if (r < 0) { - dev_warn(mmc_dev(host->mmc), "alloc %s channel err %d\n", - "rx", r); - goto free_master_write; + int r; + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + host->dma_tx = + dma_request_channel(mask, edma_filter_fn, &host->txdma); + if (!host->dma_tx) { + dev_err(mmc_dev(host->mmc), "Can't get dma_tx channel\n"); + return -ENODEV; } - mmc_davinci_dma_setup(host, false, &host->rx_template); - /* Allocate parameter RAM slots, which will later be bound to a - * channel as needed to handle a scatterlist. - */ - link_size = min_t(unsigned, host->nr_sg, ARRAY_SIZE(host->links)); - for (i = 0; i < link_size; i++) { - r = edma_alloc_slot(EDMA_CTLR(host->txdma), EDMA_SLOT_ANY); - if (r < 0) { - dev_dbg(mmc_dev(host->mmc), "dma PaRAM alloc --> %d\n", - r); - break; - } - host->links[i] = r; + host->dma_rx = + dma_request_channel(mask, edma_filter_fn, &host->rxdma); + if (!host->dma_rx) { + dev_err(mmc_dev(host->mmc), "Can't get dma_rx channel\n"); + r = -ENODEV; + goto free_master_write; } - host->n_link = i; return 0; free_master_write: - edma_free_channel(host->txdma); + dma_release_channel(host->dma_tx); return r; } @@ -1359,7 +1252,7 @@ static int __init davinci_mmcsd_probe(struct platform_device *pdev) * Each hw_seg uses one EDMA parameter RAM slot, always one * channel and then usually some linked slots. */ - mmc->max_segs = 1 + host->n_link; + mmc->max_segs = MAX_NR_SG; /* EDMA limit per hw segment (one or two MBytes) */ mmc->max_seg_size = MAX_CCNT * rw_threshold; -- cgit v0.10.2 From bec9d4e5939987053169a9bb48fc58b6a2d3e237 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Mon, 17 Sep 2012 16:45:10 +0800 Subject: mmc: sdhci: Test cd-gpio instead of SDHCI presence when probing Previously to this patch, an SDHCI platform that uses a GPIO for card detection instead of the internal SDHCI_CARD_PRESENT bit on the presence register would fail to detect a new card. Some drivers worked around this in various ways: esdhc-imx defines an IO accessor to fake the presence bit being true, s3c turns on polling (which stops the SDHCI driver from checking the bit) after a card's inserted. But none of this should be necessary; the real fix is to check whether we're using a GPIO and avoid relying on the presence bit if so, as this patch implements. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d98b199..0e15c79 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "sdhci.h" @@ -1293,6 +1294,13 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT; + /* If we're using a cd-gpio, testing the presence bit might fail. */ + if (!present) { + int ret = mmc_gpio_get_cd(host->mmc); + if (ret > 0) + present = true; + } + if (!present || host->flags & SDHCI_DEVICE_DEAD) { host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); -- cgit v0.10.2 From 950d56acce5d401f477b91d0177605b543d63d07 Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Mon, 17 Sep 2012 08:42:02 +0000 Subject: mmc: support BKOPS feature for eMMC Enable eMMC background operations (BKOPS) feature. If URGENT_BKOPS is set after a response, note that BKOPS are required. Immediately run BKOPS if required. Read/write operations should be requested during BKOPS(LEVEL-1), then issue HPI to interrupt the ongoing BKOPS and service the foreground operation. (This patch only controls the LEVEL2/3.) When repeating the writing 1GB data, at a certain time, performance is decreased. At that time, card triggers the Level-3 or Level-2. After running bkops, performance is recovered. Future considerations: * Check BKOPS_LEVEL=1 and start BKOPS in a preventive manner. * Interrupt ongoing BKOPS before powering off the card. * How do we get BKOPS_STATUS value (periodically send ext_csd command)? * If using periodic bkops, also consider runtime_pm control. Signed-off-by: Jaehoon Chung Signed-off-by: Kyungmin Park Signed-off-by: Konstantin Dorfman Reviewed-by: Maya Erez Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index af2c4d2..044cd01 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,12 @@ #include "sd_ops.h" #include "sdio_ops.h" +/* + * Background operations can take a long time, depending on the housekeeping + * operations the card has to perform. + */ +#define MMC_BKOPS_MAX_TIMEOUT (4 * 60 * 1000) /* max time to wait in ms */ + static struct workqueue_struct *workqueue; static const unsigned freqs[] = { 400000, 300000, 200000, 100000 }; @@ -245,6 +252,70 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) host->ops->request(host, mrq); } +/** + * mmc_start_bkops - start BKOPS for supported cards + * @card: MMC card to start BKOPS + * @form_exception: A flag to indicate if this function was + * called due to an exception raised by the card + * + * Start background operations whenever requested. + * When the urgent BKOPS bit is set in a R1 command response + * then background operations should be started immediately. +*/ +void mmc_start_bkops(struct mmc_card *card, bool from_exception) +{ + int err; + int timeout; + bool use_busy_signal; + + BUG_ON(!card); + + if (!card->ext_csd.bkops_en || mmc_card_doing_bkops(card)) + return; + + err = mmc_read_bkops_status(card); + if (err) { + pr_err("%s: Failed to read bkops status: %d\n", + mmc_hostname(card->host), err); + return; + } + + if (!card->ext_csd.raw_bkops_status) + return; + + if (card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2 && + from_exception) + return; + + mmc_claim_host(card->host); + if (card->ext_csd.raw_bkops_status >= EXT_CSD_BKOPS_LEVEL_2) { + timeout = MMC_BKOPS_MAX_TIMEOUT; + use_busy_signal = true; + } else { + timeout = 0; + use_busy_signal = false; + } + + err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BKOPS_START, 1, timeout, use_busy_signal); + if (err) { + pr_warn("%s: Error %d starting bkops\n", + mmc_hostname(card->host), err); + goto out; + } + + /* + * For urgent bkops status (LEVEL_2 and more) + * bkops executed synchronously, otherwise + * the operation is in progress + */ + if (!use_busy_signal) + mmc_card_set_doing_bkops(card); +out: + mmc_release_host(card->host); +} +EXPORT_SYMBOL(mmc_start_bkops); + static void mmc_wait_done(struct mmc_request *mrq) { complete(&mrq->completion); @@ -354,6 +425,14 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (host->areq) { mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); + /* + * Check BKOPS urgency for each R1 response + */ + if (host->card && mmc_card_mmc(host->card) && + ((mmc_resp_type(host->areq->mrq->cmd) == MMC_RSP_R1) || + (mmc_resp_type(host->areq->mrq->cmd) == MMC_RSP_R1B)) && + (host->areq->mrq->cmd->resp[0] & R1_EXCEPTION_EVENT)) + mmc_start_bkops(host->card, true); } if (!err && areq) @@ -398,7 +477,7 @@ EXPORT_SYMBOL(mmc_wait_for_req); * @card: the MMC card associated with the HPI transfer * * Issued High Priority Interrupt, and check for card status - * util out-of prg-state. + * until out-of prg-state. */ int mmc_interrupt_hpi(struct mmc_card *card) { @@ -490,6 +569,64 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries EXPORT_SYMBOL(mmc_wait_for_cmd); /** + * mmc_stop_bkops - stop ongoing BKOPS + * @card: MMC card to check BKOPS + * + * Send HPI command to stop ongoing background operations to + * allow rapid servicing of foreground operations, e.g. read/ + * writes. Wait until the card comes out of the programming state + * to avoid errors in servicing read/write requests. + */ +int mmc_stop_bkops(struct mmc_card *card) +{ + int err = 0; + + BUG_ON(!card); + err = mmc_interrupt_hpi(card); + + /* + * If err is EINVAL, we can't issue an HPI. + * It should complete the BKOPS. + */ + if (!err || (err == -EINVAL)) { + mmc_card_clr_doing_bkops(card); + err = 0; + } + + return err; +} +EXPORT_SYMBOL(mmc_stop_bkops); + +int mmc_read_bkops_status(struct mmc_card *card) +{ + int err; + u8 *ext_csd; + + /* + * In future work, we should consider storing the entire ext_csd. + */ + ext_csd = kmalloc(512, GFP_KERNEL); + if (!ext_csd) { + pr_err("%s: could not allocate buffer to receive the ext_csd.\n", + mmc_hostname(card->host)); + return -ENOMEM; + } + + mmc_claim_host(card->host); + err = mmc_send_ext_csd(card, ext_csd); + mmc_release_host(card->host); + if (err) + goto out; + + card->ext_csd.raw_bkops_status = ext_csd[EXT_CSD_BKOPS_STATUS]; + card->ext_csd.raw_exception_status = ext_csd[EXT_CSD_EXP_EVENTS_STATUS]; +out: + kfree(ext_csd); + return err; +} +EXPORT_SYMBOL(mmc_read_bkops_status); + +/** * mmc_set_data_timeout - set the timeout for a data command * @data: data phase for command * @card: the MMC card associated with the data transfer @@ -2333,9 +2470,14 @@ int mmc_suspend_host(struct mmc_host *host) mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { - - if (host->bus_ops->suspend) + if (host->bus_ops->suspend) { + if (mmc_card_doing_bkops(host->card)) { + err = mmc_stop_bkops(host->card); + if (err) + goto out; + } err = host->bus_ops->suspend(host); + } if (err == -ENOSYS || !host->bus_ops->resume) { /* @@ -2417,11 +2559,21 @@ int mmc_pm_notify(struct notifier_block *notify_block, struct mmc_host *host = container_of( notify_block, struct mmc_host, pm_notify); unsigned long flags; - + int err = 0; switch (mode) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: + if (host->card && mmc_card_mmc(host->card) && + mmc_card_doing_bkops(host->card)) { + err = mmc_stop_bkops(host->card); + if (err) { + pr_err("%s: didn't stop bkops\n", + mmc_hostname(host)); + return err; + } + mmc_card_clr_doing_bkops(host->card); + } spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 1; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 396b258..7509de1 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -463,6 +463,17 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } if (card->ext_csd.rev >= 5) { + /* check whether the eMMC card supports BKOPS */ + if (ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1) { + card->ext_csd.bkops = 1; + card->ext_csd.bkops_en = ext_csd[EXT_CSD_BKOPS_EN]; + card->ext_csd.raw_bkops_status = + ext_csd[EXT_CSD_BKOPS_STATUS]; + if (!card->ext_csd.bkops_en) + pr_info("%s: BKOPS_EN bit is not set\n", + mmc_hostname(card->host)); + } + /* check whether the eMMC card supports HPI */ if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x1) { card->ext_csd.hpi = 1; diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 225371a..a0e1720 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -393,18 +393,19 @@ int mmc_spi_set_crc(struct mmc_host *host, int use_crc) } /** - * mmc_switch - modify EXT_CSD register + * __mmc_switch - modify EXT_CSD register * @card: the MMC card associated with the data transfer * @set: cmd set values * @index: EXT_CSD register index * @value: value to program into EXT_CSD register * @timeout_ms: timeout (ms) for operation performed by register write, * timeout of zero implies maximum possible timeout + * @use_busy_signal: use the busy signal as response type * * Modifies the EXT_CSD register for selected card. */ -int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, - unsigned int timeout_ms) +int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, + unsigned int timeout_ms, bool use_busy_signal) { int err; struct mmc_command cmd = {0}; @@ -418,13 +419,23 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, (index << 16) | (value << 8) | set; - cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + cmd.flags = MMC_CMD_AC; + if (use_busy_signal) + cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B; + else + cmd.flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1; + + cmd.cmd_timeout_ms = timeout_ms; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); if (err) return err; + /* No need to check card status in case of unblocking command */ + if (!use_busy_signal) + return 0; + /* Must check status to be sure of no errors */ do { err = mmc_send_status(card, &status); @@ -449,6 +460,13 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, return 0; } +EXPORT_SYMBOL_GPL(__mmc_switch); + +int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, + unsigned int timeout_ms) +{ + return __mmc_switch(card, set, index, value, timeout_ms, true); +} EXPORT_SYMBOL_GPL(mmc_switch); int mmc_send_status(struct mmc_card *card, u32 *status) diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 4b27f9f..78cc3be 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -76,10 +76,13 @@ struct mmc_ext_csd { bool hpi_en; /* HPI enablebit */ bool hpi; /* HPI support bit */ unsigned int hpi_cmd; /* cmd used as HPI */ + bool bkops; /* background support bit */ + bool bkops_en; /* background enable bit */ unsigned int data_sector_size; /* 512 bytes or 4KB */ unsigned int data_tag_unit_size; /* DATA TAG UNIT size */ unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; + u8 raw_exception_status; /* 53 */ u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ @@ -93,6 +96,7 @@ struct mmc_ext_csd { u8 raw_sec_erase_mult; /* 230 */ u8 raw_sec_feature_support;/* 231 */ u8 raw_trim_mult; /* 232 */ + u8 raw_bkops_status; /* 246 */ u8 raw_sectors[4]; /* 212 - 4 bytes */ unsigned int feature_support; @@ -226,6 +230,7 @@ struct mmc_card { #define MMC_CARD_REMOVED (1<<7) /* card has been removed */ #define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */ #define MMC_STATE_SLEEP (1<<9) /* card is in sleep state */ +#define MMC_STATE_DOING_BKOPS (1<<10) /* card is doing BKOPS */ unsigned int quirks; /* card quirks */ #define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize */ @@ -393,6 +398,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC) #define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED)) #define mmc_card_is_sleep(c) ((c)->state & MMC_STATE_SLEEP) +#define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -405,7 +411,9 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC) #define mmc_card_set_removed(c) ((c)->state |= MMC_CARD_REMOVED) #define mmc_card_set_sleep(c) ((c)->state |= MMC_STATE_SLEEP) +#define mmc_card_set_doing_bkops(c) ((c)->state |= MMC_STATE_DOING_BKOPS) +#define mmc_card_clr_doing_bkops(c) ((c)->state &= ~MMC_STATE_DOING_BKOPS) #define mmc_card_clr_sleep(c) ((c)->state &= ~MMC_STATE_SLEEP) /* * Quirk add/remove for MMC products. diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 1b431c7..9b9cdaf 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -134,6 +134,8 @@ struct mmc_host; struct mmc_card; struct mmc_async_req; +extern int mmc_stop_bkops(struct mmc_card *); +extern int mmc_read_bkops_status(struct mmc_card *); extern struct mmc_async_req *mmc_start_req(struct mmc_host *, struct mmc_async_req *, int *); extern int mmc_interrupt_hpi(struct mmc_card *); @@ -142,6 +144,8 @@ extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *); extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, struct mmc_command *, int); +extern void mmc_start_bkops(struct mmc_card *card, bool from_exception); +extern int __mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int, bool); extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); #define MMC_ERASE_ARG 0x00000000 diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index d425cab..01e4b39 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -139,6 +139,7 @@ static inline bool mmc_op_multi(u32 opcode) #define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ #define R1_READY_FOR_DATA (1 << 8) /* sx, a */ #define R1_SWITCH_ERROR (1 << 7) /* sx, c */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sx, a */ #define R1_APP_CMD (1 << 5) /* sr, c */ #define R1_STATE_IDLE 0 @@ -274,12 +275,15 @@ struct _mmc_csd { #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ +#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ #define EXT_CSD_HPI_MGMT 161 /* R/W */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_BKOPS_EN 163 /* R/W */ +#define EXT_CSD_BKOPS_START 164 /* W */ #define EXT_CSD_SANITIZE_START 165 /* W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ #define EXT_CSD_BOOT_WP 173 /* R/W */ @@ -313,11 +317,13 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_200_360 237 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_BKOPS_STATUS 246 /* RO */ #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ #define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ +#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */ #define EXT_CSD_HPI_FEATURES 503 /* RO */ /* @@ -378,6 +384,19 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_8BIT_SHIFT 4 #define EXT_CSD_PWR_CL_4BIT_SHIFT 0 /* + * EXCEPTION_EVENT_STATUS field + */ +#define EXT_CSD_URGENT_BKOPS BIT(0) +#define EXT_CSD_DYNCAP_NEEDED BIT(1) +#define EXT_CSD_SYSPOOL_EXHAUSTED BIT(2) +#define EXT_CSD_PACKED_FAILURE BIT(3) + +/* + * BKOPS status level + */ +#define EXT_CSD_BKOPS_LEVEL_2 0x2 + +/* * MMC_SWITCH access modes */ -- cgit v0.10.2 From 4a90920c6b39a3af26470cfc26b8e5ec9c4e7f3c Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:35 +0000 Subject: mmc: dw_mmc: convert copy of struct device in struct dw_mci to a reference The 'struct dw_mci' maintains a copy of the pdev->dev instance instead of maintaining a reference to that 'struct device' instance. Any resource allocated using the device resource management kernel API with the instance of 'struct device' in 'struct dw_mci' is then incorrect. Fix this by converting the copy of 'struct device' in 'struct dw_mci' to a reference. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc-pci.c b/drivers/mmc/host/dw_mmc-pci.c index f5ab03d..edb37e9 100644 --- a/drivers/mmc/host/dw_mmc-pci.c +++ b/drivers/mmc/host/dw_mmc-pci.c @@ -59,7 +59,7 @@ static int __devinit dw_mci_pci_probe(struct pci_dev *pdev, host->irq = pdev->irq; host->irq_flags = IRQF_SHARED; - host->dev = pdev->dev; + host->dev = &pdev->dev; host->pdata = &pci_board_data; host->regs = pci_iomap(pdev, PCI_BAR_NO, COMPLETE_BAR); diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index a54d3a3..528d37b 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -43,7 +43,7 @@ static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) goto err_free; } - host->dev = pdev->dev; + host->dev = &pdev->dev; host->irq_flags = 0; host->pdata = pdev->dev.platform_data; ret = -ENOMEM; diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 36f98c0..a18e73c 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -266,7 +266,7 @@ static void dw_mci_start_command(struct dw_mci *host, struct mmc_command *cmd, u32 cmd_flags) { host->cmd = cmd; - dev_vdbg(&host->dev, + dev_vdbg(host->dev, "start command: ARGR=0x%08x CMDR=0x%08x\n", cmd->arg, cmd_flags); @@ -308,7 +308,7 @@ static void dw_mci_dma_cleanup(struct dw_mci *host) if (data) if (!data->host_cookie) - dma_unmap_sg(&host->dev, + dma_unmap_sg(host->dev, data->sg, data->sg_len, dw_mci_get_dma_dir(data)); @@ -334,7 +334,7 @@ static void dw_mci_idmac_complete_dma(struct dw_mci *host) { struct mmc_data *data = host->data; - dev_vdbg(&host->dev, "DMA complete\n"); + dev_vdbg(host->dev, "DMA complete\n"); host->dma_ops->cleanup(host); @@ -414,13 +414,13 @@ static int dw_mci_idmac_init(struct dw_mci *host) dma_support = (mci_readl(host, HCON) >> 16) & 0x3; if (!dma_support || dma_support > 2) { - dev_err(&host->dev, + dev_err(host->dev, "Host Controller does not support IDMA Tx.\n"); host->dma_ops = NULL; return -ENODEV; } - dev_info(&host->dev, "Using internal DMA controller.\n"); + dev_info(host->dev, "Using internal DMA controller.\n"); /* Forward link the descriptor list */ for (i = 0, p = host->sg_cpu; i < host->ring_size - 1; i++, p++) @@ -476,7 +476,7 @@ static int dw_mci_pre_dma_transfer(struct dw_mci *host, return -EINVAL; } - sg_len = dma_map_sg(&host->dev, + sg_len = dma_map_sg(host->dev, data->sg, data->sg_len, dw_mci_get_dma_dir(data)); @@ -519,7 +519,7 @@ static void dw_mci_post_req(struct mmc_host *mmc, return; if (data->host_cookie) - dma_unmap_sg(&slot->host->dev, + dma_unmap_sg(slot->host->dev, data->sg, data->sg_len, dw_mci_get_dma_dir(data)); @@ -545,7 +545,7 @@ static int dw_mci_submit_data_dma(struct dw_mci *host, struct mmc_data *data) host->using_dma = 1; - dev_vdbg(&host->dev, + dev_vdbg(host->dev, "sd sg_cpu: %#lx sg_dma: %#lx sg_len: %d\n", (unsigned long)host->sg_cpu, (unsigned long)host->sg_dma, sg_len); @@ -939,12 +939,12 @@ static void dw_mci_request_end(struct dw_mci *host, struct mmc_request *mrq) slot = list_entry(host->queue.next, struct dw_mci_slot, queue_node); list_del(&slot->queue_node); - dev_vdbg(&host->dev, "list not empty: %s is next\n", + dev_vdbg(host->dev, "list not empty: %s is next\n", mmc_hostname(slot->mmc)); host->state = STATE_SENDING_CMD; dw_mci_start_request(host, slot); } else { - dev_vdbg(&host->dev, "list empty\n"); + dev_vdbg(host->dev, "list empty\n"); host->state = STATE_IDLE; } @@ -1083,7 +1083,7 @@ static void dw_mci_tasklet_func(unsigned long priv) data->bytes_xfered = 0; data->error = -ETIMEDOUT; } else { - dev_err(&host->dev, + dev_err(host->dev, "data FIFO error " "(status=%08x)\n", status); @@ -1772,7 +1772,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) struct mmc_host *mmc; struct dw_mci_slot *slot; - mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), &host->dev); + mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev); if (!mmc) return -ENOMEM; @@ -1884,10 +1884,10 @@ static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id) static void dw_mci_init_dma(struct dw_mci *host) { /* Alloc memory for sg translation */ - host->sg_cpu = dma_alloc_coherent(&host->dev, PAGE_SIZE, + host->sg_cpu = dma_alloc_coherent(host->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL); if (!host->sg_cpu) { - dev_err(&host->dev, "%s: could not alloc DMA memory\n", + dev_err(host->dev, "%s: could not alloc DMA memory\n", __func__); goto no_dma; } @@ -1903,12 +1903,12 @@ static void dw_mci_init_dma(struct dw_mci *host) if (host->dma_ops->init && host->dma_ops->start && host->dma_ops->stop && host->dma_ops->cleanup) { if (host->dma_ops->init(host)) { - dev_err(&host->dev, "%s: Unable to initialize " + dev_err(host->dev, "%s: Unable to initialize " "DMA Controller.\n", __func__); goto no_dma; } } else { - dev_err(&host->dev, "DMA initialization not found.\n"); + dev_err(host->dev, "DMA initialization not found.\n"); goto no_dma; } @@ -1916,7 +1916,7 @@ static void dw_mci_init_dma(struct dw_mci *host) return; no_dma: - dev_info(&host->dev, "Using PIO mode.\n"); + dev_info(host->dev, "Using PIO mode.\n"); host->use_dma = 0; return; } @@ -1948,19 +1948,19 @@ int dw_mci_probe(struct dw_mci *host) u32 fifo_size; if (!host->pdata || !host->pdata->init) { - dev_err(&host->dev, + dev_err(host->dev, "Platform data must supply init function\n"); return -ENODEV; } if (!host->pdata->select_slot && host->pdata->num_slots > 1) { - dev_err(&host->dev, + dev_err(host->dev, "Platform data must supply select_slot function\n"); return -ENODEV; } if (!host->pdata->bus_hz) { - dev_err(&host->dev, + dev_err(host->dev, "Platform data must supply bus speed\n"); return -ENODEV; } @@ -1998,7 +1998,7 @@ int dw_mci_probe(struct dw_mci *host) } /* Reset all blocks */ - if (!mci_wait_reset(&host->dev, host)) + if (!mci_wait_reset(host->dev, host)) return -ENODEV; host->dma_ops = host->pdata->dma_ops; @@ -2065,7 +2065,7 @@ int dw_mci_probe(struct dw_mci *host) * Need to check the version-id and set data-offset for DATA register. */ host->verid = SDMMC_GET_VERID(mci_readl(host, VERID)); - dev_info(&host->dev, "Version ID is %04x\n", host->verid); + dev_info(host->dev, "Version ID is %04x\n", host->verid); if (host->verid < DW_MMC_240A) host->data_offset = DATA_OFFSET; @@ -2082,12 +2082,12 @@ int dw_mci_probe(struct dw_mci *host) DW_MCI_ERROR_FLAGS | SDMMC_INT_CD); mci_writel(host, CTRL, SDMMC_CTRL_INT_ENABLE); /* Enable mci interrupt */ - dev_info(&host->dev, "DW MMC controller at irq %d, " + dev_info(host->dev, "DW MMC controller at irq %d, " "%d bit host data width, " "%u deep fifo\n", host->irq, width, fifo_size); if (host->quirks & DW_MCI_QUIRK_IDMAC_DTO) - dev_info(&host->dev, "Internal DMAC interrupt fix enabled.\n"); + dev_info(host->dev, "Internal DMAC interrupt fix enabled.\n"); return 0; @@ -2106,7 +2106,7 @@ err_workqueue: err_dmaunmap: if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); - dma_free_coherent(&host->dev, PAGE_SIZE, + dma_free_coherent(host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); if (host->vmmc) { @@ -2125,7 +2125,7 @@ void dw_mci_remove(struct dw_mci *host) mci_writel(host, INTMASK, 0); /* disable all mmc interrupt first */ for (i = 0; i < host->num_slots; i++) { - dev_dbg(&host->dev, "remove slot %d\n", i); + dev_dbg(host->dev, "remove slot %d\n", i); if (host->slot[i]) dw_mci_cleanup_slot(host->slot[i], i); } @@ -2136,7 +2136,7 @@ void dw_mci_remove(struct dw_mci *host) free_irq(host->irq, host); destroy_workqueue(host->card_workqueue); - dma_free_coherent(&host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); + dma_free_coherent(host->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); if (host->use_dma && host->dma_ops->exit) host->dma_ops->exit(host); @@ -2188,7 +2188,7 @@ int dw_mci_resume(struct dw_mci *host) if (host->vmmc) regulator_enable(host->vmmc); - if (!mci_wait_reset(&host->dev, host)) { + if (!mci_wait_reset(host->dev, host)) { ret = -ENODEV; return ret; } diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 7a7ebd3..a37a573 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -156,7 +156,7 @@ struct dw_mci { u32 fifoth_val; u16 verid; u16 data_offset; - struct device dev; + struct device *dev; struct dw_mci_board *pdata; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; -- cgit v0.10.2 From bb8bdc77efdecc868d522691487d261ac32d3237 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:36 +0000 Subject: mmc: dw_mmc: Use devm_* functions in dw_mmc platform driver Use devm_* managed functions for simpler error handling. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index 528d37b..ce7be1a 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -27,38 +27,27 @@ static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) struct resource *regs; int ret; - host = kzalloc(sizeof(struct dw_mci), GFP_KERNEL); + host = devm_kzalloc(&pdev->dev, sizeof(struct dw_mci), GFP_KERNEL); if (!host) return -ENOMEM; regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!regs) { - ret = -ENXIO; - goto err_free; - } + if (!regs) + return -ENXIO; host->irq = platform_get_irq(pdev, 0); - if (host->irq < 0) { - ret = host->irq; - goto err_free; - } + if (host->irq < 0) + return host->irq; host->dev = &pdev->dev; host->irq_flags = 0; host->pdata = pdev->dev.platform_data; - ret = -ENOMEM; - host->regs = ioremap(regs->start, resource_size(regs)); + host->regs = devm_request_and_ioremap(&pdev->dev, regs); if (!host->regs) - goto err_free; + return -ENOMEM; + platform_set_drvdata(pdev, host); ret = dw_mci_probe(host); - if (ret) - goto err_out; - return ret; -err_out: - iounmap(host->regs); -err_free: - kfree(host); return ret; } @@ -68,8 +57,6 @@ static int __devexit dw_mci_pltfm_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); dw_mci_remove(host); - iounmap(host->regs); - kfree(host); return 0; } -- cgit v0.10.2 From 1c2215b7c6f20a65877431a5ebb4f9a789df3811 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:37 +0000 Subject: mmc: dw_mmc: allow probe to succeed even if one slot is initialized Instead of aborting the probe when a slot initialization fails, allow initialization of as many slots as possible. If there is at least one instance of a slot that is successfully initialized, allow the driver probe to succeed. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index a18e73c..227c42e 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1946,6 +1946,7 @@ int dw_mci_probe(struct dw_mci *host) { int width, i, ret = 0; u32 fifo_size; + int init_slots = 0; if (!host->pdata || !host->pdata->init) { dev_err(host->dev, @@ -2054,10 +2055,18 @@ int dw_mci_probe(struct dw_mci *host) /* We need at least one slot to succeed */ for (i = 0; i < host->num_slots; i++) { ret = dw_mci_init_slot(host, i); - if (ret) { - ret = -ENODEV; - goto err_init_slot; - } + if (ret) + dev_dbg(host->dev, "slot %d init failed\n", i); + else + init_slots++; + } + + if (init_slots) { + dev_info(host->dev, "%d slots initialized\n", init_slots); + } else { + dev_dbg(host->dev, "attempted to initialize %d slots, " + "but failed on all\n", host->num_slots); + goto err_init_slot; } /* @@ -2092,12 +2101,6 @@ int dw_mci_probe(struct dw_mci *host) return 0; err_init_slot: - /* De-init any initialized slots */ - while (i > 0) { - if (host->slot[i]) - dw_mci_cleanup_slot(host->slot[i], i); - i--; - } free_irq(host->irq, host); err_workqueue: -- cgit v0.10.2 From f90a0612f0e110a8af976835273124dff4fa8b3d Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:38 +0000 Subject: mmc: dw_mmc: lookup for optional biu and ciu clocks Some platforms allow for clock gating and control of bus interface unit clock and card interface unit clock. Add support for clock lookup of optional biu and ciu clocks for clock gating and clock speed determination. Signed-off-by: Abhilash Kesavan Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 227c42e..de45ad2 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1960,13 +1960,42 @@ int dw_mci_probe(struct dw_mci *host) return -ENODEV; } - if (!host->pdata->bus_hz) { + host->biu_clk = clk_get(host->dev, "biu"); + if (IS_ERR(host->biu_clk)) { + dev_dbg(host->dev, "biu clock not available\n"); + } else { + ret = clk_prepare_enable(host->biu_clk); + if (ret) { + dev_err(host->dev, "failed to enable biu clock\n"); + clk_put(host->biu_clk); + return ret; + } + } + + host->ciu_clk = clk_get(host->dev, "ciu"); + if (IS_ERR(host->ciu_clk)) { + dev_dbg(host->dev, "ciu clock not available\n"); + } else { + ret = clk_prepare_enable(host->ciu_clk); + if (ret) { + dev_err(host->dev, "failed to enable ciu clock\n"); + clk_put(host->ciu_clk); + goto err_clk_biu; + } + } + + if (IS_ERR(host->ciu_clk)) + host->bus_hz = host->pdata->bus_hz; + else + host->bus_hz = clk_get_rate(host->ciu_clk); + + if (!host->bus_hz) { dev_err(host->dev, "Platform data must supply bus speed\n"); - return -ENODEV; + ret = -ENODEV; + goto err_clk_ciu; } - host->bus_hz = host->pdata->bus_hz; host->quirks = host->pdata->quirks; spin_lock_init(&host->lock); @@ -2116,6 +2145,17 @@ err_dmaunmap: regulator_disable(host->vmmc); regulator_put(host->vmmc); } + +err_clk_ciu: + if (!IS_ERR(host->ciu_clk)) { + clk_disable_unprepare(host->ciu_clk); + clk_put(host->ciu_clk); + } +err_clk_biu: + if (!IS_ERR(host->biu_clk)) { + clk_disable_unprepare(host->biu_clk); + clk_put(host->biu_clk); + } return ret; } EXPORT_SYMBOL(dw_mci_probe); @@ -2149,6 +2189,12 @@ void dw_mci_remove(struct dw_mci *host) regulator_put(host->vmmc); } + if (!IS_ERR(host->ciu_clk)) + clk_disable_unprepare(host->ciu_clk); + if (!IS_ERR(host->biu_clk)) + clk_disable_unprepare(host->biu_clk); + clk_put(host->ciu_clk); + clk_put(host->biu_clk); } EXPORT_SYMBOL(dw_mci_remove); diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index a37a573..787ad56 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -78,6 +78,8 @@ struct mmc_data; * @data_offset: Set the offset of DATA register according to VERID. * @dev: Device associated with the MMC controller. * @pdata: Platform data associated with the MMC controller. + * @biu_clk: Pointer to bus interface unit clock instance. + * @ciu_clk: Pointer to card interface unit clock instance. * @slot: Slots sharing this MMC controller. * @fifo_depth: depth of FIFO. * @data_shift: log2 of FIFO item size. @@ -158,6 +160,8 @@ struct dw_mci { u16 data_offset; struct device *dev; struct dw_mci_board *pdata; + struct clk *biu_clk; + struct clk *ciu_clk; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; /* FIFO push and pull */ -- cgit v0.10.2 From b4967aa58e2bbafbb280dd4f0c5a777181500e41 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:39 +0000 Subject: mmc: dw_mmc: add quirk to indicate missing write protect line If the write protect pad of the controller is not connected to the write protect pin of the slot, the driver should be notified of this condition so that incorrect check for write protection by reading the WRTORT register can avoided. The get_ro platform callback can be used for in such cases, but with device tree support enabled, such platform callbacks cannot be supported. Add a new quirk for notifying the driver about the missing write protect line so the driver can assume that the card write protection is disabled. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index de45ad2..455a155 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -830,7 +830,9 @@ static int dw_mci_get_ro(struct mmc_host *mmc) struct dw_mci_board *brd = slot->host->pdata; /* Use platform get_ro function, else try on board write protect */ - if (brd->get_ro) + if (brd->quirks & DW_MCI_QUIRK_NO_WRITE_PROTECT) + read_only = 0; + else if (brd->get_ro) read_only = brd->get_ro(slot->id); else read_only = diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 787ad56..b72e4aa9 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -205,7 +205,8 @@ struct dw_mci_dma_ops { #define DW_MCI_QUIRK_HIGHSPEED BIT(2) /* Unreliable card detection */ #define DW_MCI_QUIRK_BROKEN_CARD_DETECTION BIT(3) - +/* Write Protect detection not available */ +#define DW_MCI_QUIRK_NO_WRITE_PROTECT BIT(4) struct dma_pdata; -- cgit v0.10.2 From c91eab4b2564f2424268113ab348eacf9381c2d9 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:40 +0000 Subject: mmc: dw_mmc: add device tree support Add device tree based discovery support. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/synposis-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/synposis-dw-mshc.txt new file mode 100644 index 0000000..06cd32d08 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/synposis-dw-mshc.txt @@ -0,0 +1,79 @@ +* Synopsis Designware Mobile Storage Host Controller + +The Synopsis designware mobile storage host controller is used to interface +a SoC with storage medium such as eMMC or SD/MMC cards. This file documents +differences between the core mmc properties described by mmc.txt and the +properties used by the Synopsis Designware Mobile Storage Host Controller. + +Required Properties: + +* compatible: should be + - snps,dw-mshc: for controllers compliant with synopsis dw-mshc. +* #address-cells: should be 1. +* #size-cells: should be 0. + +# Slots: The slot specific information are contained within child-nodes with + each child-node representing a supported slot. There should be atleast one + child node representing a card slot. The name of the child node representing + the slot is recommended to be slot@n where n is the unique number of the slot + connnected to the controller. The following are optional properties which + can be included in the slot child node. + + * reg: specifies the physical slot number. The valid values of this + property is 0 to (num-slots -1), where num-slots is the value + specified by the num-slots property. + + * bus-width: as documented in mmc core bindings. + + * wp-gpios: specifies the write protect gpio line. The format of the + gpio specifier depends on the gpio controller. If the write-protect + line is not available, this property is optional. + +Optional properties: + +* num-slots: specifies the number of slots supported by the controller. + The number of physical slots actually used could be equal or less than the + value specified by num-slots. If this property is not specified, the value + of num-slot property is assumed to be 1. + +* fifo-depth: The maximum size of the tx/rx fifo's. If this property is not + specified, the default value of the fifo size is determined from the + controller registers. + +* card-detect-delay: Delay in milli-seconds before detecting card after card + insert event. The default value is 0. + +* supports-highspeed: Enables support for high speed cards (upto 50MHz) + +* broken-cd: as documented in mmc core bindings. + +Aliases: + +- All the MSHC controller nodes should be represented in the aliases node using + the following format 'mshc{n}' where n is a unique number for the alias. + +Example: + +The MSHC controller node can be split into two portions, SoC specific and +board specific portions as listed below. + + dwmmc0@12200000 { + compatible = "snps,dw-mshc"; + reg = <0x12200000 0x1000>; + interrupts = <0 75 0>; + #address-cells = <1>; + #size-cells = <0>; + }; + + dwmmc0@12200000 { + num-slots = <1>; + supports-highspeed; + broken-cd; + fifo-depth = <0x80>; + card-detect-delay = <200>; + + slot@0 { + reg = <0>; + bus-width = <8>; + }; + }; diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index ce7be1a..7205905 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -19,6 +19,8 @@ #include #include #include +#include + #include "dw_mmc.h" static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) @@ -94,10 +96,17 @@ static int dw_mci_pltfm_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume); +static const struct of_device_id dw_mci_pltfm_match[] = { + { .compatible = "snps,dw-mshc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match); + static struct platform_driver dw_mci_pltfm_driver = { .remove = __exit_p(dw_mci_pltfm_remove), .driver = { .name = "dw_mmc", + .of_match_table = of_match_ptr(dw_mci_pltfm_match), .pm = &dw_mci_pltfm_pmops, }, }; diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 455a155..c792466 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "dw_mmc.h" @@ -1769,10 +1770,57 @@ static void dw_mci_work_routine_card(struct work_struct *work) } } +#ifdef CONFIG_OF +/* given a slot id, find out the device node representing that slot */ +static struct device_node *dw_mci_of_find_slot_node(struct device *dev, u8 slot) +{ + struct device_node *np; + const __be32 *addr; + int len; + + if (!dev || !dev->of_node) + return NULL; + + for_each_child_of_node(dev->of_node, np) { + addr = of_get_property(np, "reg", &len); + if (!addr || (len < sizeof(int))) + continue; + if (be32_to_cpup(addr) == slot) + return np; + } + return NULL; +} + +/* find out bus-width for a given slot */ +static u32 dw_mci_of_get_bus_wd(struct device *dev, u8 slot) +{ + struct device_node *np = dw_mci_of_find_slot_node(dev, slot); + u32 bus_wd = 1; + + if (!np) + return 1; + + if (of_property_read_u32(np, "bus-width", &bus_wd)) + dev_err(dev, "bus-width property not found, assuming width" + " as 1\n"); + return bus_wd; +} +#else /* CONFIG_OF */ +static u32 dw_mci_of_get_bus_wd(struct device *dev, u8 slot) +{ + return 1; +} +static struct device_node *dw_mci_of_find_slot_node(struct device *dev, u8 slot) +{ + return NULL; +} +#endif /* CONFIG_OF */ + static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) { struct mmc_host *mmc; struct dw_mci_slot *slot; + u8 bus_width; mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev); if (!mmc) @@ -1782,6 +1830,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) slot->id = id; slot->mmc = mmc; slot->host = host; + host->slot[id] = slot; mmc->ops = &dw_mci_ops; mmc->f_min = DIV_ROUND_UP(host->bus_hz, 510); @@ -1806,8 +1855,18 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) mmc->caps2 = host->pdata->caps2; if (host->pdata->get_bus_wd) - if (host->pdata->get_bus_wd(slot->id) >= 4) - mmc->caps |= MMC_CAP_4_BIT_DATA; + bus_width = host->pdata->get_bus_wd(slot->id); + else if (host->dev->of_node) + bus_width = dw_mci_of_get_bus_wd(host->dev, slot->id); + else + bus_width = 1; + + switch (bus_width) { + case 8: + mmc->caps |= MMC_CAP_8_BIT_DATA; + case 4: + mmc->caps |= MMC_CAP_4_BIT_DATA; + } if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED) mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; @@ -1852,7 +1911,6 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) else clear_bit(DW_MMC_CARD_PRESENT, &slot->flags); - host->slot[id] = slot; mmc_add_host(mmc); #if defined(CONFIG_DEBUG_FS) @@ -1944,16 +2002,74 @@ static bool mci_wait_reset(struct device *dev, struct dw_mci *host) return false; } +#ifdef CONFIG_OF +static struct dw_mci_of_quirks { + char *quirk; + int id; +} of_quirks[] = { + { + .quirk = "supports-highspeed", + .id = DW_MCI_QUIRK_HIGHSPEED, + }, { + .quirk = "broken-cd", + .id = DW_MCI_QUIRK_BROKEN_CARD_DETECTION, + }, +}; + +static struct dw_mci_board *dw_mci_parse_dt(struct dw_mci *host) +{ + struct dw_mci_board *pdata; + struct device *dev = host->dev; + struct device_node *np = dev->of_node; + int idx; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "could not allocate memory for pdata\n"); + return ERR_PTR(-ENOMEM); + } + + /* find out number of slots supported */ + if (of_property_read_u32(dev->of_node, "num-slots", + &pdata->num_slots)) { + dev_info(dev, "num-slots property not found, " + "assuming 1 slot is available\n"); + pdata->num_slots = 1; + } + + /* get quirks */ + for (idx = 0; idx < ARRAY_SIZE(of_quirks); idx++) + if (of_get_property(np, of_quirks[idx].quirk, NULL)) + pdata->quirks |= of_quirks[idx].id; + + if (of_property_read_u32(np, "fifo-depth", &pdata->fifo_depth)) + dev_info(dev, "fifo-depth property not found, using " + "value of FIFOTH register as default\n"); + + of_property_read_u32(np, "card-detect-delay", &pdata->detect_delay_ms); + + return pdata; +} + +#else /* CONFIG_OF */ +static struct dw_mci_board *dw_mci_parse_dt(struct dw_mci *host) +{ + return ERR_PTR(-EINVAL); +} +#endif /* CONFIG_OF */ + int dw_mci_probe(struct dw_mci *host) { int width, i, ret = 0; u32 fifo_size; int init_slots = 0; - if (!host->pdata || !host->pdata->init) { - dev_err(host->dev, - "Platform data must supply init function\n"); - return -ENODEV; + if (!host->pdata) { + host->pdata = dw_mci_parse_dt(host); + if (IS_ERR(host->pdata)) { + dev_err(host->dev, "platform data not available\n"); + return -EINVAL; + } } if (!host->pdata->select_slot && host->pdata->num_slots > 1) { -- cgit v0.10.2 From 17403f235e94dcccf95b43138b197c4de2ab6816 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:41 +0000 Subject: mmc: dw_mmc: prepare functions in dw_mmc-pltfm for reuse Platform implementations of dw-mshc controller may choose to extend the features of the standard dw-mshc controller such as adding additional clocking options or modifying the bus interface. Support for such implementation specific extensions can be incorporated into dw_mmc-pltfm, but including multiple such platform specific implementations would convolute the existing dw_mmc-pltfm code. Instead, it would be better to create implementation specific platform drivers to support implementation specific features. Such platforms drivers can reuse the existing infrastructure in dw_mmc-pltfm for resource identification and controller registration and provide support for implementation specific features. So, allow the infrastructure in dw_mmc-pltfm to be reused by other implementation specific platform drivers. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index 7205905..e17da91 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -23,7 +23,7 @@ #include "dw_mmc.h" -static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) +int dw_mci_pltfm_register(struct platform_device *pdev) { struct dw_mci *host; struct resource *regs; @@ -52,6 +52,12 @@ static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) ret = dw_mci_probe(host); return ret; } +EXPORT_SYMBOL_GPL(dw_mci_pltfm_register); + +static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) +{ + return dw_mci_pltfm_register(pdev); +} static int __devexit dw_mci_pltfm_remove(struct platform_device *pdev) { @@ -61,6 +67,7 @@ static int __devexit dw_mci_pltfm_remove(struct platform_device *pdev) dw_mci_remove(host); return 0; } +EXPORT_SYMBOL_GPL(dw_mci_pltfm_remove); #ifdef CONFIG_PM_SLEEP /* @@ -94,7 +101,8 @@ static int dw_mci_pltfm_resume(struct device *dev) #define dw_mci_pltfm_resume NULL #endif /* CONFIG_PM_SLEEP */ -static SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume); +SIMPLE_DEV_PM_OPS(dw_mci_pltfm_pmops, dw_mci_pltfm_suspend, dw_mci_pltfm_resume); +EXPORT_SYMBOL_GPL(dw_mci_pltfm_pmops); static const struct of_device_id dw_mci_pltfm_match[] = { { .compatible = "snps,dw-mshc", }, diff --git a/drivers/mmc/host/dw_mmc-pltfm.h b/drivers/mmc/host/dw_mmc-pltfm.h new file mode 100644 index 0000000..6c065d9 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-pltfm.h @@ -0,0 +1,19 @@ +/* + * Synopsys DesignWare Multimedia Card Interface Platform driver + * + * Copyright (C) 2012, Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef _DW_MMC_PLTFM_H_ +#define _DW_MMC_PLTFM_H_ + +extern int dw_mci_pltfm_register(struct platform_device *pdev); +extern int __devexit dw_mci_pltfm_remove(struct platform_device *pdev); +extern const struct dev_pm_ops dw_mci_pltfm_pmops; + +#endif /* _DW_MMC_PLTFM_H_ */ -- cgit v0.10.2 From 800d78bfccb3d38116abfda2a5b9c8afdbd5ea21 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:42 +0000 Subject: mmc: dw_mmc: add support for implementation specific callbacks The core dw-mshc controller driver can let platform specific implementations of the dw-mshc controller to control the hardware as required by such implementations. This is acheived by invoking implementation specific (optional) callbacks. Define the list of callbacks supported the add invocation points for the same. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc-pltfm.c b/drivers/mmc/host/dw_mmc-pltfm.c index e17da91..c960ca7 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.c +++ b/drivers/mmc/host/dw_mmc-pltfm.c @@ -23,7 +23,8 @@ #include "dw_mmc.h" -int dw_mci_pltfm_register(struct platform_device *pdev) +int dw_mci_pltfm_register(struct platform_device *pdev, + struct dw_mci_drv_data *drv_data) { struct dw_mci *host; struct resource *regs; @@ -41,6 +42,7 @@ int dw_mci_pltfm_register(struct platform_device *pdev) if (host->irq < 0) return host->irq; + host->drv_data = drv_data; host->dev = &pdev->dev; host->irq_flags = 0; host->pdata = pdev->dev.platform_data; @@ -48,6 +50,12 @@ int dw_mci_pltfm_register(struct platform_device *pdev) if (!host->regs) return -ENOMEM; + if (host->drv_data->init) { + ret = host->drv_data->init(host); + if (ret) + return ret; + } + platform_set_drvdata(pdev, host); ret = dw_mci_probe(host); return ret; @@ -56,7 +64,7 @@ EXPORT_SYMBOL_GPL(dw_mci_pltfm_register); static int __devinit dw_mci_pltfm_probe(struct platform_device *pdev) { - return dw_mci_pltfm_register(pdev); + return dw_mci_pltfm_register(pdev, NULL); } static int __devexit dw_mci_pltfm_remove(struct platform_device *pdev) diff --git a/drivers/mmc/host/dw_mmc-pltfm.h b/drivers/mmc/host/dw_mmc-pltfm.h index 6c065d9..301f245 100644 --- a/drivers/mmc/host/dw_mmc-pltfm.h +++ b/drivers/mmc/host/dw_mmc-pltfm.h @@ -12,7 +12,8 @@ #ifndef _DW_MMC_PLTFM_H_ #define _DW_MMC_PLTFM_H_ -extern int dw_mci_pltfm_register(struct platform_device *pdev); +extern int dw_mci_pltfm_register(struct platform_device *pdev, + struct dw_mci_drv_data *drv_data); extern int __devexit dw_mci_pltfm_remove(struct platform_device *pdev); extern const struct dev_pm_ops dw_mci_pltfm_pmops; diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index c792466..9f8e487 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -231,6 +231,7 @@ static void dw_mci_set_timeout(struct dw_mci *host) static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) { struct mmc_data *data; + struct dw_mci_slot *slot = mmc_priv(mmc); u32 cmdr; cmd->error = -EINPROGRESS; @@ -260,6 +261,9 @@ static u32 dw_mci_prepare_command(struct mmc_host *mmc, struct mmc_command *cmd) cmdr |= SDMMC_CMD_DAT_WR; } + if (slot->host->drv_data->prepare_command) + slot->host->drv_data->prepare_command(slot->host, &cmdr); + return cmdr; } @@ -815,6 +819,9 @@ static void dw_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) slot->clock = ios->clock; } + if (slot->host->drv_data->set_ios) + slot->host->drv_data->set_ios(slot->host, ios); + switch (ios->power_mode) { case MMC_POWER_UP: set_bit(DW_MMC_CARD_NEED_INIT, &slot->flags); @@ -1820,6 +1827,7 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) { struct mmc_host *mmc; struct dw_mci_slot *slot; + int ctrl_id, ret; u8 bus_width; mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev); @@ -1851,6 +1859,16 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) if (host->pdata->caps) mmc->caps = host->pdata->caps; + if (host->dev->of_node) { + ctrl_id = of_alias_get_id(host->dev->of_node, "mshc"); + if (ctrl_id < 0) + ctrl_id = 0; + } else { + ctrl_id = to_platform_device(host->dev)->id; + } + if (host->drv_data && host->drv_data->caps) + mmc->caps |= host->drv_data->caps[ctrl_id]; + if (host->pdata->caps2) mmc->caps2 = host->pdata->caps2; @@ -1861,6 +1879,14 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) else bus_width = 1; + if (host->drv_data->setup_bus) { + struct device_node *slot_np; + slot_np = dw_mci_of_find_slot_node(host->dev, slot->id); + ret = host->drv_data->setup_bus(host, slot_np, bus_width); + if (ret) + goto err_setup_bus; + } + switch (bus_width) { case 8: mmc->caps |= MMC_CAP_8_BIT_DATA; @@ -1927,6 +1953,10 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) queue_work(host->card_workqueue, &host->card_work); return 0; + +err_setup_bus: + mmc_free_host(mmc); + return -EINVAL; } static void dw_mci_cleanup_slot(struct dw_mci_slot *slot, unsigned int id) @@ -2021,7 +2051,7 @@ static struct dw_mci_board *dw_mci_parse_dt(struct dw_mci *host) struct dw_mci_board *pdata; struct device *dev = host->dev; struct device_node *np = dev->of_node; - int idx; + int idx, ret; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { @@ -2048,6 +2078,12 @@ static struct dw_mci_board *dw_mci_parse_dt(struct dw_mci *host) of_property_read_u32(np, "card-detect-delay", &pdata->detect_delay_ms); + if (host->drv_data->parse_dt) { + ret = host->drv_data->parse_dt(host); + if (ret) + return ERR_PTR(ret); + } + return pdata; } @@ -2107,6 +2143,15 @@ int dw_mci_probe(struct dw_mci *host) else host->bus_hz = clk_get_rate(host->ciu_clk); + if (host->drv_data->setup_clock) { + ret = host->drv_data->setup_clock(host); + if (ret) { + dev_err(host->dev, + "implementation specific clock setup failed\n"); + goto err_clk_ciu; + } + } + if (!host->bus_hz) { dev_err(host->dev, "Platform data must supply bus speed\n"); diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index 15c27e1..53b8fd9 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -182,4 +182,28 @@ extern int dw_mci_suspend(struct dw_mci *host); extern int dw_mci_resume(struct dw_mci *host); #endif +/** + * dw_mci driver data - dw-mshc implementation specific driver data. + * @caps: mmc subsystem specified capabilities of the controller(s). + * @init: early implementation specific initialization. + * @setup_clock: implementation specific clock configuration. + * @prepare_command: handle CMD register extensions. + * @set_ios: handle bus specific extensions. + * @parse_dt: parse implementation specific device tree properties. + * @setup_bus: initialize io-interface + * + * Provide controller implementation specific extensions. The usage of this + * data structure is fully optional and usage of each member in this structure + * is optional as well. + */ +struct dw_mci_drv_data { + unsigned long *caps; + int (*init)(struct dw_mci *host); + int (*setup_clock)(struct dw_mci *host); + void (*prepare_command)(struct dw_mci *host, u32 *cmdr); + void (*set_ios)(struct dw_mci *host, struct mmc_ios *ios); + int (*parse_dt)(struct dw_mci *host); + int (*setup_bus)(struct dw_mci *host, + struct device_node *slot_np, u8 bus_width); +}; #endif /* _DW_MMC_H_ */ diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index b72e4aa9..6cb043e 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -78,6 +78,8 @@ struct mmc_data; * @data_offset: Set the offset of DATA register according to VERID. * @dev: Device associated with the MMC controller. * @pdata: Platform data associated with the MMC controller. + * @drv_data: Driver specific data for identified variant of the controller + * @priv: Implementation defined private data. * @biu_clk: Pointer to bus interface unit clock instance. * @ciu_clk: Pointer to card interface unit clock instance. * @slot: Slots sharing this MMC controller. @@ -160,6 +162,8 @@ struct dw_mci { u16 data_offset; struct device *dev; struct dw_mci_board *pdata; + struct dw_mci_drv_data *drv_data; + void *priv; struct clk *biu_clk; struct clk *ciu_clk; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; -- cgit v0.10.2 From c3665006eccd7d2326b7dc2c1d5ff2f545dee6c5 Mon Sep 17 00:00:00 2001 From: Thomas Abraham Date: Mon, 17 Sep 2012 18:16:43 +0000 Subject: mmc: dw_mmc: add support for exynos specific implementation of dw-mshc Samsung Exynos SoC's extend the dw-mshc controller for additional clock and bus control. Add support for these extensions and include provide device tree based discovery suppory as well. Signed-off-by: Thomas Abraham Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt b/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt new file mode 100644 index 0000000..7927689 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/exynos-dw-mshc.txt @@ -0,0 +1,87 @@ +* Samsung Exynos specific extensions to the Synopsis Designware Mobile + Storage Host Controller + +The Synopsis designware mobile storage host controller is used to interface +a SoC with storage medium such as eMMC or SD/MMC cards. This file documents +differences between the core Synopsis dw mshc controller properties described +by synposis-dw-mshc.txt and the properties used by the Samsung Exynos specific +extensions to the Synopsis Designware Mobile Storage Host Controller. + +Required Properties: + +* compatible: should be + - "samsung,exynos4210-dw-mshc": for controllers with Samsung Exynos4210 + specific extentions. + - "samsung,exynos4412-dw-mshc": for controllers with Samsung Exynos4412 + specific extentions. + - "samsung,exynos5250-dw-mshc": for controllers with Samsung Exynos5250 + specific extentions. + +* samsung,dw-mshc-ciu-div: Specifies the divider value for the card interface + unit (ciu) clock. This property is applicable only for Exynos5 SoC's and + ignored for Exynos4 SoC's. The valid range of divider value is 0 to 7. + +* samsung,dw-mshc-sdr-timing: Specifies the value of CIU clock phase shift value + in transmit mode and CIU clock phase shift value in receive mode for single + data rate mode operation. Refer notes below for the order of the cells and the + valid values. + +* samsung,dw-mshc-ddr-timing: Specifies the value of CUI clock phase shift value + in transmit mode and CIU clock phase shift value in receive mode for double + data rate mode operation. Refer notes below for the order of the cells and the + valid values. + + Notes for the sdr-timing and ddr-timing values: + + The order of the cells should be + - First Cell: CIU clock phase shift value for tx mode. + - Second Cell: CIU clock phase shift value for rx mode. + + Valid values for SDR and DDR CIU clock timing for Exynos5250: + - valid value for tx phase shift and rx phase shift is 0 to 7. + - when CIU clock divider value is set to 3, all possible 8 phase shift + values can be used. + - if CIU clock divider value is 0 (that is divide by 1), both tx and rx + phase shift clocks should be 0. + +Required properties for a slot: + +* gpios: specifies a list of gpios used for command, clock and data bus. The + first gpio is the command line and the second gpio is the clock line. The + rest of the gpios (depending on the bus-width property) are the data lines in + no particular order. The format of the gpio specifier depends on the gpio + controller. + +Example: + + The MSHC controller node can be split into two portions, SoC specific and + board specific portions as listed below. + + dwmmc0@12200000 { + compatible = "samsung,exynos5250-dw-mshc"; + reg = <0x12200000 0x1000>; + interrupts = <0 75 0>; + #address-cells = <1>; + #size-cells = <0>; + }; + + dwmmc0@12200000 { + num-slots = <1>; + supports-highspeed; + broken-cd; + fifo-depth = <0x80>; + card-detect-delay = <200>; + samsung,dw-mshc-ciu-div = <3>; + samsung,dw-mshc-sdr-timing = <2 3>; + samsung,dw-mshc-ddr-timing = <1 2>; + + slot@0 { + reg = <0>; + bus-width = <8>; + gpios = <&gpc0 0 2 0 3>, <&gpc0 1 2 0 3>, + <&gpc1 0 2 3 3>, <&gpc1 1 2 3 3>, + <&gpc1 2 2 3 3>, <&gpc1 3 2 3 3>, + <&gpc0 3 2 3 3>, <&gpc0 4 2 3 3>, + <&gpc0 5 2 3 3>, <&gpc0 6 2 3 3>; + }; + }; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index aa131b3..9bf10e7 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -540,6 +540,15 @@ config MMC_DW_PLTFM If unsure, say Y. +config MMC_DW_EXYNOS + tristate "Exynos specific extentions for Synopsys DW Memory Card Interface" + depends on MMC_DW + select MMC_DW_PLTFM + help + This selects support for Samsung Exynos SoC specific extensions to the + Synopsys DesignWare Memory Card Interface driver. Select this option + for platforms based on Exynos4 and Exynos5 SoC's. + config MMC_DW_PCI tristate "Synopsys Designware MCI support on PCI bus" depends on MMC_DW && PCI diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 8922b06..17ad0a7 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_DW) += dw_mmc.o obj-$(CONFIG_MMC_DW_PLTFM) += dw_mmc-pltfm.o +obj-$(CONFIG_MMC_DW_EXYNOS) += dw_mmc-exynos.o obj-$(CONFIG_MMC_DW_PCI) += dw_mmc-pci.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o diff --git a/drivers/mmc/host/dw_mmc-exynos.c b/drivers/mmc/host/dw_mmc-exynos.c new file mode 100644 index 0000000..660bbc5 --- /dev/null +++ b/drivers/mmc/host/dw_mmc-exynos.c @@ -0,0 +1,253 @@ +/* + * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver + * + * Copyright (C) 2012, Samsung Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "dw_mmc.h" +#include "dw_mmc-pltfm.h" + +#define NUM_PINS(x) (x + 2) + +#define SDMMC_CLKSEL 0x09C +#define SDMMC_CLKSEL_CCLK_SAMPLE(x) (((x) & 7) << 0) +#define SDMMC_CLKSEL_CCLK_DRIVE(x) (((x) & 7) << 16) +#define SDMMC_CLKSEL_CCLK_DIVIDER(x) (((x) & 7) << 24) +#define SDMMC_CLKSEL_GET_DRV_WD3(x) (((x) >> 16) & 0x7) +#define SDMMC_CLKSEL_TIMING(x, y, z) (SDMMC_CLKSEL_CCLK_SAMPLE(x) | \ + SDMMC_CLKSEL_CCLK_DRIVE(y) | \ + SDMMC_CLKSEL_CCLK_DIVIDER(z)) + +#define SDMMC_CMD_USE_HOLD_REG BIT(29) + +#define EXYNOS4210_FIXED_CIU_CLK_DIV 2 +#define EXYNOS4412_FIXED_CIU_CLK_DIV 4 + +/* Variations in Exynos specific dw-mshc controller */ +enum dw_mci_exynos_type { + DW_MCI_TYPE_EXYNOS4210, + DW_MCI_TYPE_EXYNOS4412, + DW_MCI_TYPE_EXYNOS5250, +}; + +/* Exynos implementation specific driver private data */ +struct dw_mci_exynos_priv_data { + enum dw_mci_exynos_type ctrl_type; + u8 ciu_div; + u32 sdr_timing; + u32 ddr_timing; +}; + +static struct dw_mci_exynos_compatible { + char *compatible; + enum dw_mci_exynos_type ctrl_type; +} exynos_compat[] = { + { + .compatible = "samsung,exynos4210-dw-mshc", + .ctrl_type = DW_MCI_TYPE_EXYNOS4210, + }, { + .compatible = "samsung,exynos4412-dw-mshc", + .ctrl_type = DW_MCI_TYPE_EXYNOS4412, + }, { + .compatible = "samsung,exynos5250-dw-mshc", + .ctrl_type = DW_MCI_TYPE_EXYNOS5250, + }, +}; + +static int dw_mci_exynos_priv_init(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv; + int idx; + + priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(host->dev, "mem alloc failed for private data\n"); + return -ENOMEM; + } + + for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) { + if (of_device_is_compatible(host->dev->of_node, + exynos_compat[idx].compatible)) + priv->ctrl_type = exynos_compat[idx].ctrl_type; + } + + host->priv = priv; + return 0; +} + +static int dw_mci_exynos_setup_clock(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5250) + host->bus_hz /= (priv->ciu_div + 1); + else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412) + host->bus_hz /= EXYNOS4412_FIXED_CIU_CLK_DIV; + else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210) + host->bus_hz /= EXYNOS4210_FIXED_CIU_CLK_DIV; + + return 0; +} + +static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr) +{ + /* + * Exynos4412 and Exynos5250 extends the use of CMD register with the + * use of bit 29 (which is reserved on standard MSHC controllers) for + * optionally bypassing the HOLD register for command and data. The + * HOLD register should be bypassed in case there is no phase shift + * applied on CMD/DATA that is sent to the card. + */ + if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL))) + *cmdr |= SDMMC_CMD_USE_HOLD_REG; +} + +static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios) +{ + struct dw_mci_exynos_priv_data *priv = host->priv; + + if (ios->timing == MMC_TIMING_UHS_DDR50) + mci_writel(host, CLKSEL, priv->ddr_timing); + else + mci_writel(host, CLKSEL, priv->sdr_timing); +} + +static int dw_mci_exynos_parse_dt(struct dw_mci *host) +{ + struct dw_mci_exynos_priv_data *priv = host->priv; + struct device_node *np = host->dev->of_node; + u32 timing[2]; + u32 div = 0; + int ret; + + of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div); + priv->ciu_div = div; + + ret = of_property_read_u32_array(np, + "samsung,dw-mshc-sdr-timing", timing, 2); + if (ret) + return ret; + + priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); + + ret = of_property_read_u32_array(np, + "samsung,dw-mshc-ddr-timing", timing, 2); + if (ret) + return ret; + + priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); + return 0; +} + +static int dw_mci_exynos_setup_bus(struct dw_mci *host, + struct device_node *slot_np, u8 bus_width) +{ + int idx, gpio, ret; + + if (!slot_np) + return -EINVAL; + + /* cmd + clock + bus-width pins */ + for (idx = 0; idx < NUM_PINS(bus_width); idx++) { + gpio = of_get_gpio(slot_np, idx); + if (!gpio_is_valid(gpio)) { + dev_err(host->dev, "invalid gpio: %d\n", gpio); + return -EINVAL; + } + + ret = devm_gpio_request(host->dev, gpio, "dw-mci-bus"); + if (ret) { + dev_err(host->dev, "gpio [%d] request failed\n", gpio); + return -EBUSY; + } + } + + gpio = of_get_named_gpio(slot_np, "wp-gpios", 0); + if (gpio_is_valid(gpio)) { + if (devm_gpio_request(host->dev, gpio, "dw-mci-wp")) + dev_info(host->dev, "gpio [%d] request failed\n", + gpio); + } else { + dev_info(host->dev, "wp gpio not available"); + host->pdata->quirks |= DW_MCI_QUIRK_NO_WRITE_PROTECT; + } + + if (host->pdata->quirks & DW_MCI_QUIRK_BROKEN_CARD_DETECTION) + return 0; + + gpio = of_get_named_gpio(slot_np, "samsung,cd-pinmux-gpio", 0); + if (gpio_is_valid(gpio)) { + if (devm_gpio_request(host->dev, gpio, "dw-mci-cd")) + dev_err(host->dev, "gpio [%d] request failed\n", gpio); + } else { + dev_info(host->dev, "cd gpio not available"); + } + + return 0; +} + +/* Exynos5250 controller specific capabilities */ +static unsigned long exynos5250_dwmmc_caps[4] = { + MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR | + MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23, + MMC_CAP_CMD23, + MMC_CAP_CMD23, + MMC_CAP_CMD23, +}; + +static struct dw_mci_drv_data exynos5250_drv_data = { + .caps = exynos5250_dwmmc_caps, + .init = dw_mci_exynos_priv_init, + .setup_clock = dw_mci_exynos_setup_clock, + .prepare_command = dw_mci_exynos_prepare_command, + .set_ios = dw_mci_exynos_set_ios, + .parse_dt = dw_mci_exynos_parse_dt, + .setup_bus = dw_mci_exynos_setup_bus, +}; + +static const struct of_device_id dw_mci_exynos_match[] = { + { .compatible = "samsung,exynos5250-dw-mshc", + .data = (void *)&exynos5250_drv_data, }, + {}, +}; +MODULE_DEVICE_TABLE(of, dw_mci_pltfm_match); + +int dw_mci_exynos_probe(struct platform_device *pdev) +{ + struct dw_mci_drv_data *drv_data; + const struct of_device_id *match; + + match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node); + drv_data = match->data; + return dw_mci_pltfm_register(pdev, drv_data); +} + +static struct platform_driver dw_mci_exynos_pltfm_driver = { + .probe = dw_mci_exynos_probe, + .remove = __exit_p(dw_mci_pltfm_remove), + .driver = { + .name = "dwmmc_exynos", + .of_match_table = of_match_ptr(dw_mci_exynos_match), + .pm = &dw_mci_pltfm_pmops, + }, +}; + +module_platform_driver(dw_mci_exynos_pltfm_driver); + +MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension"); +MODULE_AUTHOR("Thomas Abraham Date: Thu, 23 Aug 2012 17:10:09 +0000 Subject: mmc: sdhci-s3c: Add device tree support Add device tree based discovery support for Samsung's sdhci controller. Cc: Ben Dooks Cc: Kukjin Kim Signed-off-by: Thomas Abraham Reviewed-by: Jaehoon Chung Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/samsung-sdhci.txt b/Documentation/devicetree/bindings/mmc/samsung-sdhci.txt new file mode 100644 index 0000000..630a7d7 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/samsung-sdhci.txt @@ -0,0 +1,53 @@ +* Samsung's SDHCI Controller device tree bindings + +Samsung's SDHCI controller is used as a connectivity interface with external +MMC, SD and eMMC storage mediums. This file documents differences between the +core mmc properties described by mmc.txt and the properties used by the +Samsung implmentation of the SDHCI controller. + +Note: The mmc core bindings documentation states that if none of the core +card-detect bindings are used, then the standard sdhci card detect mechanism +is used. The Samsung's SDHCI controller bindings extends this as listed below. + +[A] The property "samsung,cd-pinmux-gpio" can be used as stated in the + "Optional Board Specific Properties" section below. + +[B] If core card-detect bindings and "samsung,cd-pinmux-gpio" property + is not specified, it is assumed that there is no card detection + mechanism used. + +Required SoC Specific Properties: +- compatible: should be one of the following + - "samsung,s3c6410-sdhci": For controllers compatible with s3c6410 sdhci + controller. + - "samsung,exynos4210-sdhci": For controllers compatible with Exynos4 sdhci + controller. + +Required Board Specific Properties: +- gpios: Should specify the gpios used for clock, command and data lines. The + gpio specifier format depends on the gpio controller. + +Optional Board Specific Properties: +- samsung,cd-pinmux-gpio: Specifies the card detect line that is routed + through a pinmux to the card-detect pin of the card slot. This property + should be used only if none of the mmc core card-detect properties are + used. + +Example: + sdhci@12530000 { + compatible = "samsung,exynos4210-sdhci"; + reg = <0x12530000 0x100>; + interrupts = <0 75 0>; + bus-width = <4>; + cd-gpios = <&gpk2 2 2 3 3>; + gpios = <&gpk2 0 2 0 3>, /* clock line */ + <&gpk2 1 2 0 3>, /* command line */ + <&gpk2 3 2 3 3>, /* data line 0 */ + <&gpk2 4 2 3 3>, /* data line 1 */ + <&gpk2 5 2 3 3>, /* data line 2 */ + <&gpk2 6 2 3 3>; /* data line 3 */ + }; + + Note: This example shows both SoC specific and board specific properties + in a single device node. The properties can be actually be seperated + into SoC specific node and board specific node. diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 00969ba..0c77b10 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -34,6 +34,9 @@ #define MAX_BUS_CLK (4) +/* Number of gpio's used is max data bus width + command and clock lines */ +#define NUM_GPIOS(x) (x + 2) + /** * struct sdhci_s3c - S3C SDHCI instance * @host: The SDHCI host created @@ -41,6 +44,7 @@ * @ioarea: The resource created when we claimed the IO area. * @pdata: The platform data for this controller. * @cur_clk: The index of the current bus clock. + * @gpios: List of gpio numbers parsed from device tree. * @clk_io: The clock for the internal bus interface. * @clk_bus: The clocks that are available for the SD/MMC bus clock. */ @@ -52,6 +56,7 @@ struct sdhci_s3c { unsigned int cur_clk; int ext_cd_irq; int ext_cd_gpio; + int *gpios; struct clk *clk_io; struct clk *clk_bus[MAX_BUS_CLK]; @@ -422,9 +427,121 @@ static void sdhci_s3c_setup_card_detect_gpio(struct sdhci_s3c *sc) } } +#ifdef CONFIG_OF +static int __devinit sdhci_s3c_parse_dt(struct device *dev, + struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) +{ + struct device_node *node = dev->of_node; + struct sdhci_s3c *ourhost = to_s3c(host); + u32 max_width; + int gpio, cnt, ret; + + /* if the bus-width property is not specified, assume width as 1 */ + if (of_property_read_u32(node, "bus-width", &max_width)) + max_width = 1; + pdata->max_width = max_width; + + ourhost->gpios = devm_kzalloc(dev, NUM_GPIOS(pdata->max_width) * + sizeof(int), GFP_KERNEL); + if (!ourhost->gpios) + return -ENOMEM; + + /* get the card detection method */ + if (of_get_property(node, "broken-cd", 0)) { + pdata->cd_type = S3C_SDHCI_CD_NONE; + goto setup_bus; + } + + if (of_get_property(node, "non-removable", 0)) { + pdata->cd_type = S3C_SDHCI_CD_PERMANENT; + goto setup_bus; + } + + gpio = of_get_named_gpio(node, "cd-gpios", 0); + if (gpio_is_valid(gpio)) { + pdata->cd_type = S3C_SDHCI_CD_GPIO; + goto found_cd; + } else if (gpio != -ENOENT) { + dev_err(dev, "invalid card detect gpio specified\n"); + return -EINVAL; + } + + gpio = of_get_named_gpio(node, "samsung,cd-pinmux-gpio", 0); + if (gpio_is_valid(gpio)) { + pdata->cd_type = S3C_SDHCI_CD_INTERNAL; + goto found_cd; + } else if (gpio != -ENOENT) { + dev_err(dev, "invalid card detect gpio specified\n"); + return -EINVAL; + } + + dev_info(dev, "assuming no card detect line available\n"); + pdata->cd_type = S3C_SDHCI_CD_NONE; + + found_cd: + if (pdata->cd_type == S3C_SDHCI_CD_GPIO) { + pdata->ext_cd_gpio = gpio; + ourhost->ext_cd_gpio = -1; + if (of_get_property(node, "cd-inverted", NULL)) + pdata->ext_cd_gpio_invert = 1; + } else if (pdata->cd_type == S3C_SDHCI_CD_INTERNAL) { + ret = gpio_request(gpio, "sdhci-cd"); + if (ret) { + dev_err(dev, "card detect gpio request failed\n"); + return -EINVAL; + } + ourhost->ext_cd_gpio = gpio; + } + + setup_bus: + /* get the gpios for command, clock and data lines */ + for (cnt = 0; cnt < NUM_GPIOS(pdata->max_width); cnt++) { + gpio = of_get_gpio(node, cnt); + if (!gpio_is_valid(gpio)) { + dev_err(dev, "invalid gpio[%d]\n", cnt); + goto err_free_dt_cd_gpio; + } + ourhost->gpios[cnt] = gpio; + } + + for (cnt = 0; cnt < NUM_GPIOS(pdata->max_width); cnt++) { + ret = gpio_request(ourhost->gpios[cnt], "sdhci-gpio"); + if (ret) { + dev_err(dev, "gpio[%d] request failed\n", cnt); + goto err_free_dt_gpios; + } + } + + return 0; + + err_free_dt_gpios: + while (--cnt >= 0) + gpio_free(ourhost->gpios[cnt]); + err_free_dt_cd_gpio: + if (pdata->cd_type == S3C_SDHCI_CD_INTERNAL) + gpio_free(ourhost->ext_cd_gpio); + return -EINVAL; +} +#else +static int __devinit sdhci_s3c_parse_dt(struct device *dev, + struct sdhci_host *host, struct s3c_sdhci_platdata *pdata) +{ + return -EINVAL; +} +#endif + +static const struct of_device_id sdhci_s3c_dt_match[]; + static inline struct sdhci_s3c_drv_data *sdhci_s3c_get_driver_data( struct platform_device *pdev) { +#ifdef CONFIG_OF + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(sdhci_s3c_dt_match, pdev->dev.of_node); + return (struct sdhci_s3c_drv_data *)match->data; + } +#endif return (struct sdhci_s3c_drv_data *) platform_get_device_id(pdev)->driver_data; } @@ -439,7 +556,7 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) struct resource *res; int ret, irq, ptr, clks; - if (!pdev->dev.platform_data) { + if (!pdev->dev.platform_data && !pdev->dev.of_node) { dev_err(dev, "no device data specified\n"); return -ENOENT; } @@ -455,21 +572,28 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) dev_err(dev, "sdhci_alloc_host() failed\n"); return PTR_ERR(host); } + sc = sdhci_priv(host); pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { ret = -ENOMEM; - goto err_io_clk; + goto err_pdata; + } + + if (pdev->dev.of_node) { + ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata); + if (ret) + goto err_pdata; + } else { + memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); + sc->ext_cd_gpio = -1; /* invalid gpio number */ } - memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); drv_data = sdhci_s3c_get_driver_data(pdev); - sc = sdhci_priv(host); sc->host = host; sc->pdev = pdev; sc->pdata = pdata; - sc->ext_cd_gpio = -1; /* invalid gpio number */ platform_set_drvdata(pdev, host); @@ -633,6 +757,12 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) clk_put(sc->clk_io); err_io_clk: + for (ptr = 0; ptr < NUM_GPIOS(sc->pdata->max_width); ptr++) + gpio_free(sc->gpios[ptr]); + if (pdata->cd_type == S3C_SDHCI_CD_INTERNAL) + gpio_free(sc->ext_cd_gpio); + + err_pdata: sdhci_free_host(host); return ret; @@ -640,9 +770,9 @@ 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); + struct s3c_sdhci_platdata *pdata = sc->pdata; int ptr; if (pdata->cd_type == S3C_SDHCI_CD_EXTERNAL && pdata->ext_cd_cleanup) @@ -667,6 +797,11 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) clk_disable(sc->clk_io); clk_put(sc->clk_io); + if (pdev->dev.of_node) { + for (ptr = 0; ptr < NUM_GPIOS(sc->pdata->max_width); ptr++) + gpio_free(sc->gpios[ptr]); + } + sdhci_free_host(host); platform_set_drvdata(pdev, NULL); @@ -739,6 +874,16 @@ static struct platform_device_id sdhci_s3c_driver_ids[] = { }; MODULE_DEVICE_TABLE(platform, sdhci_s3c_driver_ids); +#ifdef CONFIG_OF +static const struct of_device_id sdhci_s3c_dt_match[] = { + { .compatible = "samsung,s3c6410-sdhci", }, + { .compatible = "samsung,exynos4210-sdhci", + .data = (void *)EXYNOS4_SDHCI_DRV_DATA }, + {}, +}; +MODULE_DEVICE_TABLE(of, sdhci_s3c_dt_match); +#endif + static struct platform_driver sdhci_s3c_driver = { .probe = sdhci_s3c_probe, .remove = __devexit_p(sdhci_s3c_remove), @@ -746,6 +891,7 @@ static struct platform_driver sdhci_s3c_driver = { .driver = { .owner = THIS_MODULE, .name = "s3c-sdhci", + .of_match_table = of_match_ptr(sdhci_s3c_dt_match), .pm = SDHCI_S3C_PMOPS, }, }; -- cgit v0.10.2 From ac940938df138acd241dc5ae9a933416da036a35 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 26 Aug 2012 16:00:59 +0000 Subject: mmc: mmci: use clk_prepare_enable and clk_disable_unprepare clk_prepare_enable and clk_disable_unprepare combine clk_prepare and clk_enable, and clk_disable and clk_unprepare. They make the code more concise, and ensure that clk_unprepare is called when clk_enable fails. A simplified version of the semantic patch that introduces calls to these functions is as follows: (http://coccinelle.lip6.fr/) // @@ expression e; @@ - clk_prepare(e); - clk_enable(e); + clk_prepare_enable(e); @@ expression e; @@ - clk_disable(e); - clk_unprepare(e); + clk_disable_unprepare(e); // Signed-off-by: Julia Lawall Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 50ff19a..edc3e9b 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -1309,14 +1309,10 @@ static int __devinit mmci_probe(struct amba_device *dev, goto host_free; } - ret = clk_prepare(host->clk); + ret = clk_prepare_enable(host->clk); if (ret) goto clk_free; - ret = clk_enable(host->clk); - if (ret) - goto clk_unprep; - host->plat = plat; host->variant = variant; host->mclk = clk_get_rate(host->clk); @@ -1515,9 +1511,7 @@ static int __devinit mmci_probe(struct amba_device *dev, err_gpio_cd: iounmap(host->base); clk_disable: - clk_disable(host->clk); - clk_unprep: - clk_unprepare(host->clk); + clk_disable_unprepare(host->clk); clk_free: clk_put(host->clk); host_free: @@ -1564,8 +1558,7 @@ static int __devexit mmci_remove(struct amba_device *dev) gpio_free(host->gpio_cd); iounmap(host->base); - clk_disable(host->clk); - clk_unprepare(host->clk); + clk_disable_unprepare(host->clk); clk_put(host->clk); if (host->vcc) -- cgit v0.10.2 From ccdfe6122f61b40592820d48b4cde021295418a0 Mon Sep 17 00:00:00 2001 From: Hein_Tibosch Date: Thu, 30 Aug 2012 16:34:38 +0000 Subject: mmc: atmel-mci: DMA can be used with other controllers After the latest changes to atmel-mci, it could not be used with DMA on the AVR32 platform. This patch will allow to use DMA again and it will avoid access to MCI register ATMCI_DMA. Even if the IP version is lower than v3xx and doesn't have the DMA configuration register, DMA transfers can be used with a different controller than the Atmel AHB DMA one. For instance, some AVR chips use the Synopsys DesignWare AHB DMA controller. Signed-off-by: Hein Tibosch Acked-by: Ludovic Desroches Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 8c72828..6823d96 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -74,7 +74,7 @@ enum atmci_pdc_buf { }; struct atmel_mci_caps { - bool has_dma; + bool has_dma_conf_reg; bool has_pdc; bool has_cfg_reg; bool has_cstor_reg; @@ -421,7 +421,7 @@ static int atmci_regs_show(struct seq_file *s, void *v) atmci_show_status_reg(s, "SR", buf[ATMCI_SR / 4]); atmci_show_status_reg(s, "IMR", buf[ATMCI_IMR / 4]); - if (host->caps.has_dma) { + if (host->caps.has_dma_conf_reg) { u32 val; val = buf[ATMCI_DMA / 4]; @@ -841,7 +841,7 @@ static void atmci_dma_complete(void *arg) dev_vdbg(&host->pdev->dev, "DMA complete\n"); - if (host->caps.has_dma) + if (host->caps.has_dma_conf_reg) /* Disable DMA hardware handshaking on MCI */ atmci_writel(host, ATMCI_DMA, atmci_readl(host, ATMCI_DMA) & ~ATMCI_DMAEN); @@ -1028,7 +1028,9 @@ atmci_prepare_data_dma(struct atmel_mci *host, struct mmc_data *data) maxburst = atmci_convert_chksize(host->dma_conf.dst_maxburst); } - atmci_writel(host, ATMCI_DMA, ATMCI_DMA_CHKSIZE(maxburst) | ATMCI_DMAEN); + if (host->caps.has_dma_conf_reg) + atmci_writel(host, ATMCI_DMA, ATMCI_DMA_CHKSIZE(maxburst) | + ATMCI_DMAEN); sglen = dma_map_sg(chan->device->dev, data->sg, data->sg_len, direction); @@ -2243,7 +2245,10 @@ static bool atmci_configure_dma(struct atmel_mci *host) pdata = host->pdev->dev.platform_data; - if (pdata && find_slave_dev(pdata->dma_slave)) { + if (!pdata) + return false; + + if (pdata->dma_slave && find_slave_dev(pdata->dma_slave)) { dma_cap_mask_t mask; /* Try to grab a DMA channel */ @@ -2284,7 +2289,7 @@ static void __init atmci_get_cap(struct atmel_mci *host) dev_info(&host->pdev->dev, "version: 0x%x\n", version); - host->caps.has_dma = 0; + host->caps.has_dma_conf_reg = 0; host->caps.has_pdc = 1; host->caps.has_cfg_reg = 0; host->caps.has_cstor_reg = 0; @@ -2302,12 +2307,7 @@ static void __init atmci_get_cap(struct atmel_mci *host) host->caps.has_odd_clk_div = 1; case 0x400: case 0x300: -#ifdef CONFIG_AT_HDMAC - host->caps.has_dma = 1; -#else - dev_info(&host->pdev->dev, - "has dma capability but dma engine is not selected, then use pio\n"); -#endif + host->caps.has_dma_conf_reg = 1; host->caps.has_pdc = 0; host->caps.has_cfg_reg = 1; host->caps.has_cstor_reg = 1; @@ -2388,7 +2388,7 @@ static int __init atmci_probe(struct platform_device *pdev) /* Get MCI capabilities and set operations according to it */ atmci_get_cap(host); - if (host->caps.has_dma && atmci_configure_dma(host)) { + if (atmci_configure_dma(host)) { host->prepare_data = &atmci_prepare_data_dma; host->submit_data = &atmci_submit_data_dma; host->stop_transfer = &atmci_stop_transfer_dma; -- cgit v0.10.2 From 6bf2af8cd2cb35f6098953a33b9a160a4ca0b626 Mon Sep 17 00:00:00 2001 From: Hein_Tibosch Date: Thu, 30 Aug 2012 16:34:27 +0000 Subject: mmc: atmel-mci: AP700x PDC is not connected to MCI Earlier, atmel-mci was adapted to make use of the peripheral DMA controller (PDC), in case normal DMA wouldn't work. ( http://comments.gmane.org/gmane.linux.kernel.mmc/9403 ) This works OK on ARM platforms (AT91), but it broke the driver for AVR32, the AP700x. Although the MCI has PDC support, the connection is not done for AVR chips. This patch makes the use of PDC depend on !CONFIG_AVR32. Signed-off-by: Hein Tibosch Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/atmel-mci-regs.h b/drivers/mmc/host/atmel-mci-regs.h index ab56f7d..c97001e 100644 --- a/drivers/mmc/host/atmel-mci-regs.h +++ b/drivers/mmc/host/atmel-mci-regs.h @@ -140,6 +140,13 @@ #define atmci_writel(port,reg,value) \ __raw_writel((value), (port)->regs + reg) +/* On AVR chips the Peripheral DMA Controller is not connected to MCI. */ +#ifdef CONFIG_AVR32 +# define ATMCI_PDC_CONNECTED 0 +#else +# define ATMCI_PDC_CONNECTED 1 +#endif + /* * Fix sconfig's burst size according to atmel MCI. We need to convert them as: * 1 -> 0, 4 -> 1, 8 -> 2, 16 -> 3. diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 6823d96..312c8fd 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -2290,7 +2290,7 @@ static void __init atmci_get_cap(struct atmel_mci *host) "version: 0x%x\n", version); host->caps.has_dma_conf_reg = 0; - host->caps.has_pdc = 1; + host->caps.has_pdc = ATMCI_PDC_CONNECTED; host->caps.has_cfg_reg = 0; host->caps.has_cstor_reg = 0; host->caps.has_highspeed = 0; -- cgit v0.10.2 From c4c8eeb4df00aabb641553d6fbcd46f458e56cd9 Mon Sep 17 00:00:00 2001 From: Vaibhav Bedia Date: Thu, 13 Sep 2012 06:31:03 +0000 Subject: mmc: omap_hsmmc: Pass on the suspend failure to the PM core In some cases mmc_suspend_host() is not able to claim the host and proceed with the suspend process. The core returns -EBUSY to the host controller driver. Unfortunately, the host controller driver does not pass on this information to the PM core and hence the system suspend process continues. ret = mmc_suspend_host(host->mmc); if (ret) { host->suspended = 0; if (host->pdata->resume) { ret = host->pdata->resume(dev, host->slot_id); The return status from mmc_suspend_host() is overwritten by return status from host->pdata->resume. So the original return status is lost. In these cases the MMC core gets to an unexpected state during resume and multiple issues related to MMC crop up. 1. Host controller driver starts accessing the device registers before the clocks are enabled which leads to a prefetch abort. 2. A file copy thread which was launched before suspend gets stuck due to the host not being reclaimed during resume. To avoid such problems pass on the -EBUSY status to the PM core from the host controller driver. With this change, MMC core suspend might still fail but it does not end up making the system unusable. Suspend gets aborted and the user can try suspending the system again. Signed-off-by: Vaibhav Bedia Signed-off-by: Hebbar, Gururaja Acked-by: Venkatraman S Cc: stable Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 9afdd20..d9af5f1 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -2050,8 +2050,7 @@ static int omap_hsmmc_suspend(struct device *dev) if (ret) { host->suspended = 0; if (host->pdata->resume) { - ret = host->pdata->resume(dev, host->slot_id); - if (ret) + if (host->pdata->resume(dev, host->slot_id)) dev_dbg(dev, "Unmask interrupt failed\n"); } goto err; -- cgit v0.10.2 From f6ad0a481342223b2e7ae9f55b154e14f1391ada Mon Sep 17 00:00:00 2001 From: Javier Martin Date: Fri, 7 Sep 2012 10:43:37 +0000 Subject: mmc: mxcmmc: fix bug that may block a data transfer forever The problem can be easily reproduced using a script that loops copying a file in an SD card to another place in the same SD card and its related to read transfers. This only happens with DMA enabled. This is related to the fact that, when reading, an MMC irq signals the fact that all data from the SD card has been copied to the internal buffers. However, it doesn't signal whether the DMA transfer that is in charge of moving data from these internal buffers to RAM has finished or not. Thus, calling dmaengine_terminate_all() in the MMC irq routine can cancel an ongoing DMA transfer leaving some data in the internal buffers that produces an accumulative effect which, in the end, blocks a read data transfer forever. The following patch watches DMA irq for reading and MMC irqs for writing transfers. The 'dangerous' usage of dmaengine_terminate_all() is removed and a timeout of 10 seconds is added so that the MMC won't block forever anymore. Signed-off-by: Javier Martin Reviewed-by: Sascha Hauer Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/mxcmmc.c b/drivers/mmc/host/mxcmmc.c index 28ed52d..fc42a2e 100644 --- a/drivers/mmc/host/mxcmmc.c +++ b/drivers/mmc/host/mxcmmc.c @@ -44,6 +44,7 @@ #include #define DRIVER_NAME "mxc-mmc" +#define MXCMCI_TIMEOUT_MS 10000 #define MMC_REG_STR_STP_CLK 0x00 #define MMC_REG_STATUS 0x04 @@ -150,6 +151,8 @@ struct mxcmci_host { int dmareq; struct dma_slave_config dma_slave_config; struct imx_dma_data dma_data; + + struct timer_list watchdog; }; static void mxcmci_set_clk_rate(struct mxcmci_host *host, unsigned int clk_ios); @@ -271,9 +274,32 @@ static int mxcmci_setup_data(struct mxcmci_host *host, struct mmc_data *data) dmaengine_submit(host->desc); dma_async_issue_pending(host->dma); + mod_timer(&host->watchdog, jiffies + msecs_to_jiffies(MXCMCI_TIMEOUT_MS)); + return 0; } +static void mxcmci_cmd_done(struct mxcmci_host *host, unsigned int stat); +static void mxcmci_data_done(struct mxcmci_host *host, unsigned int stat); + +static void mxcmci_dma_callback(void *data) +{ + struct mxcmci_host *host = data; + u32 stat; + + del_timer(&host->watchdog); + + stat = readl(host->base + MMC_REG_STATUS); + writel(stat & ~STATUS_DATA_TRANS_DONE, host->base + MMC_REG_STATUS); + + dev_dbg(mmc_dev(host->mmc), "%s: 0x%08x\n", __func__, stat); + + if (stat & STATUS_READ_OP_DONE) + writel(STATUS_READ_OP_DONE, host->base + MMC_REG_STATUS); + + mxcmci_data_done(host, stat); +} + static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd, unsigned int cmdat) { @@ -305,8 +331,14 @@ static int mxcmci_start_cmd(struct mxcmci_host *host, struct mmc_command *cmd, int_cntr = INT_END_CMD_RES_EN; - if (mxcmci_use_dma(host)) - int_cntr |= INT_READ_OP_EN | INT_WRITE_OP_DONE_EN; + if (mxcmci_use_dma(host)) { + if (host->dma_dir == DMA_FROM_DEVICE) { + host->desc->callback = mxcmci_dma_callback; + host->desc->callback_param = host; + } else { + int_cntr |= INT_WRITE_OP_DONE_EN; + } + } spin_lock_irqsave(&host->lock, flags); if (host->use_sdio) @@ -345,11 +377,9 @@ static int mxcmci_finish_data(struct mxcmci_host *host, unsigned int stat) struct mmc_data *data = host->data; int data_error; - if (mxcmci_use_dma(host)) { - dmaengine_terminate_all(host->dma); + if (mxcmci_use_dma(host)) dma_unmap_sg(host->dma->device->dev, data->sg, data->sg_len, host->dma_dir); - } if (stat & STATUS_ERR_MASK) { dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n", @@ -624,8 +654,10 @@ static irqreturn_t mxcmci_irq(int irq, void *devid) mxcmci_cmd_done(host, stat); if (mxcmci_use_dma(host) && - (stat & (STATUS_DATA_TRANS_DONE | STATUS_WRITE_OP_DONE))) + (stat & (STATUS_DATA_TRANS_DONE | STATUS_WRITE_OP_DONE))) { + del_timer(&host->watchdog); mxcmci_data_done(host, stat); + } if (host->default_irq_mask && (stat & (STATUS_CARD_INSERTION | STATUS_CARD_REMOVAL))) @@ -836,6 +868,34 @@ static bool filter(struct dma_chan *chan, void *param) return true; } +static void mxcmci_watchdog(unsigned long data) +{ + struct mmc_host *mmc = (struct mmc_host *)data; + struct mxcmci_host *host = mmc_priv(mmc); + struct mmc_request *req = host->req; + unsigned int stat = readl(host->base + MMC_REG_STATUS); + + if (host->dma_dir == DMA_FROM_DEVICE) { + dmaengine_terminate_all(host->dma); + dev_err(mmc_dev(host->mmc), + "%s: read time out (status = 0x%08x)\n", + __func__, stat); + } else { + dev_err(mmc_dev(host->mmc), + "%s: write time out (status = 0x%08x)\n", + __func__, stat); + mxcmci_softreset(host); + } + + /* Mark transfer as erroneus and inform the upper layers */ + + host->data->error = -ETIMEDOUT; + host->req = NULL; + host->cmd = NULL; + host->data = NULL; + mmc_request_done(host->mmc, req); +} + static const struct mmc_host_ops mxcmci_ops = { .request = mxcmci_request, .set_ios = mxcmci_set_ios, @@ -968,6 +1028,10 @@ static int mxcmci_probe(struct platform_device *pdev) mmc_add_host(mmc); + init_timer(&host->watchdog); + host->watchdog.function = &mxcmci_watchdog; + host->watchdog.data = (unsigned long)mmc; + return 0; out_free_irq: -- cgit v0.10.2 From 897b69e72054a39c1c1d99c8f334ffed6dc99ac4 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Wed, 19 Sep 2012 13:58:31 +0800 Subject: mmc: Revert "mmc: dw_mmc: Add check for IDMAC configuration" This reverts commit 94c6cee91 (Add check for IDMAC configuration). Synopsys says that only if internal dmac is not present, optional external dma interface is present. When internal dmac is present, '0' value in DMA_INTERFACE of HCON is reasonable. DMA_INTERFACE indicates external dma interface. And idmac initialization is prohibited now. The commit should be reverted since: the check for IDMAC is not reliable; falling back to PIO would provide awful performance; we wouldn't expect to see instances of this block without DMA support. Signed-off-by: Seungwon Jeon Acked-by: Will Newton Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 9f8e487..a23af77 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -410,23 +410,11 @@ static void dw_mci_idmac_start_dma(struct dw_mci *host, unsigned int sg_len) static int dw_mci_idmac_init(struct dw_mci *host) { struct idmac_desc *p; - int i, dma_support; + int i; /* Number of descriptors in the ring buffer */ host->ring_size = PAGE_SIZE / sizeof(struct idmac_desc); - /* Check if Hardware Configuration Register has support for DMA */ - dma_support = (mci_readl(host, HCON) >> 16) & 0x3; - - if (!dma_support || dma_support > 2) { - dev_err(host->dev, - "Host Controller does not support IDMA Tx.\n"); - host->dma_ops = NULL; - return -ENODEV; - } - - dev_info(host->dev, "Using internal DMA controller.\n"); - /* Forward link the descriptor list */ for (i = 0, p = host->sg_cpu; i < host->ring_size - 1; i++, p++) p->des3 = host->sg_dma + (sizeof(struct idmac_desc) * (i + 1)); @@ -1985,6 +1973,7 @@ static void dw_mci_init_dma(struct dw_mci *host) /* Determine which DMA interface to use */ #ifdef CONFIG_MMC_DW_IDMAC host->dma_ops = &dw_mci_idmac_ops; + dev_info(&host->dev, "Using internal DMA controller.\n"); #endif if (!host->dma_ops) -- cgit v0.10.2 From e684c46876f1d0c1c4964741244a34b6be837b5f Mon Sep 17 00:00:00 2001 From: Chander Kashyap Date: Fri, 14 Sep 2012 09:08:49 +0000 Subject: mmc: sdhci-s3c: Enable only required bus clock In case of multiple bus clock sources, all the clock sources were getting enabled. As only one clock source is needed at the time hence enable only the required bus clock. This patch does as follows: 1. In sdhci_s3c_probe enable only required bus clock source. 2. Handle the disabling of old bus clock and enables the best clock selected in sdhci_s3c_set_clock(). Signed-off-by: Chander Kashyap Acked-by: Jaehoon Chung Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 0c77b10..4d9acf8 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -208,10 +208,12 @@ static void sdhci_s3c_set_clock(struct sdhci_host *host, unsigned int clock) best_src, clock, best); /* select the new clock source */ - if (ourhost->cur_clk != best_src) { struct clk *clk = ourhost->clk_bus[best_src]; + clk_enable(clk); + clk_disable(ourhost->clk_bus[ourhost->cur_clk]); + /* turn clock off to card before changing clock source */ writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); @@ -625,8 +627,6 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) */ sc->cur_clk = ptr; - clk_enable(clk); - dev_info(dev, "clock source %d: %s (%ld Hz)\n", ptr, name, clk_get_rate(clk)); } @@ -637,6 +637,8 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) goto err_no_busclks; } + clk_enable(sc->clk_bus[sc->cur_clk]); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); host->ioaddr = devm_request_and_ioremap(&pdev->dev, res); if (!host->ioaddr) { @@ -745,9 +747,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) return 0; err_req_regs: + clk_disable(sc->clk_bus[sc->cur_clk]); for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { if (sc->clk_bus[ptr]) { - clk_disable(sc->clk_bus[ptr]); clk_put(sc->clk_bus[ptr]); } } @@ -788,9 +790,9 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + clk_disable(sc->clk_bus[sc->cur_clk]); for (ptr = 0; ptr < 3; ptr++) { if (sc->clk_bus[ptr]) { - clk_disable(sc->clk_bus[ptr]); clk_put(sc->clk_bus[ptr]); } } -- cgit v0.10.2 From 387a8cbdf84d5c417d24eabfdf9fe4e0b94458da Mon Sep 17 00:00:00 2001 From: Chander Kashyap Date: Fri, 14 Sep 2012 09:08:50 +0000 Subject: mmc: sdhci-s3c: Fix crash on module insertion for second time If sdhci-s3c driver is built as module, it gives following error if inserted again after removing. This was happening as pm_runtime_use_autosuspend() is called in sdhci_s3c_probe() function but in sdhci_s3c_remove() its complementary pm_runtime_dont_use_autosuspend() is not called. BUG: spinlock bad magic on CPU#1, insmod/955 lock: 0xee771368, .magic: 00000000, .owner: insmod/955, .owner_cpu: 1 [] (unwind_backtrace+0x0/0xf8) from [] (do_raw_spin_unlock+0xa4/0xe4) [] (do_raw_spin_unlock+0xa4/0xe4) from [] (_raw_spin_unlock_irqrestore+0xc/0x38) [] (_raw_spin_unlock_irqrestore+0xc/0x38) from [] (sdhci_runtime_suspend_host+0x54/0x80) [] (sdhci_runtime_suspend_host+0x54/0x80) from [] (sdhci_s3c_runtime_suspend+0x14/0x38 [sdhci_s3c]) [] (sdhci_s3c_runtime_suspend+0x14/0x38 [sdhci_s3c]) from [] (pm_generic_runtime_suspend+0x2c/0x40) [] (pm_generic_runtime_suspend+0x2c/0x40) from [] (__rpm_callback+0x70/0x98) [] (__rpm_callback+0x70/0x98) from [] (rpm_suspend+0xf0/0x534) [] (rpm_suspend+0xf0/0x534) from [] (__pm_runtime_suspend+0x5c/0x74) [] (__pm_runtime_suspend+0x5c/0x74) from [] (pm_generic_runtime_idle+0x44/0x4c) [] (pm_generic_runtime_idle+0x44/0x4c) from [] (__rpm_callback+0x70/0x98) [] (__rpm_callback+0x70/0x98) from [] (rpm_idle+0xdc/0x18c) [] (rpm_idle+0xdc/0x18c) from [] (pm_runtime_set_autosuspend_delay+0x30/0x3c) [] (pm_runtime_set_autosuspend_delay+0x30/0x3c) from [] (sdhci_s3c_probe+0x35c/0x52c [sdhci_s3c]) [] (sdhci_s3c_probe+0x35c/0x52c [sdhci_s3c]) from [] (platform_drv_probe+0x18/0x1c) Signed-off-by: Chander Kashyap Acked-by: Jaehoon Chung Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index 4d9acf8..f64108f 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -788,6 +788,7 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) sdhci_remove_host(host, 1); + pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); clk_disable(sc->clk_bus[sc->cur_clk]); -- cgit v0.10.2 From 7a7eb3286b8e078d6f5de56a2e26a3bb248085eb Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 18 Sep 2012 23:10:24 +0000 Subject: mmc: sh-mmcif: properly handle MMC_WRITE_MULTIPLE_BLOCK completion IRQ Upon completion of a MMC_WRITE_MULTIPLE_BLOCK command MMCIF issues an IRQ with the DTRANE bit set and often with one or several of CMD12 bits set. If those interrupts are not acknowledged, an additional interrupt can be produced and will be delivered later, possibly, when the transaction has already been completed. To prevent this from happening, CMD12 completion interrupt sources have to be cleared too upon reception of an DTRANE IRQ. Signed-off-by: Guennadi Liakhovetski Tested-by: Tetsuyuki Kobayashi Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 5d81427..6df3dc3 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1213,7 +1213,9 @@ static irqreturn_t sh_mmcif_intr(int irq, void *dev_id) sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_BUFRE); sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MBUFRE); } else if (state & INT_DTRANE) { - sh_mmcif_writel(host->addr, MMCIF_CE_INT, ~INT_DTRANE); + sh_mmcif_writel(host->addr, MMCIF_CE_INT, + ~(INT_CMD12DRE | INT_CMD12RBE | + INT_CMD12CRE | INT_DTRANE)); sh_mmcif_bitclr(host, MMCIF_CE_INT_MASK, MASK_MDTRANE); } else if (state & INT_CMD12RBE) { sh_mmcif_writel(host->addr, MMCIF_CE_INT, -- cgit v0.10.2 From 8464dd52d3198dd05cafb005371d76e5339eb842 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 18 Sep 2012 06:42:42 +0000 Subject: mmc: sh-mmcif: avoid oops on spurious interrupts On some systems, e.g., kzm9g, MMCIF interfaces can produce spurious interrupts without any active request. To prevent the Oops, that results in such cases, don't dereference the mmc request pointer until we make sure, that we are indeed processing such a request. Reported-by: Tetsuyuki Kobayashi Signed-off-by: Guennadi Liakhovetski Cc: stable Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 6df3dc3..11d2bc3 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -1231,6 +1231,10 @@ static irqreturn_t sh_mmcif_intr(int irq, void *dev_id) host->sd_error = true; dev_dbg(&host->pd->dev, "int err state = %08x\n", state); } + if (host->state == STATE_IDLE) { + dev_info(&host->pd->dev, "Spurious IRQ status 0x%x", state); + return IRQ_HANDLED; + } if (state & ~(INT_CMD12RBE | INT_CMD12CRE)) { if (!host->dma_active) return IRQ_WAKE_THREAD; -- cgit v0.10.2 From 5feb54a1ab91a237e247c013b8c4fb100ea347b1 Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Wed, 19 Sep 2012 14:43:33 +0800 Subject: mmc: sdhci-s3c: fix the wrong number of max bus clocks We can use up to four bus-clocks; but on module remove, we didn't disable the fourth bus clock. Signed-off-by: Jaehoon Chung Signed-off-by: Kyungmin Park Cc: stable Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index f64108f..e019672 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -792,7 +792,7 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); clk_disable(sc->clk_bus[sc->cur_clk]); - for (ptr = 0; ptr < 3; ptr++) { + for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { if (sc->clk_bus[ptr]) { clk_put(sc->clk_bus[ptr]); } -- cgit v0.10.2 From 38ca7248054b6fd92a6a49963beb87f3746115ec Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Sat, 15 Sep 2012 12:20:00 +0800 Subject: mmc: dt: Support "broken-cd" property in sdhci-pltfm Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c index d9a4ef4..65551a9 100644 --- a/drivers/mmc/host/sdhci-pltfm.c +++ b/drivers/mmc/host/sdhci-pltfm.c @@ -75,6 +75,9 @@ void sdhci_get_of_property(struct platform_device *pdev) if (sdhci_of_wp_inverted(np)) host->quirks |= SDHCI_QUIRK_INVERTED_WRITE_PROTECT; + if (of_get_property(np, "broken-cd", NULL)) + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + if (of_device_is_compatible(np, "fsl,p2020-rev1-esdhc")) host->quirks |= SDHCI_QUIRK_BROKEN_DMA; -- cgit v0.10.2 From f4f24ade1b24ec09981b5506e3a9a19a96055435 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Mon, 17 Sep 2012 14:40:15 +0800 Subject: mmc: sdhci-pxav3: Use sdhci_get_of_property for parsing DT quirks Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-pxav3.c b/drivers/mmc/host/sdhci-pxav3.c index c5ff124..e918a2b 100644 --- a/drivers/mmc/host/sdhci-pxav3.c +++ b/drivers/mmc/host/sdhci-pxav3.c @@ -290,6 +290,8 @@ static int __devinit sdhci_pxav3_probe(struct platform_device *pdev) host->ops = &pxav3_sdhci_ops; + sdhci_get_of_property(pdev); + ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "failed to add host\n"); -- cgit v0.10.2 From 9cde5b7a49a88939d648c361ec938fa8a633a045 Mon Sep 17 00:00:00 2001 From: Chris Ball Date: Wed, 19 Sep 2012 22:27:04 +0800 Subject: mmc: core: Replace MMC_CAP2_BROKEN_VOLTAGE with test for fixed regulator Before this patch, we were using MMC_CAP2_BROKEN_VOLTAGE as a way to avoid calling regulator_set_voltage() on a fixed regulator, but that's just duplicating information that already exists -- we should test whether the regulator is fixed directly, instead of via a capability. This patch implements that test. We can't reclaim the capability bit just yet, since there are still boards in arch/arm/ that reference it; those references can be removed now. Reported-by: Tomasz Figa Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 044cd01..6612163 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1113,7 +1113,8 @@ int mmc_regulator_set_ocr(struct mmc_host *mmc, int tmp; int voltage; - /* REVISIT mmc_vddrange_to_ocrmask() may have set some + /* + * REVISIT mmc_vddrange_to_ocrmask() may have set some * bits this regulator doesn't quite support ... don't * be too picky, most cards and regulators are OK with * a 0.1V range goof (it's a small error percentage). @@ -1127,12 +1128,13 @@ int mmc_regulator_set_ocr(struct mmc_host *mmc, max_uV = min_uV + 100 * 1000; } - /* avoid needless changes to this voltage; the regulator - * might not allow this operation + /* + * If we're using a fixed/static regulator, don't call + * regulator_set_voltage; it would fail. */ voltage = regulator_get_voltage(supply); - if (mmc->caps2 & MMC_CAP2_BROKEN_VOLTAGE) + if (regulator_count_voltages(supply) == 1) min_uV = max_uV = voltage; if (voltage < 0) -- cgit v0.10.2 From 2abeb5c5ded2e7f7d288058426fb0ae852adc77f Mon Sep 17 00:00:00 2001 From: Chander Kashyap Date: Fri, 21 Sep 2012 05:42:08 +0000 Subject: mmc: sdhci-s3c: Add clk_(enable/disable) in runtime suspend/resume Perform clock disable/enable in runtime suspend/resume. Signed-off-by: Chander Kashyap Acked-by: Jaehoon Chung Signed-off-by: Chris Ball diff --git a/drivers/mmc/host/sdhci-s3c.c b/drivers/mmc/host/sdhci-s3c.c index e019672..2903949 100644 --- a/drivers/mmc/host/sdhci-s3c.c +++ b/drivers/mmc/host/sdhci-s3c.c @@ -637,7 +637,9 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) goto err_no_busclks; } +#ifndef CONFIG_PM_RUNTIME clk_enable(sc->clk_bus[sc->cur_clk]); +#endif res = platform_get_resource(pdev, IORESOURCE_MEM, 0); host->ioaddr = devm_request_and_ioremap(&pdev->dev, res); @@ -744,10 +746,15 @@ static int __devinit sdhci_s3c_probe(struct platform_device *pdev) gpio_is_valid(pdata->ext_cd_gpio)) sdhci_s3c_setup_card_detect_gpio(sc); +#ifdef CONFIG_PM_RUNTIME + clk_disable(sc->clk_io); +#endif return 0; err_req_regs: +#ifndef CONFIG_PM_RUNTIME clk_disable(sc->clk_bus[sc->cur_clk]); +#endif for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { if (sc->clk_bus[ptr]) { clk_put(sc->clk_bus[ptr]); @@ -786,12 +793,17 @@ static int __devexit sdhci_s3c_remove(struct platform_device *pdev) if (gpio_is_valid(sc->ext_cd_gpio)) gpio_free(sc->ext_cd_gpio); +#ifdef CONFIG_PM_RUNTIME + clk_enable(sc->clk_io); +#endif sdhci_remove_host(host, 1); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); +#ifndef CONFIG_PM_RUNTIME clk_disable(sc->clk_bus[sc->cur_clk]); +#endif for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { if (sc->clk_bus[ptr]) { clk_put(sc->clk_bus[ptr]); @@ -831,15 +843,28 @@ static int sdhci_s3c_resume(struct device *dev) static int sdhci_s3c_runtime_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_s3c *ourhost = to_s3c(host); + struct clk *busclk = ourhost->clk_io; + int ret; + + ret = sdhci_runtime_suspend_host(host); - return sdhci_runtime_suspend_host(host); + clk_disable(ourhost->clk_bus[ourhost->cur_clk]); + clk_disable(busclk); + return ret; } static int sdhci_s3c_runtime_resume(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_s3c *ourhost = to_s3c(host); + struct clk *busclk = ourhost->clk_io; + int ret; - return sdhci_runtime_resume_host(host); + clk_enable(busclk); + clk_enable(ourhost->clk_bus[ourhost->cur_clk]); + ret = sdhci_runtime_resume_host(host); + return ret; } #endif -- cgit v0.10.2 From 067bf748bde70154a1fe1734b01f82b827c65b23 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 28 Sep 2012 15:58:22 +0530 Subject: mmc: sdhci-spear: add device tree bindings This adds simple DT bindings for SDHCI SPEAr controller. It uses cd-gpios from common mmc bindings. This also fixes spear300-evb.dts with correct name for card detect binding. Signed-off-by: Viresh Kumar Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/sdhci-spear.txt b/Documentation/devicetree/bindings/mmc/sdhci-spear.txt new file mode 100644 index 0000000..84d6e29 --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/sdhci-spear.txt @@ -0,0 +1,21 @@ +* SPEAr SDHCI Controller + +This file documents differences between the core properties in mmc.txt +and the properties used by the sdhci-spear driver. + +Required properties: +- compatible: "st,spear300-sdhci" + +Optional properties: +- cd-gpios: card detect gpio, with zero flags. + +If your board don't support these gpios then don't pass the entry. + +Example: + + sdhci@fc000000 { + compatible = "st,spear300-sdhci"; + reg = <0xfc000000 0x1000>; + + cd-gpios = <&gpio0 6 0> + }; diff --git a/arch/arm/boot/dts/spear300-evb.dts b/arch/arm/boot/dts/spear300-evb.dts index d71b8d5..1e7c7a8 100644 --- a/arch/arm/boot/dts/spear300-evb.dts +++ b/arch/arm/boot/dts/spear300-evb.dts @@ -80,8 +80,7 @@ }; sdhci@70000000 { - int-gpio = <&gpio1 0 0>; - power-gpio = <&gpio1 2 1>; + cd-gpios = <&gpio1 0 0>; status = "okay"; }; diff --git a/arch/arm/boot/dts/spear320-evb.dts b/arch/arm/boot/dts/spear320-evb.dts index e4e912f..082328b 100644 --- a/arch/arm/boot/dts/spear320-evb.dts +++ b/arch/arm/boot/dts/spear320-evb.dts @@ -103,8 +103,6 @@ }; sdhci@70000000 { - power-gpio = <&gpio0 2 1>; - power_always_enb; status = "okay"; }; diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index 423da81..ecad282 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -68,8 +70,42 @@ static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id) return IRQ_HANDLED; } +#ifdef CONFIG_OF +static struct sdhci_plat_data * __devinit +sdhci_probe_config_dt(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sdhci_plat_data *pdata = NULL; + int cd_gpio; + + cd_gpio = of_get_named_gpio(np, "cd-gpios", 0); + if (!gpio_is_valid(cd_gpio)) + cd_gpio = -1; + + /* If pdata is required */ + if (cd_gpio != -1) { + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "DT: kzalloc failed\n"); + return ERR_PTR(-ENOMEM); + } + } + + pdata->card_int_gpio = cd_gpio; + + return pdata; +} +#else +static struct sdhci_plat_data * __devinit +sdhci_probe_config_dt(struct platform_device *pdev) +{ + return ERR_PTR(-ENOSYS); +} +#endif + static int __devinit sdhci_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct sdhci_host *host; struct resource *iomem; struct spear_sdhci *sdhci; @@ -110,8 +146,16 @@ static int __devinit sdhci_probe(struct platform_device *pdev) goto put_clk; } - /* overwrite platform_data */ - sdhci->data = dev_get_platdata(&pdev->dev); + if (np) { + sdhci->data = sdhci_probe_config_dt(pdev); + if (IS_ERR(sdhci->data)) { + dev_err(&pdev->dev, "DT: Failed to get pdata\n"); + return -ENODEV; + } + } else { + sdhci->data = dev_get_platdata(&pdev->dev); + } + pdev->dev.platform_data = sdhci; if (pdev->dev.parent) @@ -276,11 +320,20 @@ static int sdhci_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(sdhci_pm_ops, sdhci_suspend, sdhci_resume); +#ifdef CONFIG_OF +static const struct of_device_id sdhci_spear_id_table[] = { + { .compatible = "st,spear300-sdhci" }, + {} +}; +MODULE_DEVICE_TABLE(of, sdhci_spear_id_table); +#endif + static struct platform_driver sdhci_driver = { .driver = { .name = "sdhci", .owner = THIS_MODULE, .pm = &sdhci_pm_ops, + .of_match_table = of_match_ptr(sdhci_spear_id_table), }, .probe = sdhci_probe, .remove = __devexit_p(sdhci_remove), -- cgit v0.10.2 From da764f97df0e5e05e5453279f934531c0cf52e11 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 28 Sep 2012 15:58:23 +0530 Subject: mmc: sdhci-spear: Add clk_{un}prepare() support clk_{un}prepare is mandatory for platforms using common clock framework. Since this driver is used by SPEAr platform, which supports common clock framework, add clk_{un}prepare() support for it. Signed-off-by: Viresh Kumar Signed-off-by: Chris Ball diff --git a/Documentation/devicetree/bindings/mmc/sdhci-spear.txt b/Documentation/devicetree/bindings/mmc/sdhci-spear.txt index 84d6e29..fd3643e 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-spear.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-spear.txt @@ -9,13 +9,10 @@ Required properties: Optional properties: - cd-gpios: card detect gpio, with zero flags. -If your board don't support these gpios then don't pass the entry. - Example: sdhci@fc000000 { compatible = "st,spear300-sdhci"; reg = <0xfc000000 0x1000>; - - cd-gpios = <&gpio0 6 0> + cd-gpios = <&gpio0 6 0>; }; diff --git a/drivers/mmc/host/sdhci-spear.c b/drivers/mmc/host/sdhci-spear.c index ecad282..6be89c0 100644 --- a/drivers/mmc/host/sdhci-spear.c +++ b/drivers/mmc/host/sdhci-spear.c @@ -140,7 +140,7 @@ static int __devinit sdhci_probe(struct platform_device *pdev) goto err; } - ret = clk_enable(sdhci->clk); + ret = clk_prepare_enable(sdhci->clk); if (ret) { dev_dbg(&pdev->dev, "Error enabling clock\n"); goto put_clk; @@ -260,7 +260,7 @@ set_drvdata: free_host: sdhci_free_host(host); disable_clk: - clk_disable(sdhci->clk); + clk_disable_unprepare(sdhci->clk); put_clk: clk_put(sdhci->clk); err: @@ -282,7 +282,7 @@ static int __devexit sdhci_remove(struct platform_device *pdev) sdhci_remove_host(host, dead); sdhci_free_host(host); - clk_disable(sdhci->clk); + clk_disable_unprepare(sdhci->clk); clk_put(sdhci->clk); return 0; @@ -297,7 +297,7 @@ static int sdhci_suspend(struct device *dev) ret = sdhci_suspend_host(host); if (!ret) - clk_disable(sdhci->clk); + clk_disable_unprepare(sdhci->clk); return ret; } @@ -308,7 +308,7 @@ static int sdhci_resume(struct device *dev) struct spear_sdhci *sdhci = dev_get_platdata(dev); int ret; - ret = clk_enable(sdhci->clk); + ret = clk_prepare_enable(sdhci->clk); if (ret) { dev_dbg(dev, "Resume: Error enabling clock\n"); return ret; -- cgit v0.10.2 From e6c085863f97f0a8f009753e1baaf83e4aac7b42 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Fri, 5 Oct 2012 12:45:39 -0400 Subject: mmc: core: Fixup broken suspend and eMMC4.5 power off notify This patch fixes up the broken suspend sequence for eMMC with sleep support. Additionally it reworks the eMMC4.5 Power Off Notification feature so it fits together with the existing sleep feature. The CMD0 based re-initialization of the eMMC at resume is re-introduced to maintain compatiblity for devices using sleep. A host shall use MMC_CAP2_POWEROFF_NOTIFY to enable the Power Off Notification feature. We might be able to remove this cap later on, if we think that Power Off Notification always is preferred over sleep, even if the host is not able to cut the eMMC VCCQ power. Signed-off-by: Ulf Hansson Signed-off-by: Saugata Das Acked-by: Linus Walleij Signed-off-by: Chris Ball diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 6612163..06c42cf 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1273,48 +1273,6 @@ void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type) mmc_host_clk_release(host); } -static void mmc_poweroff_notify(struct mmc_host *host) -{ - struct mmc_card *card; - unsigned int timeout; - unsigned int notify_type = EXT_CSD_NO_POWER_NOTIFICATION; - int err = 0; - - card = host->card; - mmc_claim_host(host); - - /* - * Send power notify command only if card - * is mmc and notify state is powered ON - */ - if (card && mmc_card_mmc(card) && - (card->poweroff_notify_state == MMC_POWERED_ON)) { - - if (host->power_notify_type == MMC_HOST_PW_NOTIFY_SHORT) { - notify_type = EXT_CSD_POWER_OFF_SHORT; - timeout = card->ext_csd.generic_cmd6_time; - card->poweroff_notify_state = MMC_POWEROFF_SHORT; - } else { - notify_type = EXT_CSD_POWER_OFF_LONG; - timeout = card->ext_csd.power_off_longtime; - card->poweroff_notify_state = MMC_POWEROFF_LONG; - } - - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_POWER_OFF_NOTIFICATION, - notify_type, timeout); - - if (err && err != -EBADMSG) - pr_err("Device failed to respond within %d poweroff " - "time. Forcefully powering down the device\n", - timeout); - - /* Set the card state to no notification after the poweroff */ - card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION; - } - mmc_release_host(host); -} - /* * Apply power to the MMC stack. This is a two-stage process. * First, we enable power to the card without the clock running. @@ -1377,8 +1335,6 @@ static void mmc_power_up(struct mmc_host *host) void mmc_power_off(struct mmc_host *host) { - int err = 0; - if (host->ios.power_mode == MMC_POWER_OFF) return; @@ -1387,22 +1343,6 @@ void mmc_power_off(struct mmc_host *host) host->ios.clock = 0; host->ios.vdd = 0; - /* - * For eMMC 4.5 device send AWAKE command before - * POWER_OFF_NOTIFY command, because in sleep state - * eMMC 4.5 devices respond to only RESET and AWAKE cmd - */ - if (host->card && mmc_card_is_sleep(host->card) && - host->bus_ops->resume) { - err = host->bus_ops->resume(host); - - if (!err) - mmc_poweroff_notify(host); - else - pr_warning("%s: error %d during resume " - "(continue with poweroff sequence)\n", - mmc_hostname(host), err); - } /* * Reset ocr mask to be the highest possible voltage supported for @@ -2579,7 +2519,6 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 1; - host->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; spin_unlock_irqrestore(&host->lock, flags); cancel_delayed_work_sync(&host->detect); @@ -2603,7 +2542,6 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 0; - host->power_notify_type = MMC_HOST_PW_NOTIFY_LONG; spin_unlock_irqrestore(&host->lock, flags); mmc_detect_change(host, 0); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 7509de1..7cc4638 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1007,7 +1007,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * so check for success and update the flag */ if (!err) - card->poweroff_notify_state = MMC_POWERED_ON; + card->ext_csd.power_off_notification = EXT_CSD_POWER_ON; } /* @@ -1273,6 +1273,35 @@ err: return err; } +static int mmc_can_poweroff_notify(const struct mmc_card *card) +{ + return card && + mmc_card_mmc(card) && + (card->ext_csd.power_off_notification == EXT_CSD_POWER_ON); +} + +static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) +{ + unsigned int timeout = card->ext_csd.generic_cmd6_time; + int err; + + /* Use EXT_CSD_POWER_OFF_SHORT as default notification type. */ + if (notify_type == EXT_CSD_POWER_OFF_LONG) + timeout = card->ext_csd.power_off_longtime; + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_OFF_NOTIFICATION, + notify_type, timeout); + if (err) + pr_err("%s: Power Off Notification timed out, %u\n", + mmc_hostname(card->host), timeout); + + /* Disable the power off notification after the switch operation. */ + card->ext_csd.power_off_notification = EXT_CSD_NO_POWER_NOTIFICATION; + + return err; +} + /* * Host is being removed. Free up the current card. */ @@ -1333,11 +1362,11 @@ static int mmc_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - if (mmc_card_can_sleep(host)) { + if (mmc_can_poweroff_notify(host->card)) + err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_SHORT); + else if (mmc_card_can_sleep(host)) err = mmc_card_sleep(host); - if (!err) - mmc_card_set_sleep(host->card); - } else if (!mmc_host_is_spi(host)) + else if (!mmc_host_is_spi(host)) err = mmc_deselect_cards(host); host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); mmc_release_host(host); @@ -1359,11 +1388,7 @@ static int mmc_resume(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - if (mmc_card_is_sleep(host->card)) { - err = mmc_card_awake(host); - mmc_card_clr_sleep(host->card); - } else - err = mmc_init_card(host, host->ocr, host->card); + err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); return err; @@ -1374,7 +1399,6 @@ static int mmc_power_restore(struct mmc_host *host) int ret; host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_200); - mmc_card_clr_sleep(host->card); mmc_claim_host(host); ret = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index a23af77..c2828f3 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1885,11 +1885,6 @@ static int dw_mci_init_slot(struct dw_mci *host, unsigned int id) if (host->pdata->quirks & DW_MCI_QUIRK_HIGHSPEED) mmc->caps |= MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED; - if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; - else - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; - if (host->pdata->blk_settings) { mmc->max_segs = host->pdata->blk_settings->max_segs; mmc->max_blk_size = host->pdata->blk_settings->max_blk_size; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0e15c79..7922adb 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2886,15 +2886,6 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[1] & SDHCI_DRIVER_TYPE_D) mmc->caps |= MMC_CAP_DRIVER_TYPE_D; - /* - * If Power Off Notify capability is enabled by the host, - * set notify to short power off notify timeout value. - */ - if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; - else - mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; - /* Initial value for re-tuning timer count */ host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >> SDHCI_RETUNING_TIMER_COUNT_SHIFT; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 78cc3be..943550d 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -57,6 +57,7 @@ struct mmc_ext_csd { unsigned int sa_timeout; /* Units: 100ns */ unsigned int generic_cmd6_time; /* Units: 10ms */ unsigned int power_off_longtime; /* Units: ms */ + u8 power_off_notification; /* state */ unsigned int hs_max_dtr; #define MMC_HIGH_26_MAX_DTR 26000000 #define MMC_HIGH_52_MAX_DTR 52000000 @@ -229,7 +230,6 @@ struct mmc_card { #define MMC_CARD_SDXC (1<<6) /* card is SDXC */ #define MMC_CARD_REMOVED (1<<7) /* card has been removed */ #define MMC_STATE_HIGHSPEED_200 (1<<8) /* card is in HS200 mode */ -#define MMC_STATE_SLEEP (1<<9) /* card is in sleep state */ #define MMC_STATE_DOING_BKOPS (1<<10) /* card is doing BKOPS */ unsigned int quirks; /* card quirks */ #define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ @@ -246,11 +246,6 @@ struct mmc_card { #define MMC_QUIRK_LONG_READ_TIME (1<<9) /* Data read time > CSD says */ #define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim */ /* byte mode */ - unsigned int poweroff_notify_state; /* eMMC4.5 notify feature */ -#define MMC_NO_POWER_NOTIFICATION 0 -#define MMC_POWERED_ON 1 -#define MMC_POWEROFF_SHORT 2 -#define MMC_POWEROFF_LONG 3 unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ @@ -397,7 +392,6 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_sd_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED) #define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC) #define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED)) -#define mmc_card_is_sleep(c) ((c)->state & MMC_STATE_SLEEP) #define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) @@ -410,11 +404,9 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_sd_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED) #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC) #define mmc_card_set_removed(c) ((c)->state |= MMC_CARD_REMOVED) -#define mmc_card_set_sleep(c) ((c)->state |= MMC_STATE_SLEEP) #define mmc_card_set_doing_bkops(c) ((c)->state |= MMC_STATE_DOING_BKOPS) - #define mmc_card_clr_doing_bkops(c) ((c)->state &= ~MMC_STATE_DOING_BKOPS) -#define mmc_card_clr_sleep(c) ((c)->state &= ~MMC_STATE_SLEEP) + /* * Quirk add/remove for MMC products. */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index d5d9bd4..7abb0e1 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -259,10 +259,6 @@ struct mmc_host { #define MMC_CAP2_RO_ACTIVE_HIGH (1 << 11) /* Write-protect signal active high */ mmc_pm_flag_t pm_caps; /* supported pm features */ - unsigned int power_notify_type; -#define MMC_HOST_PW_NOTIFY_NONE 0 -#define MMC_HOST_PW_NOTIFY_SHORT 1 -#define MMC_HOST_PW_NOTIFY_LONG 2 #ifdef CONFIG_MMC_CLKGATE int clk_requests; /* internal reference counter */ -- cgit v0.10.2