From fffe5d5aa05b4e69f79bc75a51c5ee0fc6203fa5 Mon Sep 17 00:00:00 2001 From: Qiang Liu Date: Tue, 8 Nov 2011 08:43:08 -0500 Subject: mmc: sd: Macro name cleanup for high speed dtr Add new macros for the high speed 50MHz case, rather than having a confusing reuse of the value for UHS SDR50, which is 100MHz. Reported-by: Aaron Lu Signed-off-by: Chris Ball --- include/linux/mmc/card.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include/linux') diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index c8ef9bc54d50..2c9be29684cf 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -110,6 +110,7 @@ struct sd_ssr { struct sd_switch_caps { unsigned int hs_max_dtr; unsigned int uhs_max_dtr; +#define HIGH_SPEED_MAX_DTR 50000000 #define UHS_SDR104_MAX_DTR 208000000 #define UHS_SDR50_MAX_DTR 100000000 #define UHS_DDR50_MAX_DTR 50000000 @@ -117,11 +118,13 @@ struct sd_switch_caps { #define UHS_SDR12_MAX_DTR 25000000 unsigned int sd3_bus_mode; #define UHS_SDR12_BUS_SPEED 0 +#define HIGH_SPEED_BUS_SPEED 1 #define UHS_SDR25_BUS_SPEED 1 #define UHS_SDR50_BUS_SPEED 2 #define UHS_SDR104_BUS_SPEED 3 #define UHS_DDR50_BUS_SPEED 4 +#define SD_MODE_HIGH_SPEED (1 << HIGH_SPEED_BUS_SPEED) #define SD_MODE_UHS_SDR12 (1 << UHS_SDR12_BUS_SPEED) #define SD_MODE_UHS_SDR25 (1 << UHS_SDR25_BUS_SPEED) #define SD_MODE_UHS_SDR50 (1 << UHS_SDR50_BUS_SPEED) -- cgit v1.2.3 From df16219f365f7f5a2d88a6e123251d57255cca3f Mon Sep 17 00:00:00 2001 From: Giuseppe CAVALLARO Date: Fri, 4 Nov 2011 13:53:19 +0100 Subject: mmc: debugfs: expose the SDCLK frq in sys ios This patch is to expose the actual SDCLK frequency in /sys/kernel/debug/mmcX/ios entry. For example, if the max clk for a normal speed card is 20MHz this is reported in /sys/kernel/debug/mmcX/ios. Unfortunately the actual SDCLK frequency (i.e. Baseclock / divisor) is not reported at all: for example, in that case, on Arasan HC, it should be 48/4=12 (MHz). Signed-off-by: Giuseppe Cavallaro Acked-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/core/debugfs.c | 2 ++ drivers/mmc/host/sdhci.c | 10 ++++++++++ include/linux/mmc/host.h | 2 ++ 3 files changed, 14 insertions(+) (limited to 'include/linux') diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 3923880118b6..027615d3bf3e 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -57,6 +57,8 @@ static int mmc_ios_show(struct seq_file *s, void *data) const char *str; seq_printf(s, "clock:\t\t%u Hz\n", ios->clock); + if (host->actual_clock) + seq_printf(s, "actual clock:\t%u Hz\n", host->actual_clock); seq_printf(s, "vdd:\t\t%u ", ios->vdd); if ((1 << ios->vdd) & MMC_VDD_165_195) seq_printf(s, "(1.65 - 1.95 V)\n"); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 19ed580f2cab..a7c23118dab2 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1066,12 +1066,15 @@ static void sdhci_finish_command(struct sdhci_host *host) static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) { int div = 0; /* Initialized for compiler warning */ + int real_div = div, clk_mul = 1; u16 clk = 0; unsigned long timeout; if (clock == host->clock) return; + host->mmc->actual_clock = 0; + if (host->ops->set_clock) { host->ops->set_clock(host, clock); if (host->quirks & SDHCI_QUIRK_NONSTANDARD_CLOCK) @@ -1109,6 +1112,8 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) * Control register. */ clk = SDHCI_PROG_CLOCK_MODE; + real_div = div; + clk_mul = host->clk_mul; div--; } } else { @@ -1122,6 +1127,7 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) break; } } + real_div = div; div >>= 1; } } else { @@ -1130,9 +1136,13 @@ static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) if ((host->max_clk / div) <= clock) break; } + real_div = div; div >>= 1; } + if (real_div) + host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div; + clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT; clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN) << SDHCI_DIVIDER_HI_SHIFT; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index a3ac9c48e5de..cea064f73514 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -323,6 +323,8 @@ struct mmc_host { struct fault_attr fail_mmc_request; #endif + unsigned int actual_clock; /* Actual HC clock rate */ + unsigned long private[0] ____cacheline_aligned; }; -- cgit v1.2.3 From 597dd9d79cfbbb1636d00a7fd0880355d9b20c41 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Mon, 14 Nov 2011 13:53:29 +0530 Subject: mmc: core: Use delayed work in clock gating framework Current clock gating framework disables the MCI clock as soon as the request is completed and enables it when a request arrives. This aggressive clock gating framework, when enabled, cause following issues: When there are back-to-back requests from the Queue layer, we unnecessarily end up disabling and enabling the clocks between these requests since 8MCLK clock cycles is a very short duration compared to the time delay between back to back requests reaching the MMC layer. This overhead can effect the overall performance depending on how long the clock enable and disable calls take which is platform dependent. For example on some platforms we can have clock control not on the local processor, but on a different subsystem and the time taken to perform the clock enable/disable can add significant overhead. Also if the host controller driver decides to disable the host clock too when mmc_set_ios function is called with ios.clock=0, it adds additional delay and it is highly possible that the next request had already arrived and unnecessarily blocked in enabling the clocks. This is seen frequently when the processor is executing at high speeds and in multi-core platforms thus reduces the overall throughput compared to if clock gating is disabled. Fix this by delaying turning off the clocks by posting request on delayed workqueue. Also cancel the unscheduled pending work, if any, when there is access to card. sysfs entry is provided to tune the delay as needed, default value set to 200ms. Signed-off-by: Sujit Reddy Thumma Acked-by: Linus Walleij Signed-off-by: Chris Ball --- Documentation/mmc/mmc-dev-attrs.txt | 10 +++++++ drivers/mmc/core/host.c | 57 ++++++++++++++++++++++++++++++++++--- include/linux/mmc/host.h | 4 ++- 3 files changed, 66 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index 8898a95b41e5..b0245565875a 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -64,3 +64,13 @@ Note on Erase Size and Preferred Erase Size: size specified by the card. "preferred_erase_size" is in bytes. + +SD/MMC/SDIO Clock Gating Attribute +================================== + +Read and write access is provided to following attribute. +This attribute appears only if CONFIG_MMC_CLKGATE is enabled. + + clkgate_delay Tune the clock gating delay with desired value in milli seconds. + +echo > /sys/class/mmc_host/mmcX/clkgate_delay diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index d31c78b72b0f..817a76039743 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -54,6 +54,31 @@ static DEFINE_IDR(mmc_host_idr); static DEFINE_SPINLOCK(mmc_host_lock); #ifdef CONFIG_MMC_CLKGATE +static ssize_t clkgate_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + return snprintf(buf, PAGE_SIZE, "%lu millisecs\n", + host->clkgate_delay); +} + +static ssize_t clkgate_delay_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + unsigned long flags, value; + + if (kstrtoul(buf, 0, &value)) + return -EINVAL; + + spin_lock_irqsave(&host->clk_lock, flags); + host->clkgate_delay = value; + spin_unlock_irqrestore(&host->clk_lock, flags); + + pr_info("%s: clock gate delay set to %lu ms\n", + mmc_hostname(host), value); + return count; +} /* * Enabling clock gating will make the core call out to the host @@ -114,7 +139,7 @@ static void mmc_host_clk_gate_delayed(struct mmc_host *host) static void mmc_host_clk_gate_work(struct work_struct *work) { struct mmc_host *host = container_of(work, struct mmc_host, - clk_gate_work); + clk_gate_work.work); mmc_host_clk_gate_delayed(host); } @@ -131,6 +156,8 @@ void mmc_host_clk_hold(struct mmc_host *host) { unsigned long flags; + /* cancel any clock gating work scheduled by mmc_host_clk_release() */ + cancel_delayed_work_sync(&host->clk_gate_work); mutex_lock(&host->clk_gate_mutex); spin_lock_irqsave(&host->clk_lock, flags); if (host->clk_gated) { @@ -180,7 +207,8 @@ void mmc_host_clk_release(struct mmc_host *host) host->clk_requests--; if (mmc_host_may_gate_card(host->card) && !host->clk_requests) - queue_work(system_nrt_wq, &host->clk_gate_work); + queue_delayed_work(system_nrt_wq, &host->clk_gate_work, + msecs_to_jiffies(host->clkgate_delay)); spin_unlock_irqrestore(&host->clk_lock, flags); } @@ -213,8 +241,13 @@ static inline void mmc_host_clk_init(struct mmc_host *host) host->clk_requests = 0; /* Hold MCI clock for 8 cycles by default */ host->clk_delay = 8; + /* + * Default clock gating delay is 200ms. + * This value can be tuned by writing into sysfs entry. + */ + host->clkgate_delay = 200; host->clk_gated = false; - INIT_WORK(&host->clk_gate_work, mmc_host_clk_gate_work); + INIT_DELAYED_WORK(&host->clk_gate_work, mmc_host_clk_gate_work); spin_lock_init(&host->clk_lock); mutex_init(&host->clk_gate_mutex); } @@ -229,7 +262,7 @@ static inline void mmc_host_clk_exit(struct mmc_host *host) * Wait for any outstanding gate and then make sure we're * ungated before exiting. */ - if (cancel_work_sync(&host->clk_gate_work)) + if (cancel_delayed_work_sync(&host->clk_gate_work)) mmc_host_clk_gate_delayed(host); if (host->clk_gated) mmc_host_clk_hold(host); @@ -237,6 +270,17 @@ static inline void mmc_host_clk_exit(struct mmc_host *host) WARN_ON(host->clk_requests > 1); } +static inline void mmc_host_clk_sysfs_init(struct mmc_host *host) +{ + host->clkgate_delay_attr.show = clkgate_delay_show; + host->clkgate_delay_attr.store = clkgate_delay_store; + sysfs_attr_init(&host->clkgate_delay_attr.attr); + host->clkgate_delay_attr.attr.name = "clkgate_delay"; + host->clkgate_delay_attr.attr.mode = S_IRUGO | S_IWUSR; + if (device_create_file(&host->class_dev, &host->clkgate_delay_attr)) + pr_err("%s: Failed to create clkgate_delay sysfs entry\n", + mmc_hostname(host)); +} #else static inline void mmc_host_clk_init(struct mmc_host *host) @@ -247,6 +291,10 @@ static inline void mmc_host_clk_exit(struct mmc_host *host) { } +static inline void mmc_host_clk_sysfs_init(struct mmc_host *host) +{ +} + #endif /** @@ -335,6 +383,7 @@ int mmc_add_host(struct mmc_host *host) #ifdef CONFIG_DEBUG_FS mmc_add_host_debugfs(host); #endif + mmc_host_clk_sysfs_init(host); mmc_start_host(host); register_pm_notifier(&host->pm_notify); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index cea064f73514..706f72279a17 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -253,10 +253,12 @@ struct mmc_host { int clk_requests; /* internal reference counter */ unsigned int clk_delay; /* number of MCI clk hold cycles */ bool clk_gated; /* clock gated */ - struct work_struct clk_gate_work; /* delayed clock gate */ + struct delayed_work clk_gate_work; /* delayed clock gate */ unsigned int clk_old; /* old clock value cache */ spinlock_t clk_lock; /* lock for clk fields */ struct mutex clk_gate_mutex; /* mutex for clock gating */ + struct device_attribute clkgate_delay_attr; + unsigned long clkgate_delay; #endif /* host specific block data */ -- cgit v1.2.3 From a303c5319c8e6ab0e744ebca118da8420043b2c3 Mon Sep 17 00:00:00 2001 From: Philip Rakity Date: Mon, 14 Nov 2011 19:14:38 -0800 Subject: mmc: sdio: support SDIO UHS cards This patch adds support for sdio UHS cards per the version 3.0 spec. UHS mode is only enabled for version 3.0 cards when both the host and the controller support UHS modes. 1.8v signaling support is removed if both the card and the host do not support UHS. This is done to maintain compatibility and some system/card combinations break when 1.8v signaling is enabled when the host does not support UHS. Signed-off-by: Philip Rakity Signed-off-by: Aaron Lu Reviewed-by: Arindam Nath Tested-by: Bing Zhao Signed-off-by: Chris Ball --- drivers/mmc/core/bus.c | 2 +- drivers/mmc/core/sd.c | 2 +- drivers/mmc/core/sdio.c | 329 +++++++++++++++++++++++++++++++++++++++++++---- include/linux/mmc/card.h | 4 +- include/linux/mmc/sdio.h | 29 ++++- 5 files changed, 336 insertions(+), 30 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 6be49249895a..f8a228a61fd4 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -305,7 +305,7 @@ int mmc_add_card(struct mmc_card *card) } else { printk(KERN_INFO "%s: new %s%s%s card at address %04x\n", mmc_hostname(card->host), - mmc_sd_card_uhs(card) ? "ultra high speed " : + mmc_card_uhs(card) ? "ultra high speed " : (mmc_card_highspeed(card) ? "high speed " : ""), mmc_card_ddr_mode(card) ? "DDR " : "", type, card->rca); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index f54392c4638a..85b858f6d5d4 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -960,7 +960,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, goto free_card; /* Card is an ultra-high-speed card */ - mmc_sd_card_set_uhs(card); + mmc_card_set_uhs(card); /* * Since initialization is now complete, enable preset diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 3ab565e32a6a..8c04f7f46dec 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -102,6 +102,7 @@ static int sdio_read_cccr(struct mmc_card *card) int ret; int cccr_vsn; unsigned char data; + unsigned char speed; memset(&card->cccr, 0, sizeof(struct sdio_cccr)); @@ -140,12 +141,60 @@ static int sdio_read_cccr(struct mmc_card *card) } if (cccr_vsn >= SDIO_CCCR_REV_1_20) { - ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data); + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed); if (ret) goto out; - if (data & SDIO_SPEED_SHS) - card->cccr.high_speed = 1; + card->scr.sda_spec3 = 0; + card->sw_caps.sd3_bus_mode = 0; + card->sw_caps.sd3_drv_type = 0; + if (cccr_vsn >= SDIO_CCCR_REV_3_00) { + card->scr.sda_spec3 = 1; + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_CCCR_UHS, 0, &data); + if (ret) + goto out; + + if (card->host->caps & + (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_DDR50)) { + if (data & SDIO_UHS_DDR50) + card->sw_caps.sd3_bus_mode + |= SD_MODE_UHS_DDR50; + + if (data & SDIO_UHS_SDR50) + card->sw_caps.sd3_bus_mode + |= SD_MODE_UHS_SDR50; + + if (data & SDIO_UHS_SDR104) + card->sw_caps.sd3_bus_mode + |= SD_MODE_UHS_SDR104; + } + + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_CCCR_DRIVE_STRENGTH, 0, &data); + if (ret) + goto out; + + if (data & SDIO_DRIVE_SDTA) + card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_A; + if (data & SDIO_DRIVE_SDTC) + card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_C; + if (data & SDIO_DRIVE_SDTD) + card->sw_caps.sd3_drv_type |= SD_DRIVER_TYPE_D; + } + + /* if no uhs mode ensure we check for high speed */ + if (!card->sw_caps.sd3_bus_mode) { + if (speed & SDIO_SPEED_SHS) { + card->cccr.high_speed = 1; + card->sw_caps.hs_max_dtr = 50000000; + } else { + card->cccr.high_speed = 0; + card->sw_caps.hs_max_dtr = 25000000; + } + } } out: @@ -327,6 +376,193 @@ static unsigned mmc_sdio_get_max_clock(struct mmc_card *card) return max_dtr; } +static unsigned char host_drive_to_sdio_drive(int host_strength) +{ + switch (host_strength) { + case MMC_SET_DRIVER_TYPE_A: + return SDIO_DTSx_SET_TYPE_A; + case MMC_SET_DRIVER_TYPE_B: + return SDIO_DTSx_SET_TYPE_B; + case MMC_SET_DRIVER_TYPE_C: + return SDIO_DTSx_SET_TYPE_C; + case MMC_SET_DRIVER_TYPE_D: + return SDIO_DTSx_SET_TYPE_D; + default: + return SDIO_DTSx_SET_TYPE_B; + } +} + +static void sdio_select_driver_type(struct mmc_card *card) +{ + int host_drv_type = SD_DRIVER_TYPE_B; + int card_drv_type = SD_DRIVER_TYPE_B; + int drive_strength; + unsigned char card_strength; + int err; + + /* + * If the host doesn't support any of the Driver Types A,C or D, + * or there is no board specific handler then default Driver + * Type B is used. + */ + if (!(card->host->caps & + (MMC_CAP_DRIVER_TYPE_A | + MMC_CAP_DRIVER_TYPE_C | + MMC_CAP_DRIVER_TYPE_D))) + return; + + if (!card->host->ops->select_drive_strength) + return; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_A) + host_drv_type |= SD_DRIVER_TYPE_A; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_C) + host_drv_type |= SD_DRIVER_TYPE_C; + + if (card->host->caps & MMC_CAP_DRIVER_TYPE_D) + host_drv_type |= SD_DRIVER_TYPE_D; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_A) + card_drv_type |= SD_DRIVER_TYPE_A; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_C) + card_drv_type |= SD_DRIVER_TYPE_C; + + if (card->sw_caps.sd3_drv_type & SD_DRIVER_TYPE_D) + card_drv_type |= SD_DRIVER_TYPE_D; + + /* + * The drive strength that the hardware can support + * depends on the board design. Pass the appropriate + * information and let the hardware specific code + * return what is possible given the options + */ + drive_strength = card->host->ops->select_drive_strength( + card->sw_caps.uhs_max_dtr, + host_drv_type, card_drv_type); + + /* if error just use default for drive strength B */ + err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_DRIVE_STRENGTH, 0, + &card_strength); + if (err) + return; + + card_strength &= ~(SDIO_DRIVE_DTSx_MASK<host, drive_strength); +} + + +static int sdio_set_bus_speed_mode(struct mmc_card *card) +{ + unsigned int bus_speed, timing; + int err; + unsigned char speed; + + /* + * If the host doesn't support any of the UHS-I modes, fallback on + * default speed. + */ + if (!(card->host->caps & (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_DDR50))) + return 0; + + bus_speed = SDIO_SPEED_SDR12; + timing = MMC_TIMING_UHS_SDR12; + if ((card->host->caps & MMC_CAP_UHS_SDR104) && + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104)) { + bus_speed = SDIO_SPEED_SDR104; + timing = MMC_TIMING_UHS_SDR104; + card->sw_caps.uhs_max_dtr = UHS_SDR104_MAX_DTR; + } else if ((card->host->caps & MMC_CAP_UHS_DDR50) && + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50)) { + bus_speed = SDIO_SPEED_DDR50; + timing = MMC_TIMING_UHS_DDR50; + card->sw_caps.uhs_max_dtr = UHS_DDR50_MAX_DTR; + } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_SDR50)) && (card->sw_caps.sd3_bus_mode & + SD_MODE_UHS_SDR50)) { + bus_speed = SDIO_SPEED_SDR50; + timing = MMC_TIMING_UHS_SDR50; + card->sw_caps.uhs_max_dtr = UHS_SDR50_MAX_DTR; + } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25)) && + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR25)) { + bus_speed = SDIO_SPEED_SDR25; + timing = MMC_TIMING_UHS_SDR25; + card->sw_caps.uhs_max_dtr = UHS_SDR25_MAX_DTR; + } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR12)) && (card->sw_caps.sd3_bus_mode & + SD_MODE_UHS_SDR12)) { + bus_speed = SDIO_SPEED_SDR12; + timing = MMC_TIMING_UHS_SDR12; + card->sw_caps.uhs_max_dtr = UHS_SDR12_MAX_DTR; + } + + err = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &speed); + if (err) + return err; + + speed &= ~SDIO_SPEED_BSS_MASK; + speed |= bus_speed; + err = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL); + if (err) + return err; + + if (bus_speed) { + mmc_set_timing(card->host, timing); + mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr); + } + + return 0; +} + +/* + * UHS-I specific initialization procedure + */ +static int mmc_sdio_init_uhs_card(struct mmc_card *card) +{ + int err; + + if (!card->scr.sda_spec3) + return 0; + + /* + * Switch to wider bus (if supported). + */ + if (card->host->caps & MMC_CAP_4_BIT_DATA) { + err = sdio_enable_4bit_bus(card); + if (err > 0) { + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + err = 0; + } + } + + /* Set the driver strength for the card */ + sdio_select_driver_type(card); + + /* Set bus speed mode of the card */ + err = sdio_set_bus_speed_mode(card); + if (err) + goto out; + + /* Initialize and start re-tuning timer */ + if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning) + err = card->host->ops->execute_tuning(card->host); + +out: + + return err; +} + /* * Handle the detection and initialisation of a card. * @@ -393,6 +629,30 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, if (host->ops->init_card) host->ops->init_card(host, card); + /* + * If the host and card support UHS-I mode request the card + * to switch to 1.8V signaling level. No 1.8v signalling if + * UHS mode is not enabled to maintain compatibilty and some + * systems that claim 1.8v signalling in fact do not support + * it. + */ + if ((ocr & R4_18V_PRESENT) && + (host->caps & + (MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25 | + MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104 | + MMC_CAP_UHS_DDR50))) { + err = mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_180, + true); + if (err) { + ocr &= ~R4_18V_PRESENT; + host->ocr &= ~R4_18V_PRESENT; + } + err = 0; + } else { + ocr &= ~R4_18V_PRESENT; + host->ocr &= ~R4_18V_PRESENT; + } + /* * For native busses: set card RCA and quit open drain mode. */ @@ -492,29 +752,39 @@ static int mmc_sdio_init_card(struct mmc_host *host, u32 ocr, if (err) goto remove; - /* - * Switch to high-speed (if supported). - */ - err = sdio_enable_hs(card); - if (err > 0) - mmc_sd_go_highspeed(card); - else if (err) - goto remove; + /* Initialization sequence for UHS-I cards */ + /* Only if card supports 1.8v and UHS signaling */ + if ((ocr & R4_18V_PRESENT) && card->sw_caps.sd3_bus_mode) { + err = mmc_sdio_init_uhs_card(card); + if (err) + goto remove; - /* - * Change to the card's maximum speed. - */ - mmc_set_clock(host, mmc_sdio_get_max_clock(card)); + /* Card is an ultra-high-speed card */ + mmc_card_set_uhs(card); + } else { + /* + * Switch to high-speed (if supported). + */ + err = sdio_enable_hs(card); + if (err > 0) + mmc_sd_go_highspeed(card); + else if (err) + goto remove; - /* - * Switch to wider bus (if supported). - */ - err = sdio_enable_4bit_bus(card); - if (err > 0) - mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); - else if (err) - goto remove; + /* + * Change to the card's maximum speed. + */ + mmc_set_clock(host, mmc_sdio_get_max_clock(card)); + /* + * Switch to wider bus (if supported). + */ + err = sdio_enable_4bit_bus(card); + if (err > 0) + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + else if (err) + goto remove; + } finish: if (!oldcard) host->card = card; @@ -797,8 +1067,17 @@ int mmc_attach_sdio(struct mmc_host *host) * Detect and init the card. */ err = mmc_sdio_init_card(host, host->ocr, NULL, 0); - if (err) - goto err; + if (err) { + if (err == -EAGAIN) { + /* + * Retry initialization with S18R set to 0. + */ + host->ocr &= ~R4_18V_PRESENT; + err = mmc_sdio_init_card(host, host->ocr, NULL, 0); + } + if (err) + goto err; + } card = host->card; /* diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 2c9be29684cf..534974c3ef0c 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -367,7 +367,8 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_highspeed(c) ((c)->state & MMC_STATE_HIGHSPEED) #define mmc_card_blockaddr(c) ((c)->state & MMC_STATE_BLOCKADDR) #define mmc_card_ddr_mode(c) ((c)->state & MMC_STATE_HIGHSPEED_DDR) -#define mmc_sd_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED) +#define mmc_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED) +#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_set_present(c) ((c)->state |= MMC_STATE_PRESENT) @@ -375,6 +376,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED) #define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR) #define mmc_card_set_ddr_mode(c) ((c)->state |= MMC_STATE_HIGHSPEED_DDR) +#define mmc_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED) #define mmc_sd_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED) #define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC) diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index e0b1123497b9..c9fe66c58f8f 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -38,6 +38,7 @@ * [8:0] Byte/block count */ +#define R4_18V_PRESENT (1<<24) #define R4_MEMORY_PRESENT (1 << 27) /* @@ -85,6 +86,7 @@ #define SDIO_SD_REV_1_01 0 /* SD Physical Spec Version 1.01 */ #define SDIO_SD_REV_1_10 1 /* SD Physical Spec Version 1.10 */ #define SDIO_SD_REV_2_00 2 /* SD Physical Spec Version 2.00 */ +#define SDIO_SD_REV_3_00 3 /* SD Physical Spev Version 3.00 */ #define SDIO_CCCR_IOEx 0x02 #define SDIO_CCCR_IORx 0x03 @@ -134,8 +136,31 @@ #define SDIO_CCCR_SPEED 0x13 #define SDIO_SPEED_SHS 0x01 /* Supports High-Speed mode */ -#define SDIO_SPEED_EHS 0x02 /* Enable High-Speed mode */ - +#define SDIO_SPEED_BSS_SHIFT 1 +#define SDIO_SPEED_BSS_MASK (7< Date: Mon, 14 Nov 2011 12:02:28 +0100 Subject: mmc: mmci: add capabilities2 for MMC_CAP2 Signed-off-by: Per Forlin Signed-off-by: Chris Ball --- drivers/mmc/host/mmci.c | 1 + include/linux/amba/mmci.h | 2 ++ 2 files changed, 3 insertions(+) (limited to 'include/linux') diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 50b5f9926f64..8eabf999a858 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -1239,6 +1239,7 @@ static int __devinit mmci_probe(struct amba_device *dev, if (host->vcc == NULL) mmc->ocr_avail = plat->ocr_mask; mmc->caps = plat->capabilities; + mmc->caps2 = plat->capabilities2; /* * We can do SGIO diff --git a/include/linux/amba/mmci.h b/include/linux/amba/mmci.h index 21114810c7c0..0101e9c17fa1 100644 --- a/include/linux/amba/mmci.h +++ b/include/linux/amba/mmci.h @@ -30,6 +30,7 @@ struct dma_chan; * @cd_invert: true if the gpio_cd pin value is active low * @capabilities: the capabilities of the block as implemented in * this platform, signify anything MMC_CAP_* from mmc/host.h + * @capabilities2: more capabilities, MMC_CAP2_* from mmc/host.h * @dma_filter: function used to select an appropriate RX and TX * DMA channel to be used for DMA, if and only if you're deploying the * generic DMA engine @@ -52,6 +53,7 @@ struct mmci_platform_data { int gpio_cd; bool cd_invert; unsigned long capabilities; + unsigned long capabilities2; bool (*dma_filter)(struct dma_chan *chan, void *filter_param); void *dma_rx_param; void *dma_tx_param; -- cgit v1.2.3 From d30495048892980e5d453328d1cc9343b3f7e917 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 28 Nov 2011 16:22:00 +0200 Subject: mmc: allow upper layers to know immediately if card has been removed Add a function mmc_detect_card_removed() which upper layers can use to determine immediately if a card has been removed. This function should be called after an I/O request fails so that all queued I/O requests can be errored out immediately instead of waiting for the card device to be removed. Signed-off-by: Adrian Hunter Acked-by: Sujit Reddy Thumma Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 51 +++++++++++++++++++++++++++++++++++++++++++++--- drivers/mmc/core/core.h | 3 +++ drivers/mmc/core/mmc.c | 12 +++++++++++- drivers/mmc/core/sd.c | 12 +++++++++++- drivers/mmc/core/sdio.c | 11 ++++++++++- include/linux/mmc/card.h | 3 +++ include/linux/mmc/core.h | 2 ++ include/linux/mmc/host.h | 1 + 8 files changed, 89 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 950b97d7412a..a2aa860956ef 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -140,7 +140,7 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->retries = 0; } - if (err && cmd->retries) { + if (err && cmd->retries && !mmc_card_removed(host->card)) { /* * Request starter must handle retries - see * mmc_wait_for_req_done(). @@ -247,6 +247,11 @@ static void __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq) { init_completion(&mrq->completion); mrq->done = mmc_wait_done; + if (mmc_card_removed(host->card)) { + mrq->cmd->error = -ENOMEDIUM; + complete(&mrq->completion); + return; + } mmc_start_request(host, mrq); } @@ -259,7 +264,8 @@ static void mmc_wait_for_req_done(struct mmc_host *host, wait_for_completion(&mrq->completion); cmd = mrq->cmd; - if (!cmd->error || !cmd->retries) + if (!cmd->error || !cmd->retries || + mmc_card_removed(host->card)) break; pr_debug("%s: req failed (CMD%u): %d, retrying...\n", @@ -1456,7 +1462,7 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay) WARN_ON(host->removed); spin_unlock_irqrestore(&host->lock, flags); #endif - + host->detect_change = 1; mmc_schedule_delayed_work(&host->detect, delay); } @@ -2049,6 +2055,43 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) return -EIO; } +int _mmc_detect_card_removed(struct mmc_host *host) +{ + int ret; + + if ((host->caps & MMC_CAP_NONREMOVABLE) || !host->bus_ops->alive) + return 0; + + if (!host->card || mmc_card_removed(host->card)) + return 1; + + ret = host->bus_ops->alive(host); + if (ret) { + mmc_card_set_removed(host->card); + pr_debug("%s: card remove detected\n", mmc_hostname(host)); + } + + return ret; +} + +int mmc_detect_card_removed(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + + WARN_ON(!host->claimed); + /* + * The card will be considered unchanged unless we have been asked to + * detect a change or host requires polling to provide card detection. + */ + if (card && !host->detect_change && !(host->caps & MMC_CAP_NEEDS_POLL)) + return mmc_card_removed(card); + + host->detect_change = 0; + + return _mmc_detect_card_removed(host); +} +EXPORT_SYMBOL(mmc_detect_card_removed); + void mmc_rescan(struct work_struct *work) { static const unsigned freqs[] = { 400000, 300000, 200000, 100000 }; @@ -2069,6 +2112,8 @@ void mmc_rescan(struct work_struct *work) && !(host->caps & MMC_CAP_NONREMOVABLE)) host->bus_ops->detect(host); + host->detect_change = 0; + /* * Let mmc_bus_put() free the bus/bus_ops if we've found that * the card is no longer present. diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 14664f1fb16f..34009241213c 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -24,6 +24,7 @@ struct mmc_bus_ops { int (*resume)(struct mmc_host *); int (*power_save)(struct mmc_host *); int (*power_restore)(struct mmc_host *); + int (*alive)(struct mmc_host *); }; void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops); @@ -59,6 +60,8 @@ void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void mmc_stop_host(struct mmc_host *host); +int _mmc_detect_card_removed(struct mmc_host *host); + int mmc_attach_mmc(struct mmc_host *host); int mmc_attach_sd(struct mmc_host *host); int mmc_attach_sdio(struct mmc_host *host); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index d240427c1246..fc1059bb6a08 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1104,6 +1104,14 @@ static void mmc_remove(struct mmc_host *host) host->card = NULL; } +/* + * Card detection - card is alive. + */ +static int mmc_alive(struct mmc_host *host) +{ + return mmc_send_status(host->card, NULL); +} + /* * Card detection callback from host. */ @@ -1119,7 +1127,7 @@ static void mmc_detect(struct mmc_host *host) /* * Just check if our card has been removed. */ - err = mmc_send_status(host->card, NULL); + err = _mmc_detect_card_removed(host); mmc_release_host(host); @@ -1224,6 +1232,7 @@ static const struct mmc_bus_ops mmc_ops = { .suspend = NULL, .resume = NULL, .power_restore = mmc_power_restore, + .alive = mmc_alive, }; static const struct mmc_bus_ops mmc_ops_unsafe = { @@ -1234,6 +1243,7 @@ static const struct mmc_bus_ops mmc_ops_unsafe = { .suspend = mmc_suspend, .resume = mmc_resume, .power_restore = mmc_power_restore, + .alive = mmc_alive, }; static void mmc_attach_bus_ops(struct mmc_host *host) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 85b858f6d5d4..6f27d35081b8 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1018,6 +1018,14 @@ static void mmc_sd_remove(struct mmc_host *host) host->card = NULL; } +/* + * Card detection - card is alive. + */ +static int mmc_sd_alive(struct mmc_host *host) +{ + return mmc_send_status(host->card, NULL); +} + /* * Card detection callback from host. */ @@ -1033,7 +1041,7 @@ static void mmc_sd_detect(struct mmc_host *host) /* * Just check if our card has been removed. */ - err = mmc_send_status(host->card, NULL); + err = _mmc_detect_card_removed(host); mmc_release_host(host); @@ -1102,6 +1110,7 @@ static const struct mmc_bus_ops mmc_sd_ops = { .suspend = NULL, .resume = NULL, .power_restore = mmc_sd_power_restore, + .alive = mmc_sd_alive, }; static const struct mmc_bus_ops mmc_sd_ops_unsafe = { @@ -1110,6 +1119,7 @@ static const struct mmc_bus_ops mmc_sd_ops_unsafe = { .suspend = mmc_sd_suspend, .resume = mmc_sd_resume, .power_restore = mmc_sd_power_restore, + .alive = mmc_sd_alive, }; static void mmc_sd_attach_bus_ops(struct mmc_host *host) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 8c04f7f46dec..b77f770ce5d1 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -819,6 +819,14 @@ static void mmc_sdio_remove(struct mmc_host *host) host->card = NULL; } +/* + * Card detection - card is alive. + */ +static int mmc_sdio_alive(struct mmc_host *host) +{ + return mmc_select_card(host->card); +} + /* * Card detection callback from host. */ @@ -841,7 +849,7 @@ static void mmc_sdio_detect(struct mmc_host *host) /* * Just check if our card has been removed. */ - err = mmc_select_card(host->card); + err = _mmc_detect_card_removed(host); mmc_release_host(host); @@ -1019,6 +1027,7 @@ static const struct mmc_bus_ops mmc_sdio_ops = { .suspend = mmc_sdio_suspend, .resume = mmc_sdio_resume, .power_restore = mmc_sdio_power_restore, + .alive = mmc_sdio_alive, }; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 534974c3ef0c..6402d9224d6a 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -209,6 +209,7 @@ struct mmc_card { #define MMC_STATE_HIGHSPEED_DDR (1<<4) /* card is in high speed mode */ #define MMC_STATE_ULTRAHIGHSPEED (1<<5) /* card is in ultra high speed mode */ #define MMC_CARD_SDXC (1<<6) /* card is SDXC */ +#define MMC_CARD_REMOVED (1<<7) /* card has been removed */ 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 */ @@ -370,6 +371,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED) #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_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -379,6 +381,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED) #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) /* * Quirk add/remove for MMC products. diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 174a844a5dda..87a976cc5654 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -180,6 +180,8 @@ extern int mmc_try_claim_host(struct mmc_host *host); extern int mmc_flush_cache(struct mmc_card *); +extern int mmc_detect_card_removed(struct mmc_host *host); + /** * mmc_claim_host - exclusively claim a host * @host: mmc host to claim diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 706f72279a17..9a03d0335745 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -299,6 +299,7 @@ struct mmc_host { int claim_cnt; /* "claim" nesting count */ struct delayed_work detect; + int detect_change; /* card detect flag */ const struct mmc_bus_ops *bus_ops; /* current bus driver */ unsigned int bus_refs; /* reference counter */ -- cgit v1.2.3 From add710eaa88606de8ba98a014d37178579e6dbaf Mon Sep 17 00:00:00 2001 From: Johan Rudholm Date: Fri, 2 Dec 2011 08:51:06 +0100 Subject: mmc: boot partition ro lock support Enable boot partitions to be read-only locked until next power on via a sysfs entry. There will be one sysfs entry for each boot partition: /sys/block/mmcblkXbootY/ro_lock_until_next_power_on Each boot partition is locked by writing 1 to its file. Signed-off-by: Johan Rudholm Signed-off-by: John Beckett Signed-off-by: Chris Ball --- Documentation/mmc/mmc-dev-parts.txt | 13 ++++ drivers/mmc/card/block.c | 121 +++++++++++++++++++++++++++++++++--- drivers/mmc/core/mmc.c | 14 ++++- include/linux/mmc/card.h | 10 ++- include/linux/mmc/mmc.h | 6 ++ 5 files changed, 153 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/Documentation/mmc/mmc-dev-parts.txt b/Documentation/mmc/mmc-dev-parts.txt index 2db28b8e662f..f08d078d43cf 100644 --- a/Documentation/mmc/mmc-dev-parts.txt +++ b/Documentation/mmc/mmc-dev-parts.txt @@ -25,3 +25,16 @@ echo 0 > /sys/block/mmcblkXbootY/force_ro To re-enable read-only access: echo 1 > /sys/block/mmcblkXbootY/force_ro + +The boot partitions can also be locked read only until the next power on, +with: + +echo 1 > /sys/block/mmcblkXbootY/ro_lock_until_next_power_on + +This is a feature of the card and not of the kernel. If the card does +not support boot partition locking, the file will not exist. If the +feature has been disabled on the card, the file will be read-only. + +The boot partitions can also be locked permanently, but this feature is +not accessible through sysfs in order to avoid accidental or malicious +bricking. diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index ad0fb8d74dda..0c959c96005e 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -107,6 +107,8 @@ struct mmc_blk_data { */ unsigned int part_curr; struct device_attribute force_ro; + struct device_attribute power_ro_lock; + int area_type; }; static DEFINE_MUTEX(open_lock); @@ -165,6 +167,70 @@ static void mmc_blk_put(struct mmc_blk_data *md) mutex_unlock(&open_lock); } +static ssize_t power_ro_lock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + struct mmc_card *card = md->queue.card; + int locked = 0; + + if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PERM_WP_EN) + locked = 2; + else if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PWR_WP_EN) + locked = 1; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", locked); + + return ret; +} + +static ssize_t power_ro_lock_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret; + struct mmc_blk_data *md, *part_md; + struct mmc_card *card; + unsigned long set; + + if (kstrtoul(buf, 0, &set)) + return -EINVAL; + + if (set != 1) + return count; + + md = mmc_blk_get(dev_to_disk(dev)); + card = md->queue.card; + + mmc_claim_host(card->host); + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP, + card->ext_csd.boot_ro_lock | + EXT_CSD_BOOT_WP_B_PWR_WP_EN, + card->ext_csd.part_time); + if (ret) + pr_err("%s: Locking boot partition ro until next power on failed: %d\n", md->disk->disk_name, ret); + else + card->ext_csd.boot_ro_lock |= EXT_CSD_BOOT_WP_B_PWR_WP_EN; + + mmc_release_host(card->host); + + if (!ret) { + pr_info("%s: Locking boot partition ro until next power on\n", + md->disk->disk_name); + set_disk_ro(md->disk, 1); + + list_for_each_entry(part_md, &md->part, part) + if (part_md->area_type == MMC_BLK_DATA_AREA_BOOT) { + pr_info("%s: Locking boot partition ro until next power on\n", part_md->disk->disk_name); + set_disk_ro(part_md->disk, 1); + } + } + + mmc_blk_put(md); + return count; +} + static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1347,7 +1413,8 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, struct device *parent, sector_t size, bool default_ro, - const char *subname) + const char *subname, + int area_type) { struct mmc_blk_data *md; int devidx, ret; @@ -1372,11 +1439,12 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, if (!subname) { md->name_idx = find_first_zero_bit(name_use, max_devices); __set_bit(md->name_idx, name_use); - } - else + } else md->name_idx = ((struct mmc_blk_data *) dev_to_disk(parent)->private_data)->name_idx; + md->area_type = area_type; + /* * Set the read-only status based on the supported commands * and the write protect switch. @@ -1470,7 +1538,8 @@ static struct mmc_blk_data *mmc_blk_alloc(struct mmc_card *card) size = card->csd.capacity << (card->csd.read_blkbits - 9); } - md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL); + md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL, + MMC_BLK_DATA_AREA_MAIN); return md; } @@ -1479,13 +1548,14 @@ static int mmc_blk_alloc_part(struct mmc_card *card, unsigned int part_type, sector_t size, bool default_ro, - const char *subname) + const char *subname, + int area_type) { char cap_str[10]; struct mmc_blk_data *part_md; part_md = mmc_blk_alloc_req(card, disk_to_dev(md->disk), size, default_ro, - subname); + subname, area_type); if (IS_ERR(part_md)) return PTR_ERR(part_md); part_md->part_type = part_type; @@ -1518,7 +1588,8 @@ static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) card->part[idx].part_cfg, card->part[idx].size >> 9, card->part[idx].force_ro, - card->part[idx].name); + card->part[idx].name, + card->part[idx].area_type); if (ret) return ret; } @@ -1547,9 +1618,16 @@ mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card) static void mmc_blk_remove_req(struct mmc_blk_data *md) { + struct mmc_card *card; + if (md) { + card = md->queue.card; if (md->disk->flags & GENHD_FL_UP) { device_remove_file(disk_to_dev(md->disk), &md->force_ro); + if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && + card->ext_csd.boot_ro_lockable) + device_remove_file(disk_to_dev(md->disk), + &md->power_ro_lock); /* Stop new requests from getting into the queue */ del_gendisk(md->disk); @@ -1578,6 +1656,7 @@ static void mmc_blk_remove_parts(struct mmc_card *card, static int mmc_add_disk(struct mmc_blk_data *md) { int ret; + struct mmc_card *card = md->queue.card; add_disk(md->disk); md->force_ro.show = force_ro_show; @@ -1587,7 +1666,33 @@ static int mmc_add_disk(struct mmc_blk_data *md) md->force_ro.attr.mode = S_IRUGO | S_IWUSR; ret = device_create_file(disk_to_dev(md->disk), &md->force_ro); if (ret) - del_gendisk(md->disk); + goto force_ro_fail; + + if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) && + card->ext_csd.boot_ro_lockable) { + mode_t mode; + + if (card->ext_csd.boot_ro_lock & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) + mode = S_IRUGO; + else + mode = S_IRUGO | S_IWUSR; + + md->power_ro_lock.show = power_ro_lock_show; + md->power_ro_lock.store = power_ro_lock_store; + md->power_ro_lock.attr.mode = mode; + md->power_ro_lock.attr.name = + "ro_lock_until_next_power_on"; + ret = device_create_file(disk_to_dev(md->disk), + &md->power_ro_lock); + if (ret) + goto power_ro_lock_fail; + } + return ret; + +power_ro_lock_fail: + device_remove_file(disk_to_dev(md->disk), &md->force_ro); +force_ro_fail: + del_gendisk(md->disk); return ret; } diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index fc1059bb6a08..006e932a3ae3 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -348,7 +348,8 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) part_size = ext_csd[EXT_CSD_BOOT_MULT] << 17; mmc_part_add(card, part_size, EXT_CSD_PART_CONFIG_ACC_BOOT0 + idx, - "boot%d", idx, true); + "boot%d", idx, true, + MMC_BLK_DATA_AREA_BOOT); } } } @@ -435,7 +436,8 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) hc_wp_grp_sz); mmc_part_add(card, part_size << 19, EXT_CSD_PART_CONFIG_ACC_GP0 + idx, - "gp%d", idx, false); + "gp%d", idx, false, + MMC_BLK_DATA_AREA_GP); } } card->ext_csd.sec_trim_mult = @@ -446,6 +448,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT]; card->ext_csd.trim_timeout = 300 * ext_csd[EXT_CSD_TRIM_MULT]; + + /* + * Note that the call to mmc_part_add above defaults to read + * only. If this default assumption is changed, the call must + * take into account the value of boot_locked below. + */ + card->ext_csd.boot_ro_lock = ext_csd[EXT_CSD_BOOT_WP]; + card->ext_csd.boot_ro_lockable = true; } if (card->ext_csd.rev >= 5) { diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 6402d9224d6a..9478a6bf1bb1 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -71,6 +71,8 @@ struct mmc_ext_csd { bool hpi_en; /* HPI enablebit */ bool hpi; /* HPI support bit */ unsigned int hpi_cmd; /* cmd used as HPI */ + unsigned int boot_ro_lock; /* ro lock support */ + bool boot_ro_lockable; u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ @@ -187,6 +189,10 @@ struct mmc_part { unsigned int part_cfg; /* partition type */ char name[MAX_MMC_PART_NAME_LEN]; bool force_ro; /* to make boot parts RO by default */ + unsigned int area_type; +#define MMC_BLK_DATA_AREA_MAIN (1<<0) +#define MMC_BLK_DATA_AREA_BOOT (1<<1) +#define MMC_BLK_DATA_AREA_GP (1<<2) }; /* @@ -265,12 +271,14 @@ struct mmc_card { * This function fill contents in mmc_part. */ static inline void mmc_part_add(struct mmc_card *card, unsigned int size, - unsigned int part_cfg, char *name, int idx, bool ro) + unsigned int part_cfg, char *name, int idx, bool ro, + int area_type) { card->part[card->nr_parts].size = size; card->part[card->nr_parts].part_cfg = part_cfg; sprintf(card->part[card->nr_parts].name, name, idx); card->part[card->nr_parts].force_ro = ro; + card->part[card->nr_parts].area_type = area_type; card->nr_parts++; } diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 0e7135697d11..665548e639e8 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -280,6 +280,7 @@ struct _mmc_csd { #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ #define EXT_CSD_SANITIZE_START 165 /* W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ +#define EXT_CSD_BOOT_WP 173 /* R/W */ #define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ #define EXT_CSD_PART_CONFIG 179 /* R/W */ #define EXT_CSD_ERASED_MEM_CONT 181 /* RO */ @@ -321,6 +322,11 @@ struct _mmc_csd { #define EXT_CSD_WR_REL_PARAM_EN (1<<2) +#define EXT_CSD_BOOT_WP_B_PWR_WP_DIS (0x40) +#define EXT_CSD_BOOT_WP_B_PERM_WP_DIS (0x10) +#define EXT_CSD_BOOT_WP_B_PERM_WP_EN (0x04) +#define EXT_CSD_BOOT_WP_B_PWR_WP_EN (0x01) + #define EXT_CSD_PART_CONFIG_ACC_MASK (0x7) #define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1) #define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4) -- cgit v1.2.3 From 4f408cc67a0613f969d1e02fff6de74d31a29fb3 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Fri, 9 Dec 2011 14:55:52 +0900 Subject: mmc: dw_mmc: Add more capabilities field This patch adds another capabilities field for MMC_CAPS2_XXX. Signed-off-by: Seungwon Jeon Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 6 ++++++ include/linux/mmc/dw_mmc.h | 1 + 2 files changed, 7 insertions(+) (limited to 'include/linux') diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 44bc11e8761e..69e588960e79 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1681,6 +1681,12 @@ static int __init dw_mci_init_slot(struct dw_mci *host, unsigned int id) else mmc->caps = 0; + if (host->pdata->caps2) + mmc->caps2 = host->pdata->caps2; + else + mmc->caps2 = 0; + + if (host->pdata->get_bus_wd) if (host->pdata->get_bus_wd(slot->id) >= 4) mmc->caps |= MMC_CAP_4_BIT_DATA; diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 6dc9b80568a0..e8779c6d1759 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -214,6 +214,7 @@ struct dw_mci_board { unsigned int bus_hz; /* Bus speed */ unsigned int caps; /* Capabilities */ + unsigned int caps2; /* More capabilities */ /* * Override fifo depth. If 0, autodetect it from the FIFOTH register, * but note that this may not be reliable after a bootloader has used -- cgit v1.2.3 From b67e198073b2d2f16572f5fa77553fec14775f69 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Sun, 25 Dec 2011 20:40:03 -0500 Subject: mmc: add a card hotplug handler context SD/MMC controllers provide different card insertion and removal detection methods. On some of them the controller itself issues an interrupt, on others polling is used, on yet others auxiliary means are used for this purpose, e.g., a GPIO IRQ. Further, on some systems one of those methods can be chosen at driver probing time and configured in software. E.g., on some systems the SD/MMC controller card hot-plug detection pin can be configured either as a respective controller functions, or an IRQ-capable GPIO. To support such flexible configurations a card hot-plug context is added by this patch. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- include/linux/mmc/host.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/linux') diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 9a03d0335745..742f0e102e1e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -167,6 +167,11 @@ struct mmc_async_req { int (*err_check) (struct mmc_card *, struct mmc_async_req *); }; +struct mmc_hotplug { + unsigned int irq; + void *handler_priv; +}; + struct mmc_host { struct device *parent; struct device class_dev; @@ -300,6 +305,7 @@ struct mmc_host { struct delayed_work detect; int detect_change; /* card detect flag */ + struct mmc_hotplug hotplug; const struct mmc_bus_ops *bus_ops; /* current bus driver */ unsigned int bus_refs; /* reference counter */ -- cgit v1.2.3 From 349ab52446772a359bc7e7699cae3880d48fa5c9 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Sun, 25 Dec 2011 21:36:02 +0100 Subject: mmc: add a generic GPIO card-detect helper This patch adds a primitive helper to support card hotplug detection on platforms, where a GPIO, capable of producing interrupts, is used for detection of card-insertion and -removal events. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/core/Makefile | 2 +- drivers/mmc/core/cd-gpio.c | 74 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/cd-gpio.h | 19 ++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 drivers/mmc/core/cd-gpio.c create mode 100644 include/linux/mmc/cd-gpio.h (limited to 'include/linux') diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 639501970b41..dca4428380f1 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -7,6 +7,6 @@ mmc_core-y := core.o bus.o host.o \ mmc.o mmc_ops.o sd.o sd_ops.o \ sdio.o sdio_ops.o sdio_bus.o \ sdio_cis.o sdio_io.o sdio_irq.o \ - quirks.o + quirks.o cd-gpio.o mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o diff --git a/drivers/mmc/core/cd-gpio.c b/drivers/mmc/core/cd-gpio.c new file mode 100644 index 000000000000..082202ae4a03 --- /dev/null +++ b/drivers/mmc/core/cd-gpio.c @@ -0,0 +1,74 @@ +/* + * Generic GPIO card-detect helper + * + * Copyright (C) 2011, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct mmc_cd_gpio { + unsigned int gpio; + char label[0]; +}; + +static irqreturn_t mmc_cd_gpio_irqt(int irq, void *dev_id) +{ + /* Schedule a card detection after a debounce timeout */ + mmc_detect_change(dev_id, msecs_to_jiffies(100)); + return IRQ_HANDLED; +} + +int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio, + unsigned int irq, unsigned long flags) +{ + size_t len = strlen(dev_name(host->parent)) + 4; + struct mmc_cd_gpio *cd = kmalloc(sizeof(*cd) + len, GFP_KERNEL); + int ret; + + if (!cd) + return -ENOMEM; + + snprintf(cd->label, len, "%s cd", dev_name(host->parent)); + + ret = gpio_request_one(gpio, GPIOF_DIR_IN, cd->label); + if (ret < 0) + goto egpioreq; + + ret = request_threaded_irq(irq, NULL, mmc_cd_gpio_irqt, + flags, cd->label, host); + if (ret < 0) + goto eirqreq; + + cd->gpio = gpio; + host->hotplug.irq = irq; + host->hotplug.handler_priv = cd; + + return 0; + +eirqreq: + gpio_free(gpio); +egpioreq: + kfree(cd); + return ret; +} +EXPORT_SYMBOL(mmc_cd_gpio_request); + +void mmc_cd_gpio_free(struct mmc_host *host) +{ + struct mmc_cd_gpio *cd = host->hotplug.handler_priv; + + free_irq(host->hotplug.irq, host); + gpio_free(cd->gpio); + kfree(cd); +} +EXPORT_SYMBOL(mmc_cd_gpio_free); diff --git a/include/linux/mmc/cd-gpio.h b/include/linux/mmc/cd-gpio.h new file mode 100644 index 000000000000..a8e469783318 --- /dev/null +++ b/include/linux/mmc/cd-gpio.h @@ -0,0 +1,19 @@ +/* + * Generic GPIO card-detect helper header + * + * Copyright (C) 2011, Guennadi Liakhovetski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef MMC_CD_GPIO_H +#define MMC_CD_GPIO_H + +struct mmc_host; +int mmc_cd_gpio_request(struct mmc_host *host, unsigned int gpio, + unsigned int irq, unsigned long flags); +void mmc_cd_gpio_free(struct mmc_host *host); + +#endif -- cgit v1.2.3 From 52c506f0bc72530fb786838e7ffd4f158a2e5c3a Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 27 Dec 2011 15:48:43 +0200 Subject: mmc: sdhci-pci: add platform data Add a means of getting platform data for the SDHCI PCI devices. The data is stored against the slot not the device in order to support multi-slot devices. The data allows platform-specific setup (such as getting GPIO numbers from firmware or setting up wl12xx for SDIO) to be done in platform support files instead of the sdhci-pci driver. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/Makefile | 2 +- drivers/mmc/Makefile | 3 +-- drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sdhci-pci-data.c | 5 +++++ drivers/mmc/host/sdhci-pci.c | 32 ++++++++++++++++++++++++++++---- include/linux/mmc/sdhci-pci-data.h | 18 ++++++++++++++++++ 6 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 drivers/mmc/host/sdhci-pci-data.c create mode 100644 include/linux/mmc/sdhci-pci-data.h (limited to 'include/linux') diff --git a/drivers/Makefile b/drivers/Makefile index 1b3142127bf5..c07be024b962 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -97,7 +97,7 @@ obj-$(CONFIG_EISA) += eisa/ obj-y += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ -obj-$(CONFIG_MMC) += mmc/ +obj-y += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-y += leds/ obj-$(CONFIG_INFINIBAND) += infiniband/ diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile index 12eef393e216..400756ec7c49 100644 --- a/drivers/mmc/Makefile +++ b/drivers/mmc/Makefile @@ -6,5 +6,4 @@ subdir-ccflags-$(CONFIG_MMC_DEBUG) := -DDEBUG obj-$(CONFIG_MMC) += core/ obj-$(CONFIG_MMC) += card/ -obj-$(CONFIG_MMC) += host/ - +obj-$(subst m,y,$(CONFIG_MMC)) += host/ diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index b4b83f302e32..745f8fce2519 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_MMC_MXC) += mxcmmc.o obj-$(CONFIG_MMC_MXS) += mxs-mmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o +obj-$(subst m,y,$(CONFIG_MMC_SDHCI_PCI)) += sdhci-pci-data.o obj-$(CONFIG_MMC_SDHCI_PXAV3) += sdhci-pxav3.o obj-$(CONFIG_MMC_SDHCI_PXAV2) += sdhci-pxav2.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o diff --git a/drivers/mmc/host/sdhci-pci-data.c b/drivers/mmc/host/sdhci-pci-data.c new file mode 100644 index 000000000000..a611217769f5 --- /dev/null +++ b/drivers/mmc/host/sdhci-pci-data.c @@ -0,0 +1,5 @@ +#include +#include + +struct sdhci_pci_data *(*sdhci_pci_get_data)(struct pci_dev *pdev, int slotno); +EXPORT_SYMBOL_GPL(sdhci_pci_get_data); diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index d2e77fb21b26..4e8f324e2c84 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "sdhci.h" @@ -61,6 +62,7 @@ struct sdhci_pci_fixes { struct sdhci_pci_slot { struct sdhci_pci_chip *chip; struct sdhci_host *host; + struct sdhci_pci_data *data; int pci_bar; int rst_n_gpio; @@ -1188,11 +1190,12 @@ static const struct dev_pm_ops sdhci_pci_pm_ops = { \*****************************************************************************/ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( - struct pci_dev *pdev, struct sdhci_pci_chip *chip, int bar) + struct pci_dev *pdev, struct sdhci_pci_chip *chip, int first_bar, + int slotno) { struct sdhci_pci_slot *slot; struct sdhci_host *host; - int ret; + int ret, bar = first_bar + slotno; if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) { dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar); @@ -1227,6 +1230,20 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( slot->pci_bar = bar; slot->rst_n_gpio = -EINVAL; + /* Retrieve platform data if there is any */ + if (*sdhci_pci_get_data) + slot->data = sdhci_pci_get_data(pdev, slotno); + + if (slot->data) { + if (slot->data->setup) { + ret = slot->data->setup(slot->data); + if (ret) { + dev_err(&pdev->dev, "platform setup failed\n"); + goto free; + } + } + } + host->hw_name = "PCI"; host->ops = &sdhci_pci_ops; host->quirks = chip->quirks; @@ -1236,7 +1253,7 @@ static struct sdhci_pci_slot * __devinit sdhci_pci_probe_slot( ret = pci_request_region(pdev, bar, mmc_hostname(host->mmc)); if (ret) { dev_err(&pdev->dev, "cannot request region\n"); - goto free; + goto cleanup; } host->ioaddr = pci_ioremap_bar(pdev, bar); @@ -1270,6 +1287,10 @@ unmap: release: pci_release_region(pdev, bar); +cleanup: + if (slot->data && slot->data->cleanup) + slot->data->cleanup(slot->data); + free: sdhci_free_host(host); @@ -1291,6 +1312,9 @@ static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot) if (slot->chip->fixes && slot->chip->fixes->remove_slot) slot->chip->fixes->remove_slot(slot, dead); + if (slot->data && slot->data->cleanup) + slot->data->cleanup(slot->data); + pci_release_region(slot->chip->pdev, slot->pci_bar); sdhci_free_host(slot->host); @@ -1377,7 +1401,7 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev, slots = chip->num_slots; /* Quirk may have changed this */ for (i = 0; i < slots; i++) { - slot = sdhci_pci_probe_slot(pdev, chip, first_bar + i); + slot = sdhci_pci_probe_slot(pdev, chip, first_bar, i); if (IS_ERR(slot)) { for (i--; i >= 0; i--) sdhci_pci_remove_slot(chip->slots[i]); diff --git a/include/linux/mmc/sdhci-pci-data.h b/include/linux/mmc/sdhci-pci-data.h new file mode 100644 index 000000000000..8959604a13d3 --- /dev/null +++ b/include/linux/mmc/sdhci-pci-data.h @@ -0,0 +1,18 @@ +#ifndef LINUX_MMC_SDHCI_PCI_DATA_H +#define LINUX_MMC_SDHCI_PCI_DATA_H + +struct pci_dev; + +struct sdhci_pci_data { + struct pci_dev *pdev; + int slotno; + int rst_n_gpio; /* Set to -EINVAL if unused */ + int cd_gpio; /* Set to -EINVAL if unused */ + int (*setup)(struct sdhci_pci_data *data); + void (*cleanup)(struct sdhci_pci_data *data); +}; + +extern struct sdhci_pci_data *(*sdhci_pci_get_data)(struct pci_dev *pdev, + int slotno); + +#endif -- cgit v1.2.3 From e2a0a5829c4069ee4a0f28c7301187ffaba91a46 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Tue, 27 Dec 2011 15:48:45 +0200 Subject: mmc: sdhci-pci: remove SDHCI_QUIRK2_OWN_CARD_DETECTION Even if a driver provides separate card detection, an interrupt is still needed to abort mmc requests that are in progress. SDHCI_QUIRK2_OWN_CARD_DETECTION prevents that, so remove it. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pci.c | 1 - drivers/mmc/host/sdhci.c | 1 - include/linux/mmc/sdhci.h | 2 -- 3 files changed, 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 646680a5993a..83a152e9b976 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -212,7 +212,6 @@ static void sdhci_pci_add_own_cd(struct sdhci_pci_slot *slot) slot->cd_gpio = gpio; slot->cd_irq = irq; - slot->host->quirks2 |= SDHCI_QUIRK2_OWN_CARD_DETECTION; return; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index e6c6cd6e95f2..6f1fd02fe01b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -147,7 +147,6 @@ static void sdhci_set_card_detection(struct sdhci_host *host, bool enable) u32 present, irqs; if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) || - (host->quirks2 & SDHCI_QUIRK2_OWN_CARD_DETECTION) || !mmc_card_is_removable(host->mmc)) return; diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index e4b69353678d..dad7a469f09c 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -90,8 +90,6 @@ struct sdhci_host { unsigned int quirks2; /* More deviations from spec. */ -#define SDHCI_QUIRK2_OWN_CARD_DETECTION (1<<0) - int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ -- cgit v1.2.3 From aa9df4fb2adcc73d36fa41e23059519be770aaa5 Mon Sep 17 00:00:00 2001 From: Ulf Hansson Date: Mon, 19 Dec 2011 16:24:19 +0100 Subject: mmc: core: Add option to prevent eMMC sleep command Host may now use MMC_CAP2_NO_SLEEP_CMD to disable the use of eMMC sleep/awake command. This option can be used when your platform has a buggy kernel crash dump software, which is supposed to store the dump on the eMMC, but is not able to wake up the eMMC from sleep state. In particular, failures have been seen with u-boot; even if it is fixed there, platforms will be slow to update their bootloader binaries. Signed-off-by: Ulf Hansson Reviewed-by: Hanumath Prasad Reviewed-by: Srinidhi Kasagar Acked-by: Subhash Jadavani Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 6 ++++++ include/linux/mmc/host.h | 1 + 2 files changed, 7 insertions(+) (limited to 'include/linux') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 22050525be84..be7569f3fb56 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2246,6 +2246,9 @@ int mmc_card_awake(struct mmc_host *host) { int err = -ENOSYS; + if (host->caps2 & MMC_CAP2_NO_SLEEP_CMD) + return 0; + mmc_bus_get(host); if (host->bus_ops && !host->bus_dead && host->bus_ops->awake) @@ -2261,6 +2264,9 @@ int mmc_card_sleep(struct mmc_host *host) { int err = -ENOSYS; + if (host->caps2 & MMC_CAP2_NO_SLEEP_CMD) + return 0; + mmc_bus_get(host); if (host->bus_ops && !host->bus_dead && host->bus_ops->sleep) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 742f0e102e1e..031d865167a2 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -247,6 +247,7 @@ struct mmc_host { #define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */ #define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */ #define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */ +#define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */ mmc_pm_flag_t pm_caps; /* supported pm features */ unsigned int power_notify_type; -- cgit v1.2.3 From a4924c71aa43d4f8a3f342b1f71788349472e684 Mon Sep 17 00:00:00 2001 From: Girish K S Date: Wed, 11 Jan 2012 14:04:52 -0500 Subject: mmc: core: HS200 mode support for eMMC 4.5 This patch adds the support of the HS200 bus speed for eMMC 4.5 devices. The eMMC 4.5 devices have support for 200MHz bus speed. The function prototype of the tuning function is modified to handle the tuning command number which is different in sd and mmc case. Signed-off-by: Girish K S Signed-off-by: Philip Rakity Signed-off-by: Chris Ball --- drivers/mmc/core/bus.c | 3 +- drivers/mmc/core/debugfs.c | 3 + drivers/mmc/core/mmc.c | 162 ++++++++++++++++++++++++++++++++++++++++++--- drivers/mmc/core/sd.c | 3 +- drivers/mmc/core/sdio.c | 4 +- include/linux/mmc/card.h | 3 + include/linux/mmc/host.h | 11 ++- include/linux/mmc/mmc.h | 66 +++++++++++++++++- 8 files changed, 241 insertions(+), 14 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index f8a228a61fd4..5d011a39dfff 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -303,10 +303,11 @@ int mmc_add_card(struct mmc_card *card) mmc_card_ddr_mode(card) ? "DDR " : "", type); } else { - printk(KERN_INFO "%s: new %s%s%s card at address %04x\n", + pr_info("%s: new %s%s%s%s card at address %04x\n", mmc_hostname(card->host), mmc_card_uhs(card) ? "ultra high speed " : (mmc_card_highspeed(card) ? "high speed " : ""), + (mmc_card_hs200(card) ? "HS200 " : ""), mmc_card_ddr_mode(card) ? "DDR " : "", type, card->rca); } diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 027615d3bf3e..9ab5b17d488a 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -135,6 +135,9 @@ static int mmc_ios_show(struct seq_file *s, void *data) case MMC_TIMING_UHS_DDR50: str = "sd uhs DDR50"; break; + case MMC_TIMING_MMC_HS200: + str = "mmc high-speed SDR200"; + break; default: str = "invalid"; break; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 67f346e0d105..59b9ba52e66a 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -286,6 +286,27 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } card->ext_csd.raw_card_type = ext_csd[EXT_CSD_CARD_TYPE]; switch (ext_csd[EXT_CSD_CARD_TYPE] & EXT_CSD_CARD_TYPE_MASK) { + case EXT_CSD_CARD_TYPE_SDR_ALL: + case EXT_CSD_CARD_TYPE_SDR_ALL_DDR_1_8V: + case EXT_CSD_CARD_TYPE_SDR_ALL_DDR_1_2V: + case EXT_CSD_CARD_TYPE_SDR_ALL_DDR_52: + card->ext_csd.hs_max_dtr = 200000000; + card->ext_csd.card_type = EXT_CSD_CARD_TYPE_SDR_200; + break; + case EXT_CSD_CARD_TYPE_SDR_1_2V_ALL: + case EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_1_8V: + case EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_1_2V: + case EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_52: + card->ext_csd.hs_max_dtr = 200000000; + card->ext_csd.card_type = EXT_CSD_CARD_TYPE_SDR_1_2V; + break; + case EXT_CSD_CARD_TYPE_SDR_1_8V_ALL: + case EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_1_8V: + case EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_1_2V: + case EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_52: + card->ext_csd.hs_max_dtr = 200000000; + card->ext_csd.card_type = EXT_CSD_CARD_TYPE_SDR_1_8V; + break; case EXT_CSD_CARD_TYPE_DDR_52 | EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26: card->ext_csd.hs_max_dtr = 52000000; @@ -699,6 +720,79 @@ static int mmc_select_powerclass(struct mmc_card *card, return err; } +/* + * Selects the desired buswidth and switch to the HS200 mode + * if bus width set without error + */ +static int mmc_select_hs200(struct mmc_card *card) +{ + int idx, err = 0; + struct mmc_host *host; + static unsigned ext_csd_bits[] = { + EXT_CSD_BUS_WIDTH_4, + EXT_CSD_BUS_WIDTH_8, + }; + static unsigned bus_widths[] = { + MMC_BUS_WIDTH_4, + MMC_BUS_WIDTH_8, + }; + + BUG_ON(!card); + + host = card->host; + + if (card->ext_csd.card_type & EXT_CSD_CARD_TYPE_SDR_1_2V && + host->caps2 & MMC_CAP2_HS200_1_2V_SDR) + if (mmc_set_signal_voltage(host, MMC_SIGNAL_VOLTAGE_120, 0)) + err = mmc_set_signal_voltage(host, + MMC_SIGNAL_VOLTAGE_180, 0); + + /* If fails try again during next card power cycle */ + if (err) + goto err; + + idx = (host->caps & MMC_CAP_8_BIT_DATA) ? 1 : 0; + + /* + * Unlike SD, MMC cards dont have a configuration register to notify + * supported bus width. So bus test command should be run to identify + * the supported bus width or compare the ext csd values of current + * bus width and ext csd values of 1 bit mode read earlier. + */ + for (; idx >= 0; idx--) { + + /* + * Host is capable of 8bit transfer, then switch + * the device to work in 8bit transfer mode. If the + * mmc switch command returns error then switch to + * 4bit transfer mode. On success set the corresponding + * bus width on the host. + */ + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, + ext_csd_bits[idx], + card->ext_csd.generic_cmd6_time); + if (err) + continue; + + mmc_set_bus_width(card->host, bus_widths[idx]); + + if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST)) + err = mmc_compare_ext_csds(card, bus_widths[idx]); + else + err = mmc_bus_test(card, bus_widths[idx]); + if (!err) + break; + } + + /* switch to HS200 mode if bus width set successfully */ + if (!err) + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, 2, 0); +err: + return err; +} + /* * Handle the detection and initialisation of a card. * @@ -905,11 +999,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* * Activate high speed (if supported) */ - if ((card->ext_csd.hs_max_dtr != 0) && - (host->caps & MMC_CAP_MMC_HIGHSPEED)) { - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HS_TIMING, 1, - card->ext_csd.generic_cmd6_time); + if (card->ext_csd.hs_max_dtr != 0) { + err = 0; + if (card->ext_csd.hs_max_dtr > 52000000 && + host->caps2 & MMC_CAP2_HS200) + err = mmc_select_hs200(card); + else if (host->caps & MMC_CAP_MMC_HIGHSPEED) + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, 1, 0); + if (err && err != -EBADMSG) goto free_card; @@ -918,8 +1016,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, mmc_hostname(card->host)); err = 0; } else { - mmc_card_set_highspeed(card); - mmc_set_timing(card->host, MMC_TIMING_MMC_HS); + if (card->ext_csd.hs_max_dtr > 52000000 && + host->caps2 & MMC_CAP2_HS200) { + mmc_card_set_hs200(card); + mmc_set_timing(card->host, + MMC_TIMING_MMC_HS200); + } else { + mmc_card_set_highspeed(card); + mmc_set_timing(card->host, MMC_TIMING_MMC_HS); + } } } @@ -944,7 +1049,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, */ max_dtr = (unsigned int)-1; - if (mmc_card_highspeed(card)) { + if (mmc_card_highspeed(card) || mmc_card_hs200(card)) { if (max_dtr > card->ext_csd.hs_max_dtr) max_dtr = card->ext_csd.hs_max_dtr; } else if (max_dtr > card->csd.max_dtr) { @@ -969,10 +1074,49 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, ddr = MMC_1_2V_DDR_MODE; } + /* + * Indicate HS200 SDR mode (if supported). + */ + if (mmc_card_hs200(card)) { + u32 ext_csd_bits; + u32 bus_width = card->host->ios.bus_width; + + /* + * For devices supporting HS200 mode, the bus width has + * to be set before executing the tuning function. If + * set before tuning, then device will respond with CRC + * errors for responses on CMD line. So for HS200 the + * sequence will be + * 1. set bus width 4bit / 8 bit (1 bit not supported) + * 2. switch to HS200 mode + * 3. set the clock to > 52Mhz <=200MHz and + * 4. execute tuning for HS200 + */ + if ((host->caps2 & MMC_CAP2_HS200) && + card->host->ops->execute_tuning) + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK_HS200); + if (err) { + pr_warning("%s: tuning execution failed\n", + mmc_hostname(card->host)); + goto err; + } + + ext_csd_bits = (bus_width == MMC_BUS_WIDTH_8) ? + EXT_CSD_BUS_WIDTH_8 : EXT_CSD_BUS_WIDTH_4; + err = mmc_select_powerclass(card, ext_csd_bits, ext_csd); + if (err) { + pr_err("%s: power class selection to bus width %d failed\n", + mmc_hostname(card->host), 1 << bus_width); + goto err; + } + } + /* * Activate wide bus and DDR (if supported). */ - if ((card->csd.mmca_vsn >= CSD_SPEC_VER_4) && + if (!mmc_card_hs200(card) && + (card->csd.mmca_vsn >= CSD_SPEC_VER_3) && (host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) { static unsigned ext_csd_bits[][2] = { { EXT_CSD_BUS_WIDTH_8, EXT_CSD_DDR_BUS_WIDTH_8 }, diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 6f27d35081b8..c63ad03c29c7 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -661,7 +661,8 @@ static int mmc_sd_init_uhs_card(struct mmc_card *card) /* SPI mode doesn't define CMD19 */ if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning) - err = card->host->ops->execute_tuning(card->host); + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK); out: kfree(status); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index b77f770ce5d1..bd7bacc950dc 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -556,7 +557,8 @@ static int mmc_sdio_init_uhs_card(struct mmc_card *card) /* Initialize and start re-tuning timer */ if (!mmc_host_is_spi(card->host) && card->host->ops->execute_tuning) - err = card->host->ops->execute_tuning(card->host); + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK); out: diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 9478a6bf1bb1..9f22ba572de0 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -216,6 +216,7 @@ struct mmc_card { #define MMC_STATE_ULTRAHIGHSPEED (1<<5) /* card is in ultra high speed mode */ #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 */ 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 */ @@ -374,6 +375,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_present(c) ((c)->state & MMC_STATE_PRESENT) #define mmc_card_readonly(c) ((c)->state & MMC_STATE_READONLY) #define mmc_card_highspeed(c) ((c)->state & MMC_STATE_HIGHSPEED) +#define mmc_card_hs200(c) ((c)->state & MMC_STATE_HIGHSPEED_200) #define mmc_card_blockaddr(c) ((c)->state & MMC_STATE_BLOCKADDR) #define mmc_card_ddr_mode(c) ((c)->state & MMC_STATE_HIGHSPEED_DDR) #define mmc_card_uhs(c) ((c)->state & MMC_STATE_ULTRAHIGHSPEED) @@ -384,6 +386,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) #define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED) +#define mmc_card_set_hs200(c) ((c)->state |= MMC_STATE_HIGHSPEED_200) #define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR) #define mmc_card_set_ddr_mode(c) ((c)->state |= MMC_STATE_HIGHSPEED_DDR) #define mmc_card_set_uhs(c) ((c)->state |= MMC_STATE_ULTRAHIGHSPEED) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 031d865167a2..dd13e0539092 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -56,10 +56,13 @@ struct mmc_ios { #define MMC_TIMING_UHS_SDR50 3 #define MMC_TIMING_UHS_SDR104 4 #define MMC_TIMING_UHS_DDR50 5 +#define MMC_TIMING_MMC_HS200 6 #define MMC_SDR_MODE 0 #define MMC_1_2V_DDR_MODE 1 #define MMC_1_8V_DDR_MODE 2 +#define MMC_1_2V_SDR_MODE 3 +#define MMC_1_8V_SDR_MODE 4 unsigned char signal_voltage; /* signalling voltage (1.8V or 3.3V) */ @@ -148,7 +151,9 @@ struct mmc_host_ops { void (*init_card)(struct mmc_host *host, struct mmc_card *card); int (*start_signal_voltage_switch)(struct mmc_host *host, struct mmc_ios *ios); - int (*execute_tuning)(struct mmc_host *host); + + /* The tuning command opcode value is different for SD and eMMC cards */ + int (*execute_tuning)(struct mmc_host *host, u32 opcode); void (*enable_preset_value)(struct mmc_host *host, bool enable); int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); void (*hw_reset)(struct mmc_host *host); @@ -248,6 +253,10 @@ struct mmc_host { #define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */ #define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */ #define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */ +#define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */ +#define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */ +#define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \ + MMC_CAP2_HS200_1_2V_SDR) mmc_pm_flag_t pm_caps; /* supported pm features */ unsigned int power_notify_type; diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 665548e639e8..fb9f6e116e1c 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -51,6 +51,7 @@ #define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */ #define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */ #define MMC_SEND_TUNING_BLOCK 19 /* adtc R1 */ +#define MMC_SEND_TUNING_BLOCK_HS200 21 /* adtc R1 */ /* class 3 */ #define MMC_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */ @@ -339,13 +340,76 @@ struct _mmc_csd { #define EXT_CSD_CARD_TYPE_26 (1<<0) /* Card can run at 26MHz */ #define EXT_CSD_CARD_TYPE_52 (1<<1) /* Card can run at 52MHz */ -#define EXT_CSD_CARD_TYPE_MASK 0xF /* Mask out reserved bits */ +#define EXT_CSD_CARD_TYPE_MASK 0x3F /* Mask out reserved bits */ #define EXT_CSD_CARD_TYPE_DDR_1_8V (1<<2) /* Card can run at 52MHz */ /* DDR mode @1.8V or 3V I/O */ #define EXT_CSD_CARD_TYPE_DDR_1_2V (1<<3) /* Card can run at 52MHz */ /* DDR mode @1.2V I/O */ #define EXT_CSD_CARD_TYPE_DDR_52 (EXT_CSD_CARD_TYPE_DDR_1_8V \ | EXT_CSD_CARD_TYPE_DDR_1_2V) +#define EXT_CSD_CARD_TYPE_SDR_1_8V (1<<4) /* Card can run at 200MHz */ +#define EXT_CSD_CARD_TYPE_SDR_1_2V (1<<5) /* Card can run at 200MHz */ + /* SDR mode @1.2V I/O */ + +#define EXT_CSD_CARD_TYPE_SDR_200 (EXT_CSD_CARD_TYPE_SDR_1_8V | \ + EXT_CSD_CARD_TYPE_SDR_1_2V) + +#define EXT_CSD_CARD_TYPE_SDR_ALL (EXT_CSD_CARD_TYPE_SDR_200 | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_2V_ALL (EXT_CSD_CARD_TYPE_SDR_1_2V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_8V_ALL (EXT_CSD_CARD_TYPE_SDR_1_8V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_1_8V (EXT_CSD_CARD_TYPE_SDR_1_2V | \ + EXT_CSD_CARD_TYPE_DDR_1_8V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_1_8V (EXT_CSD_CARD_TYPE_SDR_1_8V | \ + EXT_CSD_CARD_TYPE_DDR_1_8V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_1_2V (EXT_CSD_CARD_TYPE_SDR_1_2V | \ + EXT_CSD_CARD_TYPE_DDR_1_2V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_1_2V (EXT_CSD_CARD_TYPE_SDR_1_8V | \ + EXT_CSD_CARD_TYPE_DDR_1_2V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_2V_DDR_52 (EXT_CSD_CARD_TYPE_SDR_1_2V | \ + EXT_CSD_CARD_TYPE_DDR_52 | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_1_8V_DDR_52 (EXT_CSD_CARD_TYPE_SDR_1_8V | \ + EXT_CSD_CARD_TYPE_DDR_52 | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_ALL_DDR_1_8V (EXT_CSD_CARD_TYPE_SDR_200 | \ + EXT_CSD_CARD_TYPE_DDR_1_8V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_ALL_DDR_1_2V (EXT_CSD_CARD_TYPE_SDR_200 | \ + EXT_CSD_CARD_TYPE_DDR_1_2V | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) + +#define EXT_CSD_CARD_TYPE_SDR_ALL_DDR_52 (EXT_CSD_CARD_TYPE_SDR_200 | \ + EXT_CSD_CARD_TYPE_DDR_52 | \ + EXT_CSD_CARD_TYPE_52 | \ + EXT_CSD_CARD_TYPE_26) #define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */ #define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */ -- cgit v1.2.3 From 069c9f142822d552ec885572945d8bce9eff0519 Mon Sep 17 00:00:00 2001 From: Girish K S Date: Fri, 6 Jan 2012 09:56:39 +0530 Subject: mmc: host: Adds support for eMMC 4.5 HS200 mode This patch adds support for the HS200 mode on the host side. Also enables the tuning feature required when the HS200 mode is selected. Signed-off-by: Girish K S Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci.c | 57 ++++++++++++++++++++++++++++++++++------------- drivers/mmc/host/sdhci.h | 1 + include/linux/mmc/sdhci.h | 1 + 3 files changed, 44 insertions(+), 15 deletions(-) (limited to 'include/linux') diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0636e9a587b1..96f4e548ba22 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -49,7 +49,7 @@ static void sdhci_finish_data(struct sdhci_host *); static void sdhci_send_command(struct sdhci_host *, struct mmc_command *); static void sdhci_finish_command(struct sdhci_host *); -static int sdhci_execute_tuning(struct mmc_host *mmc); +static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); static void sdhci_tuning_timer(unsigned long data); #ifdef CONFIG_PM_RUNTIME @@ -1014,7 +1014,8 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) flags |= SDHCI_CMD_INDEX; /* CMD19 is special in that the Data Present Select should be set */ - if (cmd->data || (cmd->opcode == MMC_SEND_TUNING_BLOCK)) + if (cmd->data || cmd->opcode == MMC_SEND_TUNING_BLOCK || + cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) flags |= SDHCI_CMD_DATA; sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); @@ -1287,7 +1288,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) if ((host->flags & SDHCI_NEEDS_RETUNING) && !(present_state & (SDHCI_DOING_WRITE | SDHCI_DOING_READ))) { spin_unlock_irqrestore(&host->lock, flags); - sdhci_execute_tuning(mmc); + sdhci_execute_tuning(mmc, mrq->cmd->opcode); spin_lock_irqsave(&host->lock, flags); /* Restore original mmc_request structure */ @@ -1382,7 +1383,8 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) unsigned int clock; /* In case of UHS-I modes, set High Speed Enable */ - if ((ios->timing == MMC_TIMING_UHS_SDR50) || + if ((ios->timing == MMC_TIMING_MMC_HS200) || + (ios->timing == MMC_TIMING_UHS_SDR50) || (ios->timing == MMC_TIMING_UHS_SDR104) || (ios->timing == MMC_TIMING_UHS_DDR50) || (ios->timing == MMC_TIMING_UHS_SDR25)) @@ -1435,7 +1437,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* Select Bus Speed Mode for host */ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; - if (ios->timing == MMC_TIMING_UHS_SDR12) + if (ios->timing == MMC_TIMING_MMC_HS200) + ctrl_2 |= SDHCI_CTRL_HS_SDR200; + else if (ios->timing == MMC_TIMING_UHS_SDR12) ctrl_2 |= SDHCI_CTRL_UHS_SDR12; else if (ios->timing == MMC_TIMING_UHS_SDR25) ctrl_2 |= SDHCI_CTRL_UHS_SDR25; @@ -1682,7 +1686,7 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, return err; } -static int sdhci_execute_tuning(struct mmc_host *mmc) +static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct sdhci_host *host; u16 ctrl; @@ -1690,6 +1694,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) int tuning_loop_counter = MAX_TUNING_LOOP; unsigned long timeout; int err = 0; + bool requires_tuning_nonuhs = false; host = mmc_priv(mmc); @@ -1700,13 +1705,19 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* - * Host Controller needs tuning only in case of SDR104 mode - * and for SDR50 mode when Use Tuning for SDR50 is set in + * The Host Controller needs tuning only in case of SDR104 mode + * and for SDR50 mode when Use Tuning for SDR50 is set in the * Capabilities register. + * If the Host Controller supports the HS200 mode then the + * tuning function has to be executed. */ + if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) && + (host->flags & SDHCI_SDR50_NEEDS_TUNING || + host->flags & SDHCI_HS200_NEEDS_TUNING)) + requires_tuning_nonuhs = true; + if (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR104) || - (((ctrl & SDHCI_CTRL_UHS_MASK) == SDHCI_CTRL_UHS_SDR50) && - (host->flags & SDHCI_SDR50_NEEDS_TUNING))) + requires_tuning_nonuhs) ctrl |= SDHCI_CTRL_EXEC_TUNING; else { spin_unlock(&host->lock); @@ -1742,7 +1753,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) if (!tuning_loop_counter && !timeout) break; - cmd.opcode = MMC_SEND_TUNING_BLOCK; + cmd.opcode = opcode; cmd.arg = 0; cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; cmd.retries = 0; @@ -1757,7 +1768,17 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) * block to the Host Controller. So we set the block size * to 64 here. */ - sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), SDHCI_BLOCK_SIZE); + if (cmd.opcode == MMC_SEND_TUNING_BLOCK_HS200) { + if (mmc->ios.bus_width == MMC_BUS_WIDTH_8) + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 128), + SDHCI_BLOCK_SIZE); + else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), + SDHCI_BLOCK_SIZE); + } else { + sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), + SDHCI_BLOCK_SIZE); + } /* * The tuning block is sent by the card to the host controller. @@ -2140,12 +2161,14 @@ static void sdhci_show_adma_error(struct sdhci_host *host) { } static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) { + u32 command; BUG_ON(intmask == 0); /* CMD19 generates _only_ Buffer Read Ready interrupt */ if (intmask & SDHCI_INT_DATA_AVAIL) { - if (SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) == - MMC_SEND_TUNING_BLOCK) { + command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); + if (command == MMC_SEND_TUNING_BLOCK || + command == MMC_SEND_TUNING_BLOCK_HS200) { host->tuning_done = 1; wake_up(&host->buf_ready_int); return; @@ -2747,10 +2770,14 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[1] & SDHCI_SUPPORT_DDR50) mmc->caps |= MMC_CAP_UHS_DDR50; - /* Does the host needs tuning for SDR50? */ + /* Does the host need tuning for SDR50? */ if (caps[1] & SDHCI_USE_SDR50_TUNING) host->flags |= SDHCI_SDR50_NEEDS_TUNING; + /* Does the host need tuning for HS200? */ + if (mmc->caps2 & MMC_CAP2_HS200) + host->flags |= SDHCI_HS200_NEEDS_TUNING; + /* Driver Type(s) (A, C, D) supported by the host */ if (caps[1] & SDHCI_DRIVER_TYPE_A) mmc->caps |= MMC_CAP_DRIVER_TYPE_A; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index a04d4d0c6fd2..ad265b96b75b 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -158,6 +158,7 @@ #define SDHCI_CTRL_UHS_SDR50 0x0002 #define SDHCI_CTRL_UHS_SDR104 0x0003 #define SDHCI_CTRL_UHS_DDR50 0x0004 +#define SDHCI_CTRL_HS_SDR200 0x0005 /* reserved value in SDIO spec */ #define SDHCI_CTRL_VDD_180 0x0008 #define SDHCI_CTRL_DRV_TYPE_MASK 0x0030 #define SDHCI_CTRL_DRV_TYPE_B 0x0000 diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index dad7a469f09c..c750f85177d9 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -119,6 +119,7 @@ struct sdhci_host { #define SDHCI_AUTO_CMD23 (1<<7) /* Auto CMD23 support */ #define SDHCI_PV_ENABLED (1<<8) /* Preset value enabled */ #define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */ +#define SDHCI_HS200_NEEDS_TUNING (1<<10) /* HS200 needs tuning */ unsigned int version; /* SDHCI spec. version */ -- cgit v1.2.3