From 7cc9ec9f2a3a92def752ed31253fa0b89484b39f Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 19 Apr 2016 16:21:16 -0700 Subject: [PATCH 001/472] Revert "mmc: core: Remove MMC_CLKGATE" This reverts commit 9eadcc0581a8ccaf4c2378aa1c193fb164304f1d. Clock gating is needed for Qualcomm Platforms hence reverting this upstream patch. Change-Id: I96ac0c1c7627e8e5c2d18782e2fc08608f0a7f91 Signed-off-by: Subhash Jadavani --- Documentation/mmc/mmc-dev-attrs.txt | 10 ++ drivers/mmc/core/Kconfig | 10 ++ drivers/mmc/core/core.c | 139 ++++++++++++++-- drivers/mmc/core/core.h | 3 + drivers/mmc/core/debugfs.c | 5 + drivers/mmc/core/host.c | 245 ++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 2 + drivers/mmc/core/quirks.c | 18 ++ drivers/mmc/core/sd.c | 2 + drivers/mmc/core/sdio.c | 7 +- drivers/mmc/core/sdio_irq.c | 14 +- include/linux/mmc/card.h | 1 + include/linux/mmc/host.h | 32 ++++ 13 files changed, 469 insertions(+), 19 deletions(-) diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index caa555706f89..189bab09255a 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -72,3 +72,13 @@ Note on raw_rpmb_size_mult: "raw_rpmb_size_mult" is a mutliple of 128kB block. RPMB size in byte is calculated by using the following equation: RPMB partition size = 128kB x raw_rpmb_size_mult + +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 milliseconds. + +echo > /sys/class/mmc_host/mmcX/clkgate_delay diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig index 87cc07dedd9f..57cc6b29b2d0 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -16,3 +16,13 @@ config MMC_PARANOID_SD_INIT about re-trying SD init requests. This can be a useful work-around for buggy controllers and hardware. Enable if you are experiencing issues with SD detection. + +config MMC_CLKGATE + bool "MMC host clock gating" + help + This will attempt to aggressively gate the clock to the MMC card. + This is done to save power due to gating off the logic and bus + noise when the MMC card is not in use. Your host driver has to + support handling this in order for it to be of any use. + + If unsure, say N. diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 3e54185bc985..55a0e84d004c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -190,6 +190,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) if (mrq->done) mrq->done(mrq); + + mmc_host_clk_release(host); } } @@ -293,6 +295,7 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->mrq = mrq; } } + mmc_host_clk_hold(host); led_trigger_event(host->led, LED_FULL); __mmc_start_request(host, mrq); @@ -542,8 +545,11 @@ static void mmc_wait_for_req_done(struct mmc_host *host, static void mmc_pre_req(struct mmc_host *host, struct mmc_request *mrq, bool is_first_req) { - if (host->ops->pre_req) + if (host->ops->pre_req) { + mmc_host_clk_hold(host); host->ops->pre_req(host, mrq, is_first_req); + mmc_host_clk_release(host); + } } /** @@ -558,8 +564,11 @@ static void mmc_pre_req(struct mmc_host *host, struct mmc_request *mrq, static void mmc_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) { - if (host->ops->post_req) + if (host->ops->post_req) { + mmc_host_clk_hold(host); host->ops->post_req(host, mrq, err); + mmc_host_clk_release(host); + } } /** @@ -848,9 +857,9 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) unsigned int timeout_us, limit_us; timeout_us = data->timeout_ns / 1000; - if (card->host->ios.clock) + if (mmc_host_clk_rate(card->host)) timeout_us += data->timeout_clks * 1000 / - (card->host->ios.clock / 1000); + (mmc_host_clk_rate(card->host) / 1000); if (data->flags & MMC_DATA_WRITE) /* @@ -1048,6 +1057,8 @@ static inline void mmc_set_ios(struct mmc_host *host) ios->power_mode, ios->chip_select, ios->vdd, ios->bus_width, ios->timing); + if (ios->clock > 0) + mmc_set_ungated(host); host->ops->set_ios(host, ios); } @@ -1056,15 +1067,17 @@ static inline void mmc_set_ios(struct mmc_host *host) */ void mmc_set_chip_select(struct mmc_host *host, int mode) { + mmc_host_clk_hold(host); host->ios.chip_select = mode; mmc_set_ios(host); + mmc_host_clk_release(host); } /* * Sets the host clock to the highest possible frequency that * is below "hz". */ -void mmc_set_clock(struct mmc_host *host, unsigned int hz) +static void __mmc_set_clock(struct mmc_host *host, unsigned int hz) { WARN_ON(hz && hz < host->f_min); @@ -1075,6 +1088,68 @@ void mmc_set_clock(struct mmc_host *host, unsigned int hz) mmc_set_ios(host); } +void mmc_set_clock(struct mmc_host *host, unsigned int hz) +{ + mmc_host_clk_hold(host); + __mmc_set_clock(host, hz); + mmc_host_clk_release(host); +} + +#ifdef CONFIG_MMC_CLKGATE +/* + * This gates the clock by setting it to 0 Hz. + */ +void mmc_gate_clock(struct mmc_host *host) +{ + unsigned long flags; + + spin_lock_irqsave(&host->clk_lock, flags); + host->clk_old = host->ios.clock; + host->ios.clock = 0; + host->clk_gated = true; + spin_unlock_irqrestore(&host->clk_lock, flags); + mmc_set_ios(host); +} + +/* + * This restores the clock from gating by using the cached + * clock value. + */ +void mmc_ungate_clock(struct mmc_host *host) +{ + /* + * We should previously have gated the clock, so the clock shall + * be 0 here! The clock may however be 0 during initialization, + * when some request operations are performed before setting + * the frequency. When ungate is requested in that situation + * we just ignore the call. + */ + if (host->clk_old) { + BUG_ON(host->ios.clock); + /* This call will also set host->clk_gated to false */ + __mmc_set_clock(host, host->clk_old); + } +} + +void mmc_set_ungated(struct mmc_host *host) +{ + unsigned long flags; + + /* + * We've been given a new frequency while the clock is gated, + * so make sure we regard this as ungating it. + */ + spin_lock_irqsave(&host->clk_lock, flags); + host->clk_gated = false; + spin_unlock_irqrestore(&host->clk_lock, flags); +} + +#else +void mmc_set_ungated(struct mmc_host *host) +{ +} +#endif + int mmc_execute_tuning(struct mmc_card *card) { struct mmc_host *host = card->host; @@ -1089,7 +1164,9 @@ int mmc_execute_tuning(struct mmc_card *card) else opcode = MMC_SEND_TUNING_BLOCK; + mmc_host_clk_hold(host); err = host->ops->execute_tuning(host, opcode); + mmc_host_clk_release(host); if (err) pr_err("%s: tuning execution failed\n", mmc_hostname(host)); @@ -1104,8 +1181,10 @@ int mmc_execute_tuning(struct mmc_card *card) */ void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode) { + mmc_host_clk_hold(host); host->ios.bus_mode = mode; mmc_set_ios(host); + mmc_host_clk_release(host); } /* @@ -1113,8 +1192,10 @@ void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode) */ void mmc_set_bus_width(struct mmc_host *host, unsigned int width) { + mmc_host_clk_hold(host); host->ios.bus_width = width; mmc_set_ios(host); + mmc_host_clk_release(host); } /* @@ -1555,8 +1636,11 @@ int __mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage) int old_signal_voltage = host->ios.signal_voltage; host->ios.signal_voltage = signal_voltage; - if (host->ops->start_signal_voltage_switch) + if (host->ops->start_signal_voltage_switch) { + mmc_host_clk_hold(host); err = host->ops->start_signal_voltage_switch(host, &host->ios); + mmc_host_clk_release(host); + } if (err) host->ios.signal_voltage = old_signal_voltage; @@ -1590,17 +1674,20 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr) pr_warn("%s: cannot verify signal voltage switch\n", mmc_hostname(host)); + mmc_host_clk_hold(host); + cmd.opcode = SD_SWITCH_VOLTAGE; cmd.arg = 0; cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(host, &cmd, 0); if (err) - return err; - - if (!mmc_host_is_spi(host) && (cmd.resp[0] & R1_ERROR)) - return -EIO; + goto err_command; + if (!mmc_host_is_spi(host) && (cmd.resp[0] & R1_ERROR)) { + err = -EIO; + goto err_command; + } /* * The card should drive cmd and dat[0:3] low immediately * after the response of cmd11, but wait 1 ms to be sure @@ -1649,6 +1736,9 @@ power_cycle: mmc_power_cycle(host, ocr); } +err_command: + mmc_host_clk_release(host); + return err; } @@ -1657,8 +1747,10 @@ power_cycle: */ void mmc_set_timing(struct mmc_host *host, unsigned int timing) { + mmc_host_clk_hold(host); host->ios.timing = timing; mmc_set_ios(host); + mmc_host_clk_release(host); } /* @@ -1666,8 +1758,10 @@ void mmc_set_timing(struct mmc_host *host, unsigned int timing) */ void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type) { + mmc_host_clk_hold(host); host->ios.drv_type = drv_type; mmc_set_ios(host); + mmc_host_clk_release(host); } int mmc_select_drive_strength(struct mmc_card *card, unsigned int max_dtr, @@ -1675,6 +1769,7 @@ int mmc_select_drive_strength(struct mmc_card *card, unsigned int max_dtr, { struct mmc_host *host = card->host; int host_drv_type = SD_DRIVER_TYPE_B; + int drive_strength; *drv_type = 0; @@ -1697,10 +1792,14 @@ int mmc_select_drive_strength(struct mmc_card *card, unsigned int max_dtr, * information and let the hardware specific code * return what is possible given the options */ - return host->ops->select_drive_strength(card, max_dtr, - host_drv_type, - card_drv_type, - drv_type); + mmc_host_clk_hold(host); + drive_strength = host->ops->select_drive_strength(card, max_dtr, + host_drv_type, + card_drv_type, + drv_type); + mmc_host_clk_release(host); + + return drive_strength; } /* @@ -1719,6 +1818,8 @@ void mmc_power_up(struct mmc_host *host, u32 ocr) if (host->ios.power_mode == MMC_POWER_ON) return; + mmc_host_clk_hold(host); + mmc_pwrseq_pre_power_on(host); host->ios.vdd = fls(ocr) - 1; @@ -1752,6 +1853,8 @@ void mmc_power_up(struct mmc_host *host, u32 ocr) * time required to reach a stable voltage. */ mmc_delay(10); + + mmc_host_clk_release(host); } void mmc_power_off(struct mmc_host *host) @@ -1759,6 +1862,8 @@ void mmc_power_off(struct mmc_host *host) if (host->ios.power_mode == MMC_POWER_OFF) return; + mmc_host_clk_hold(host); + mmc_pwrseq_power_off(host); host->ios.clock = 0; @@ -1774,6 +1879,8 @@ void mmc_power_off(struct mmc_host *host) * can be successfully turned on again. */ mmc_delay(1); + + mmc_host_clk_release(host); } void mmc_power_cycle(struct mmc_host *host, u32 ocr) @@ -1989,7 +2096,7 @@ static unsigned int mmc_mmc_erase_timeout(struct mmc_card *card, */ timeout_clks <<= 1; timeout_us += (timeout_clks * 1000) / - (card->host->ios.clock / 1000); + (mmc_host_clk_rate(card->host) / 1000); erase_timeout = timeout_us / 1000; @@ -2443,7 +2550,9 @@ static void mmc_hw_reset_for_init(struct mmc_host *host) { if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) return; + mmc_host_clk_hold(host); host->ops->hw_reset(host); + mmc_host_clk_release(host); } int mmc_hw_reset(struct mmc_host *host) diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 09241e56d628..1a22a82209b2 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -40,6 +40,9 @@ void mmc_init_erase(struct mmc_card *card); void mmc_set_chip_select(struct mmc_host *host, int mode); void mmc_set_clock(struct mmc_host *host, unsigned int hz); +void mmc_gate_clock(struct mmc_host *host); +void mmc_ungate_clock(struct mmc_host *host); +void mmc_set_ungated(struct mmc_host *host); void mmc_set_bus_mode(struct mmc_host *host, unsigned int mode); void mmc_set_bus_width(struct mmc_host *host, unsigned int width); u32 mmc_select_voltage(struct mmc_host *host, u32 ocr); diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 154aced0b91b..f4db93e03e88 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -255,6 +255,11 @@ void mmc_add_host_debugfs(struct mmc_host *host) &mmc_clock_fops)) goto err_node; +#ifdef CONFIG_MMC_CLKGATE + if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR), + root, &host->clk_delay)) + goto err_node; +#endif #ifdef CONFIG_FAIL_MMC_REQUEST if (fail_request) setup_fault_attr(&fail_default_attr, fail_request); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index fcf7829c759e..649b8ab923ce 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -61,6 +61,246 @@ void mmc_unregister_host_class(void) class_unregister(&mmc_host_class); } +#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\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); + return count; +} + +/* + * Enabling clock gating will make the core call out to the host + * once up and once down when it performs a request or card operation + * intermingled in any fashion. The driver will see this through + * set_ios() operations with ios.clock field set to 0 to gate (disable) + * the block clock, and to the old frequency to enable it again. + */ +static void mmc_host_clk_gate_delayed(struct mmc_host *host) +{ + unsigned long tick_ns; + unsigned long freq = host->ios.clock; + unsigned long flags; + + if (!freq) { + pr_debug("%s: frequency set to 0 in disable function, " + "this means the clock is already disabled.\n", + mmc_hostname(host)); + return; + } + /* + * New requests may have appeared while we were scheduling, + * then there is no reason to delay the check before + * clk_disable(). + */ + spin_lock_irqsave(&host->clk_lock, flags); + + /* + * Delay n bus cycles (at least 8 from MMC spec) before attempting + * to disable the MCI block clock. The reference count may have + * gone up again after this delay due to rescheduling! + */ + if (!host->clk_requests) { + spin_unlock_irqrestore(&host->clk_lock, flags); + tick_ns = DIV_ROUND_UP(1000000000, freq); + ndelay(host->clk_delay * tick_ns); + } else { + /* New users appeared while waiting for this work */ + spin_unlock_irqrestore(&host->clk_lock, flags); + return; + } + mutex_lock(&host->clk_gate_mutex); + spin_lock_irqsave(&host->clk_lock, flags); + if (!host->clk_requests) { + spin_unlock_irqrestore(&host->clk_lock, flags); + /* This will set host->ios.clock to 0 */ + mmc_gate_clock(host); + spin_lock_irqsave(&host->clk_lock, flags); + pr_debug("%s: gated MCI clock\n", mmc_hostname(host)); + } + spin_unlock_irqrestore(&host->clk_lock, flags); + mutex_unlock(&host->clk_gate_mutex); +} + +/* + * Internal work. Work to disable the clock at some later point. + */ +static void mmc_host_clk_gate_work(struct work_struct *work) +{ + struct mmc_host *host = container_of(work, struct mmc_host, + clk_gate_work.work); + + mmc_host_clk_gate_delayed(host); +} + +/** + * mmc_host_clk_hold - ungate hardware MCI clocks + * @host: host to ungate. + * + * Makes sure the host ios.clock is restored to a non-zero value + * past this call. Increase clock reference count and ungate clock + * if we're the first user. + */ +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) { + spin_unlock_irqrestore(&host->clk_lock, flags); + mmc_ungate_clock(host); + spin_lock_irqsave(&host->clk_lock, flags); + pr_debug("%s: ungated MCI clock\n", mmc_hostname(host)); + } + host->clk_requests++; + spin_unlock_irqrestore(&host->clk_lock, flags); + mutex_unlock(&host->clk_gate_mutex); +} + +/** + * mmc_host_may_gate_card - check if this card may be gated + * @card: card to check. + */ +static bool mmc_host_may_gate_card(struct mmc_card *card) +{ + /* If there is no card we may gate it */ + if (!card) + return true; + /* + * Don't gate SDIO cards! These need to be clocked at all times + * since they may be independent systems generating interrupts + * and other events. The clock requests counter from the core will + * go down to zero since the core does not need it, but we will not + * gate the clock, because there is somebody out there that may still + * be using it. + */ + return !(card->quirks & MMC_QUIRK_BROKEN_CLK_GATING); +} + +/** + * mmc_host_clk_release - gate off hardware MCI clocks + * @host: host to gate. + * + * Calls the host driver with ios.clock set to zero as often as possible + * in order to gate off hardware MCI clocks. Decrease clock reference + * count and schedule disabling of clock. + */ +void mmc_host_clk_release(struct mmc_host *host) +{ + unsigned long flags; + + spin_lock_irqsave(&host->clk_lock, flags); + host->clk_requests--; + if (mmc_host_may_gate_card(host->card) && + !host->clk_requests) + schedule_delayed_work(&host->clk_gate_work, + msecs_to_jiffies(host->clkgate_delay)); + spin_unlock_irqrestore(&host->clk_lock, flags); +} + +/** + * mmc_host_clk_rate - get current clock frequency setting + * @host: host to get the clock frequency for. + * + * Returns current clock frequency regardless of gating. + */ +unsigned int mmc_host_clk_rate(struct mmc_host *host) +{ + unsigned long freq; + unsigned long flags; + + spin_lock_irqsave(&host->clk_lock, flags); + if (host->clk_gated) + freq = host->clk_old; + else + freq = host->ios.clock; + spin_unlock_irqrestore(&host->clk_lock, flags); + return freq; +} + +/** + * mmc_host_clk_init - set up clock gating code + * @host: host with potential clock to control + */ +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 0ms to avoid wasting power. + * This value can be tuned by writing into sysfs entry. + */ + host->clkgate_delay = 0; + host->clk_gated = false; + INIT_DELAYED_WORK(&host->clk_gate_work, mmc_host_clk_gate_work); + spin_lock_init(&host->clk_lock); + mutex_init(&host->clk_gate_mutex); +} + +/** + * mmc_host_clk_exit - shut down clock gating code + * @host: host with potential clock to control + */ +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_delayed_work_sync(&host->clk_gate_work)) + mmc_host_clk_gate_delayed(host); + if (host->clk_gated) + mmc_host_clk_hold(host); + /* There should be only one user now */ + 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) +{ +} + +static inline void mmc_host_clk_exit(struct mmc_host *host) +{ +} + +static inline void mmc_host_clk_sysfs_init(struct mmc_host *host) +{ +} + +#endif + void mmc_retune_enable(struct mmc_host *host) { host->can_retune = 1; @@ -345,6 +585,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) return NULL; } + mmc_host_clk_init(host); + spin_lock_init(&host->lock); init_waitqueue_head(&host->wq); INIT_DELAYED_WORK(&host->detect, mmc_rescan); @@ -393,6 +635,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); if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)) @@ -425,6 +668,8 @@ void mmc_remove_host(struct mmc_host *host) device_del(&host->class_dev); led_trigger_unregister_simple(host->led); + + mmc_host_clk_exit(host); } EXPORT_SYMBOL(mmc_remove_host); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 3a9a79ec4343..dc70b2d7a895 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1976,12 +1976,14 @@ static int mmc_reset(struct mmc_host *host) if (!mmc_can_reset(card)) return -EOPNOTSUPP; + mmc_host_clk_hold(host); mmc_set_clock(host, host->f_init); host->ops->hw_reset(host); /* Set initial state and call mmc_set_ios */ mmc_set_initial_state(host); + mmc_host_clk_release(host); return mmc_init_card(host, card->ocr, card); } diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index fad660b95809..dd1d1e0fe322 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -35,7 +35,25 @@ #define SDIO_DEVICE_ID_MARVELL_8797_F0 0x9128 #endif +/* + * This hook just adds a quirk for all sdio devices + */ +static void add_quirk_for_sdio_devices(struct mmc_card *card, int data) +{ + if (mmc_card_sdio(card)) + card->quirks |= data; +} + static const struct mmc_fixup mmc_fixup_methods[] = { + /* by default sdio devices are considered CLK_GATING broken */ + /* good cards will be whitelisted as they are tested */ + SDIO_FIXUP(SDIO_ANY_ID, SDIO_ANY_ID, + add_quirk_for_sdio_devices, + MMC_QUIRK_BROKEN_CLK_GATING), + + SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_NONSTD_FUNC_IF), diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 4ac721fea4ef..5aa0daf621d3 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -800,7 +800,9 @@ static int mmc_sd_get_ro(struct mmc_host *host) if (!host->ops->get_ro) return -1; + mmc_host_clk_hold(host); ro = host->ops->get_ro(host); + mmc_host_clk_release(host); return ro; } diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index cc93152f82c6..61bb6c739b45 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -977,10 +977,13 @@ static int mmc_sdio_resume(struct mmc_host *host) } if (!err && host->sdio_irqs) { - if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) + if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) { wake_up_process(host->sdio_irq_thread); - else if (host->caps & MMC_CAP_SDIO_IRQ) + } else if (host->caps & MMC_CAP_SDIO_IRQ) { + mmc_host_clk_hold(host); host->ops->enable_sdio_irq(host, 1); + mmc_host_clk_release(host); + } } mmc_release_host(host); diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 91bbbfb29f3f..09cc67d028f0 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -168,15 +168,21 @@ static int sdio_irq_thread(void *_host) } set_current_state(TASK_INTERRUPTIBLE); - if (host->caps & MMC_CAP_SDIO_IRQ) + if (host->caps & MMC_CAP_SDIO_IRQ) { + mmc_host_clk_hold(host); host->ops->enable_sdio_irq(host, 1); + mmc_host_clk_release(host); + } if (!kthread_should_stop()) schedule_timeout(period); set_current_state(TASK_RUNNING); } while (!kthread_should_stop()); - if (host->caps & MMC_CAP_SDIO_IRQ) + if (host->caps & MMC_CAP_SDIO_IRQ) { + mmc_host_clk_hold(host); host->ops->enable_sdio_irq(host, 0); + mmc_host_clk_release(host); + } pr_debug("%s: IRQ thread exiting with code %d\n", mmc_hostname(host), ret); @@ -202,7 +208,9 @@ static int sdio_card_irq_get(struct mmc_card *card) return err; } } else if (host->caps & MMC_CAP_SDIO_IRQ) { + mmc_host_clk_hold(host); host->ops->enable_sdio_irq(host, 1); + mmc_host_clk_release(host); } } @@ -221,7 +229,9 @@ static int sdio_card_irq_put(struct mmc_card *card) atomic_set(&host->sdio_irq_thread_abort, 1); kthread_stop(host->sdio_irq_thread); } else if (host->caps & MMC_CAP_SDIO_IRQ) { + mmc_host_clk_hold(host); host->ops->enable_sdio_irq(host, 0); + mmc_host_clk_release(host); } } diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index eb0151bac50c..fdd0779ccdfa 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -269,6 +269,7 @@ struct mmc_card { /* for byte mode */ #define MMC_QUIRK_NONSTD_SDIO (1<<2) /* non-standard SDIO card attached */ /* (missing CIA registers) */ +#define MMC_QUIRK_BROKEN_CLK_GATING (1<<3) /* clock gating the sdio bus will make card fail */ #define MMC_QUIRK_NONSTD_FUNC_IF (1<<4) /* SDIO card has nonstd function interfaces */ #define MMC_QUIRK_DISABLE_CD (1<<5) /* disconnect CD/DAT[3] resistor */ #define MMC_QUIRK_INAND_CMD38 (1<<6) /* iNAND devices have broken CMD38 */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 40025b28c1fb..2f0d67880f44 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -292,6 +292,18 @@ struct mmc_host { mmc_pm_flag_t pm_caps; /* supported pm features */ +#ifdef CONFIG_MMC_CLKGATE + int clk_requests; /* internal reference counter */ + unsigned int clk_delay; /* number of MCI clk hold cycles */ + bool clk_gated; /* clock gated */ + 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 */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ unsigned short max_segs; /* see blk_queue_max_segments */ @@ -491,6 +503,26 @@ static inline int mmc_host_packed_wr(struct mmc_host *host) return host->caps2 & MMC_CAP2_PACKED_WR; } +#ifdef CONFIG_MMC_CLKGATE +void mmc_host_clk_hold(struct mmc_host *host); +void mmc_host_clk_release(struct mmc_host *host); +unsigned int mmc_host_clk_rate(struct mmc_host *host); + +#else +static inline void mmc_host_clk_hold(struct mmc_host *host) +{ +} + +static inline void mmc_host_clk_release(struct mmc_host *host) +{ +} + +static inline unsigned int mmc_host_clk_rate(struct mmc_host *host) +{ + return host->ios.clock; +} +#endif + static inline int mmc_card_hs(struct mmc_card *card) { return card->host->ios.timing == MMC_TIMING_SD_HS || From d51a3603b4c53412002b356f6c7f3d9363229704 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Thu, 31 Jul 2014 16:40:32 +0300 Subject: [PATCH 002/472] mmc: sdhci-msm: adapt to allow drop-in use in place of downstream driver In order for this driver to function as a drop-in replacement for the msm-3.10 driver: - Allow selectability on ARCH_MSM - Rename clock names to include _clk prefix - Change supported compatible string Change-Id: I20bc683512ebdd22fcd7845c7e43dd645a2f146f Signed-off-by: Georgi Djakov Signed-off-by: Josh Cartwright [venkatg@codeaurora.org: fix kconfig options conflict] Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/Kconfig | 2 +- drivers/mmc/host/sdhci-msm.c | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 1dee533634c9..c27e80f79fc2 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -406,7 +406,7 @@ config MMC_ATMELMCI config MMC_SDHCI_MSM tristate "Qualcomm SDHCI Controller Support" - depends on ARCH_QCOM || (ARM && COMPILE_TEST) + depends on ARCH_QCOM || ARCH_MSM || (ARM && COMPILE_TEST) depends on MMC_SDHCI_PLTFM help This selects the Secure Digital Host Controller Interface (SDHCI) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4695bee203ea..f6c6bd303050 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -31,6 +31,8 @@ #define HC_MODE_EN 0x1 #define CORE_POWER 0x0 #define CORE_SW_RST BIT(7) +#define CORE_HC_SELECT_IN_EN (1 << 18) +#define CORE_HC_SELECT_IN_MASK (7 << 19) #define MAX_PHASES 16 #define CORE_DLL_LOCK BIT(7) @@ -412,7 +414,7 @@ retry: } static const struct of_device_id sdhci_msm_dt_match[] = { - { .compatible = "qcom,sdhci-msm-v4" }, + { .compatible = "qcom,sdhci-msm" }, {}, }; @@ -458,7 +460,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) sdhci_get_of_property(pdev); /* Setup SDCC bus voter clock. */ - msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus"); + msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); if (!IS_ERR(msm_host->bus_clk)) { /* Vote for max. clk rate for max. performance */ ret = clk_set_rate(msm_host->bus_clk, INT_MAX); @@ -470,7 +472,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) } /* Setup main peripheral bus clock */ - msm_host->pclk = devm_clk_get(&pdev->dev, "iface"); + msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk"); if (IS_ERR(msm_host->pclk)) { ret = PTR_ERR(msm_host->pclk); dev_err(&pdev->dev, "Perpheral clk setup failed (%d)\n", ret); @@ -482,7 +484,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto bus_clk_disable; /* Setup SDC MMC clock */ - msm_host->clk = devm_clk_get(&pdev->dev, "core"); + msm_host->clk = devm_clk_get(&pdev->dev, "core_clk"); if (IS_ERR(msm_host->clk)) { ret = PTR_ERR(msm_host->clk); dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret); @@ -507,6 +509,12 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto clk_disable; } + /* Disable mode selection via the vendor specific register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_SELECT_IN_EN + & ~CORE_HC_SELECT_IN_MASK), + host->ioaddr + CORE_VENDOR_SPEC); + /* Reset the core and Enable SDHC mode */ writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | CORE_SW_RST, msm_host->core_mem + CORE_POWER); From 708e9f009fd4f10d281c132c509c5f903ec1bd31 Mon Sep 17 00:00:00 2001 From: Josh Cartwright Date: Mon, 11 Aug 2014 09:33:49 -0500 Subject: [PATCH 003/472] HACK: mmc: sdhci-msm: setup vdd and vdd-io supplies Change-Id: Ifd906146eb61d413880693ec7f306067895f5dac Signed-off-by: Josh Cartwright --- drivers/mmc/host/sdhci-msm.c | 60 ++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f6c6bd303050..9929f6da1373 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -63,6 +63,8 @@ struct sdhci_msm_host { struct clk *bus_clk; /* SDHC bus voter clock */ struct mmc_host *mmc; struct sdhci_pltfm_data sdhci_msm_pdata; + struct regulator *vdd; + struct regulator *vdd_io; }; /* Platform specific tuning */ @@ -459,16 +461,66 @@ static int sdhci_msm_probe(struct platform_device *pdev) sdhci_get_of_property(pdev); + msm_host->vdd = devm_regulator_get(&pdev->dev, "vdd"); + if (IS_ERR(msm_host->vdd)) { + ret = PTR_ERR(msm_host->vdd); + dev_err(&pdev->dev, "VDD regulator setup failed (%d)\n", ret); + goto pltfm_free; + } + + msm_host->vdd_io = devm_regulator_get(&pdev->dev, "vdd-io"); + if (IS_ERR(msm_host->vdd_io)) { + ret = PTR_ERR(msm_host->vdd_io); + dev_err(&pdev->dev, "VDD-IO regulator setup failed (%d)\n", ret); + goto pltfm_free; + } + + ret = regulator_set_optimum_mode(msm_host->vdd, 500000); + if (ret < 0) { + dev_err(&pdev->dev, "VDD setting current load failed (%d)\n", ret); + goto pltfm_free; + } + + ret = regulator_set_optimum_mode(msm_host->vdd_io, 154000); + if (ret < 0) { + dev_err(&pdev->dev, "VDD-IO setting current load failed (%d)\n", ret); + goto pltfm_free; + } + + ret = regulator_set_voltage(msm_host->vdd, 2950000, 2950000); + if (ret) { + dev_err(&pdev->dev, "VDD setting voltage failed (%d)\n", ret); + goto pltfm_free; + } + + ret = regulator_set_voltage(msm_host->vdd_io, 1800000, 1800000); + if (ret) { + dev_err(&pdev->dev, "VDD-IO setting voltage failed (%d)\n", ret); + goto pltfm_free; + } + + ret = regulator_enable(msm_host->vdd); + if (ret) { + dev_err(&pdev->dev, "VDD enabling regulator failed (%d)\n", ret); + goto pltfm_free; + } + + ret = regulator_enable(msm_host->vdd_io); + if (ret) { + dev_err(&pdev->dev, "VDD-IO enabling regulator failed (%d)\n", ret); + goto reg_vdd_disable; + } + /* Setup SDCC bus voter clock. */ msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); if (!IS_ERR(msm_host->bus_clk)) { /* Vote for max. clk rate for max. performance */ ret = clk_set_rate(msm_host->bus_clk, INT_MAX); if (ret) - goto pltfm_free; + goto reg_vdd_io_disable; ret = clk_prepare_enable(msm_host->bus_clk); if (ret) - goto pltfm_free; + goto reg_vdd_io_disable; } /* Setup main peripheral bus clock */ @@ -569,6 +621,10 @@ pclk_disable: bus_clk_disable: if (!IS_ERR(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); +reg_vdd_io_disable: + regulator_disable(msm_host->vdd_io); +reg_vdd_disable: + regulator_disable(msm_host->vdd); pltfm_free: sdhci_pltfm_free(pdev); return ret; From 2e5975b12aeb6b3cc34ca65edeb742590d7a151a Mon Sep 17 00:00:00 2001 From: Guoping Yu Date: Wed, 6 Aug 2014 12:44:55 +0800 Subject: [PATCH 004/472] mmc: core: add long read time fixup for certain Samsung eMMC Certain Samsung eMMC meet multi read timeout, and could not reponse status CMD anymore after that. Add long read timeout fixup to resolve it. Change-Id: Ibeb0e6ab3d889d48fdee91244bec720a6994b907 Signed-off-by: Guoping Yu [venkatg@codeaurora.org: fix trivial merge conflict] Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 21aa3244029b..9ad1cfded1b6 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2515,6 +2515,13 @@ static const struct mmc_fixup blk_fixups[] = MMC_FIXUP(CID_NAME_ANY, CID_MANFID_MICRON, 0x200, add_quirk_mmc, MMC_QUIRK_LONG_READ_TIME), + /* + * Some Samsung MMC cards need longer data read timeout than + * indicated in CSD. + */ + MMC_FIXUP("Q7XSAB", CID_MANFID_SAMSUNG, 0x100, add_quirk_mmc, + MMC_QUIRK_LONG_READ_TIME), + /* * On these Samsung MoviNAND parts, performing secure erase or * secure trim can result in unrecoverable corruption due to a From ef9919233681703694020f1d349bd5f6fcfc6b70 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 2 Sep 2014 17:55:54 -0700 Subject: [PATCH 005/472] mmc: host: sdhci: don't set SDMA buffer boundary in ADMA mode SDMA buffer boundary size parameter in block size register should only be programmed if host controller DMA is operating in SDMA mode otherwise its better not to set this parameter to avoid any side effect when DMA is operating in ADMA mode operation. Change-Id: Ia29ca4759ead2e4c9ea1d72908444a03bf205bac Signed-off-by: Subhash Jadavani [venkatg@codeaurora.org: fix trivial merge conflict] Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index b48565ed5616..766068662f49 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -722,6 +722,17 @@ static void sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd) } } +static void sdhci_set_blk_size_reg(struct sdhci_host *host, unsigned int blksz, + unsigned int sdma_boundary) +{ + if (host->flags & SDHCI_USE_ADMA) + sdhci_writew(host, SDHCI_MAKE_BLKSZ(0, blksz), + SDHCI_BLOCK_SIZE); + else + sdhci_writew(host, SDHCI_MAKE_BLKSZ(sdma_boundary, blksz), + SDHCI_BLOCK_SIZE); +} + static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) { u8 ctrl; @@ -884,8 +895,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) sdhci_set_transfer_irqs(host); /* Set the DMA boundary value and block size */ - sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, - data->blksz), SDHCI_BLOCK_SIZE); + sdhci_set_blk_size_reg(host, data->blksz, SDHCI_DEFAULT_BOUNDARY_ARG); sdhci_writew(host, data->blocks, SDHCI_BLOCK_COUNT); } @@ -1984,14 +1994,11 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) */ 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); + sdhci_set_blk_size_reg(host, 128, 7); else if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) - sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), - SDHCI_BLOCK_SIZE); + sdhci_set_blk_size_reg(host, 64, 7); } else { - sdhci_writew(host, SDHCI_MAKE_BLKSZ(7, 64), - SDHCI_BLOCK_SIZE); + sdhci_set_blk_size_reg(host, 64, 7); } /* From 2edac2000f17d1862c3bc281b9cbeef65fa41462 Mon Sep 17 00:00:00 2001 From: Abhimanyu Kapur Date: Wed, 10 Sep 2014 17:27:43 -0700 Subject: [PATCH 006/472] sdhci: msm: add support for new vendor capabilities Add support for new vendor capabilities on the msm sdhci controller. Change-Id: I934e35de4c566c9ba351e39d6eab3d88ae61a4d0 Signed-off-by: Abhimanyu Kapur [subhashj@codeaurora.org: fix trivial merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9929f6da1373..06775885e7dc 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -50,6 +50,10 @@ #define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c +#define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c +#define CORE_8_BIT_SUPPORT (1 << 18) +#define CORE_3_0V_SUPPORT (1 << 25) + #define CDR_SELEXT_SHIFT 20 #define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT) #define CMUX_SHIFT_PHASE_SHIFT 24 @@ -582,6 +586,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + writel_relaxed((readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | + CORE_3_0V_SUPPORT | CORE_8_BIT_SUPPORT), + host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; From 8e588a1be26b85ad5d6dd4bcb1c1b1a44d1aa253 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 12 Sep 2014 19:23:27 -0700 Subject: [PATCH 007/472] mmc: sdhci-msm: Disable HS200 mode Disable HS200 mode until all msm-3.10 mmc changes have been ported to msm-3.14. This avoids potential hangs at boot. Change-Id: Ifc3dfbad59705db86c133b26baef0bc739a5dc30 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 06775885e7dc..471d8d195998 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -592,6 +592,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; + host->quirks2 |= SDHCI_QUIRK2_BROKEN_HS200; host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", From 6be2ee021df7a86835ca9e049a6bbae2a08bfd9f Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 7 Oct 2014 17:28:52 -0700 Subject: [PATCH 008/472] mmc: sdhci: Reset cmd err only for sbc commands The data complete interrupt is also used to indicate that a busy state has ended. Fix a race condition between sdhci_cmd_irq() that sets any cmd err and sdhci_data_irq() (received to indicate end of busy state) that clears cmd error. This can happen when a cmd err is set and finish tasklet is scheduled but sdhci_data_irq() executes before the tasklet. The cmd err status is critical for the tasklet handler to reset the controller's state machine. This should be cleared only when we have successfully processed a sbc command and are ready to submit the actual command next, not when there is an actual cmd err. CRs-fixed: 733074 Change-Id: I91ea2b949c34446fb629446aabb21505734e27bb Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 766068662f49..9323d7fbef1d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1106,10 +1106,9 @@ static void sdhci_finish_command(struct sdhci_host *host) } } - host->cmd->error = 0; - /* Finished CMD23, now send actual command. */ if (host->cmd == host->mrq->sbc) { + host->cmd->error = 0; host->cmd = NULL; sdhci_send_command(host, host->mrq->cmd); } else { From 97b7bbc9ea362c666ceb10f4a739aa9ff891df69 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 31 Oct 2014 09:46:20 +0530 Subject: [PATCH 009/472] mmc: core: Fix error handling of MMC_BLK_DATA_ERR Avoid retrying using single block for read commands that fail with MMC_BLK_DATA_ERR. The single block read retry is needed only in case of a CRC error for which MMC_BLK_ECC_ERR will be set anyway by mmc_blk_err_check(). Change-Id: Iec9487fd73ecf2bdd5e62732cd42cdb3a639d0dc Signed-off-by: Sahitya Tummala --- drivers/mmc/card/block.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 9ad1cfded1b6..d64d7bfd08c2 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2029,10 +2029,7 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) err = mmc_blk_reset(md, card->host, type); if (!err) break; - if (err == -ENODEV || - mmc_packed_cmd(mq_rq->cmd_type)) - goto cmd_abort; - /* Fall through */ + goto cmd_abort; } case MMC_BLK_ECC_ERR: if (brq->data.blocks > 1) { From 42b23b9bcdc5f97b82c30e48788d2ab5db82f2b7 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 19 Apr 2016 16:35:29 -0700 Subject: [PATCH 010/472] mmc: sdhci-msm: remove support for Qualcomm chipsets The upstream sdhci-msm driver has diverged, removing the upstream version to recreate our internal tree from msm-3.18 to msm-4.4. Change-Id: I952b08667177272fdc30fea79b445f96a3fc2182 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/Kconfig | 17 +- drivers/mmc/host/Makefile | 1 - drivers/mmc/host/sdhci-msm.c | 671 ----------------------------------- 3 files changed, 6 insertions(+), 683 deletions(-) delete mode 100644 drivers/mmc/host/sdhci-msm.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index c27e80f79fc2..77ff230fbbf3 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -404,18 +404,13 @@ config MMC_ATMELMCI If unsure, say N. -config MMC_SDHCI_MSM - tristate "Qualcomm SDHCI Controller Support" - depends on ARCH_QCOM || ARCH_MSM || (ARM && COMPILE_TEST) - depends on MMC_SDHCI_PLTFM +config MMC_MSM + tristate "Qualcomm SDCC Controller Support" + depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50) help - This selects the Secure Digital Host Controller Interface (SDHCI) - support present in Qualcomm SOCs. The controller supports - SD/MMC/SDIO devices. - - If you have a controller with this interface, say Y or M here. - - If unsure, say N. + This provides support for the SD/MMC cell found in the + MSM and QSD SOCs from Qualcomm. The controller also has + support for SDIO devices. config MMC_MXC tristate "Freescale i.MX21/27/31 or MPC512x Multimedia Card support" diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 3595f83e89dd..a3d5b96bb95a 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -73,7 +73,6 @@ obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o -obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o ifeq ($(CONFIG_CB710_DEBUG),y) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c deleted file mode 100644 index 471d8d195998..000000000000 --- a/drivers/mmc/host/sdhci-msm.c +++ /dev/null @@ -1,671 +0,0 @@ -/* - * drivers/mmc/host/sdhci-msm.c - Qualcomm SDHCI Platform driver - * - * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include - -#include "sdhci-pltfm.h" - -#define CORE_MCI_VERSION 0x50 -#define CORE_VERSION_MAJOR_SHIFT 28 -#define CORE_VERSION_MAJOR_MASK (0xf << CORE_VERSION_MAJOR_SHIFT) -#define CORE_VERSION_MINOR_MASK 0xff - -#define CORE_HC_MODE 0x78 -#define HC_MODE_EN 0x1 -#define CORE_POWER 0x0 -#define CORE_SW_RST BIT(7) -#define CORE_HC_SELECT_IN_EN (1 << 18) -#define CORE_HC_SELECT_IN_MASK (7 << 19) - -#define MAX_PHASES 16 -#define CORE_DLL_LOCK BIT(7) -#define CORE_DLL_EN BIT(16) -#define CORE_CDR_EN BIT(17) -#define CORE_CK_OUT_EN BIT(18) -#define CORE_CDR_EXT_EN BIT(19) -#define CORE_DLL_PDN BIT(29) -#define CORE_DLL_RST BIT(30) -#define CORE_DLL_CONFIG 0x100 -#define CORE_DLL_STATUS 0x108 - -#define CORE_VENDOR_SPEC 0x10c -#define CORE_CLK_PWRSAVE BIT(1) - -#define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c - -#define CORE_VENDOR_SPEC_CAPABILITIES0 0x11c -#define CORE_8_BIT_SUPPORT (1 << 18) -#define CORE_3_0V_SUPPORT (1 << 25) - -#define CDR_SELEXT_SHIFT 20 -#define CDR_SELEXT_MASK (0xf << CDR_SELEXT_SHIFT) -#define CMUX_SHIFT_PHASE_SHIFT 24 -#define CMUX_SHIFT_PHASE_MASK (7 << CMUX_SHIFT_PHASE_SHIFT) - -struct sdhci_msm_host { - struct platform_device *pdev; - void __iomem *core_mem; /* MSM SDCC mapped address */ - struct clk *clk; /* main SD/MMC bus clock */ - struct clk *pclk; /* SDHC peripheral bus clock */ - struct clk *bus_clk; /* SDHC bus voter clock */ - struct mmc_host *mmc; - struct sdhci_pltfm_data sdhci_msm_pdata; - struct regulator *vdd; - struct regulator *vdd_io; -}; - -/* Platform specific tuning */ -static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll) -{ - u32 wait_cnt = 50; - u8 ck_out_en; - struct mmc_host *mmc = host->mmc; - - /* Poll for CK_OUT_EN bit. max. poll time = 50us */ - ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & - CORE_CK_OUT_EN); - - while (ck_out_en != poll) { - if (--wait_cnt == 0) { - dev_err(mmc_dev(mmc), "%s: CK_OUT_EN bit is not %d\n", - mmc_hostname(mmc), poll); - return -ETIMEDOUT; - } - udelay(1); - - ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & - CORE_CK_OUT_EN); - } - - return 0; -} - -static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase) -{ - int rc; - static const u8 grey_coded_phase_table[] = { - 0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, - 0xc, 0xd, 0xf, 0xe, 0xa, 0xb, 0x9, 0x8 - }; - unsigned long flags; - u32 config; - struct mmc_host *mmc = host->mmc; - - spin_lock_irqsave(&host->lock, flags); - - config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); - config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN); - config |= (CORE_CDR_EXT_EN | CORE_DLL_EN); - writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); - - /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */ - rc = msm_dll_poll_ck_out_en(host, 0); - if (rc) - goto err_out; - - /* - * Write the selected DLL clock output phase (0 ... 15) - * to CDR_SELEXT bit field of DLL_CONFIG register. - */ - config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); - config &= ~CDR_SELEXT_MASK; - config |= grey_coded_phase_table[phase] << CDR_SELEXT_SHIFT; - writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); - - /* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); - - /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */ - rc = msm_dll_poll_ck_out_en(host, 1); - if (rc) - goto err_out; - - config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); - config |= CORE_CDR_EN; - config &= ~CORE_CDR_EXT_EN; - writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); - goto out; - -err_out: - dev_err(mmc_dev(mmc), "%s: Failed to set DLL phase: %d\n", - mmc_hostname(mmc), phase); -out: - spin_unlock_irqrestore(&host->lock, flags); - return rc; -} - -/* - * Find out the greatest range of consecuitive selected - * DLL clock output phases that can be used as sampling - * setting for SD3.0 UHS-I card read operation (in SDR104 - * timing mode) or for eMMC4.5 card read operation (in HS200 - * timing mode). - * Select the 3/4 of the range and configure the DLL with the - * selected DLL clock output phase. - */ - -static int msm_find_most_appropriate_phase(struct sdhci_host *host, - u8 *phase_table, u8 total_phases) -{ - int ret; - u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} }; - u8 phases_per_row[MAX_PHASES] = { 0 }; - int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0; - int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0; - bool phase_0_found = false, phase_15_found = false; - struct mmc_host *mmc = host->mmc; - - if (!total_phases || (total_phases > MAX_PHASES)) { - dev_err(mmc_dev(mmc), "%s: Invalid argument: total_phases=%d\n", - mmc_hostname(mmc), total_phases); - return -EINVAL; - } - - for (cnt = 0; cnt < total_phases; cnt++) { - ranges[row_index][col_index] = phase_table[cnt]; - phases_per_row[row_index] += 1; - col_index++; - - if ((cnt + 1) == total_phases) { - continue; - /* check if next phase in phase_table is consecutive or not */ - } else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) { - row_index++; - col_index = 0; - } - } - - if (row_index >= MAX_PHASES) - return -EINVAL; - - /* Check if phase-0 is present in first valid window? */ - if (!ranges[0][0]) { - phase_0_found = true; - phase_0_raw_index = 0; - /* Check if cycle exist between 2 valid windows */ - for (cnt = 1; cnt <= row_index; cnt++) { - if (phases_per_row[cnt]) { - for (i = 0; i < phases_per_row[cnt]; i++) { - if (ranges[cnt][i] == 15) { - phase_15_found = true; - phase_15_raw_index = cnt; - break; - } - } - } - } - } - - /* If 2 valid windows form cycle then merge them as single window */ - if (phase_0_found && phase_15_found) { - /* number of phases in raw where phase 0 is present */ - u8 phases_0 = phases_per_row[phase_0_raw_index]; - /* number of phases in raw where phase 15 is present */ - u8 phases_15 = phases_per_row[phase_15_raw_index]; - - if (phases_0 + phases_15 >= MAX_PHASES) - /* - * If there are more than 1 phase windows then total - * number of phases in both the windows should not be - * more than or equal to MAX_PHASES. - */ - return -EINVAL; - - /* Merge 2 cyclic windows */ - i = phases_15; - for (cnt = 0; cnt < phases_0; cnt++) { - ranges[phase_15_raw_index][i] = - ranges[phase_0_raw_index][cnt]; - if (++i >= MAX_PHASES) - break; - } - - phases_per_row[phase_0_raw_index] = 0; - phases_per_row[phase_15_raw_index] = phases_15 + phases_0; - } - - for (cnt = 0; cnt <= row_index; cnt++) { - if (phases_per_row[cnt] > curr_max) { - curr_max = phases_per_row[cnt]; - selected_row_index = cnt; - } - } - - i = (curr_max * 3) / 4; - if (i) - i--; - - ret = ranges[selected_row_index][i]; - - if (ret >= MAX_PHASES) { - ret = -EINVAL; - dev_err(mmc_dev(mmc), "%s: Invalid phase selected=%d\n", - mmc_hostname(mmc), ret); - } - - return ret; -} - -static inline void msm_cm_dll_set_freq(struct sdhci_host *host) -{ - u32 mclk_freq = 0, config; - - /* Program the MCLK value to MCLK_FREQ bit field */ - if (host->clock <= 112000000) - mclk_freq = 0; - else if (host->clock <= 125000000) - mclk_freq = 1; - else if (host->clock <= 137000000) - mclk_freq = 2; - else if (host->clock <= 150000000) - mclk_freq = 3; - else if (host->clock <= 162000000) - mclk_freq = 4; - else if (host->clock <= 175000000) - mclk_freq = 5; - else if (host->clock <= 187000000) - mclk_freq = 6; - else if (host->clock <= 200000000) - mclk_freq = 7; - - config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); - config &= ~CMUX_SHIFT_PHASE_MASK; - config |= mclk_freq << CMUX_SHIFT_PHASE_SHIFT; - writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); -} - -/* Initialize the DLL (Programmable Delay Line) */ -static int msm_init_cm_dll(struct sdhci_host *host) -{ - struct mmc_host *mmc = host->mmc; - int wait_cnt = 50; - unsigned long flags; - - spin_lock_irqsave(&host->lock, flags); - - /* - * Make sure that clock is always enabled when DLL - * tuning is in progress. Keeping PWRSAVE ON may - * turn off the clock. - */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) - & ~CORE_CLK_PWRSAVE), host->ioaddr + CORE_VENDOR_SPEC); - - /* Write 1 to DLL_RST bit of DLL_CONFIG register */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - | CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); - - /* Write 1 to DLL_PDN bit of DLL_CONFIG register */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - | CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); - msm_cm_dll_set_freq(host); - - /* Write 0 to DLL_RST bit of DLL_CONFIG register */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - & ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); - - /* Write 0 to DLL_PDN bit of DLL_CONFIG register */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - & ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); - - /* Set DLL_EN bit to 1. */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - | CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG); - - /* Set CK_OUT_EN bit to 1. */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); - - /* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */ - while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) & - CORE_DLL_LOCK)) { - /* max. wait for 50us sec for LOCK bit to be set */ - if (--wait_cnt == 0) { - dev_err(mmc_dev(mmc), "%s: DLL failed to LOCK\n", - mmc_hostname(mmc)); - spin_unlock_irqrestore(&host->lock, flags); - return -ETIMEDOUT; - } - udelay(1); - } - - spin_unlock_irqrestore(&host->lock, flags); - return 0; -} - -static int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) -{ - int tuning_seq_cnt = 3; - u8 phase, tuned_phases[16], tuned_phase_cnt = 0; - int rc; - struct mmc_host *mmc = host->mmc; - struct mmc_ios ios = host->mmc->ios; - - /* - * Tuning is required for SDR104, HS200 and HS400 cards and - * if clock frequency is greater than 100MHz in these modes. - */ - if (host->clock <= 100 * 1000 * 1000 || - !((ios.timing == MMC_TIMING_MMC_HS200) || - (ios.timing == MMC_TIMING_UHS_SDR104))) - return 0; - -retry: - /* First of all reset the tuning block */ - rc = msm_init_cm_dll(host); - if (rc) - return rc; - - phase = 0; - do { - /* Set the phase in delay line hw block */ - rc = msm_config_cm_dll_phase(host, phase); - if (rc) - return rc; - - rc = mmc_send_tuning(mmc, opcode, NULL); - if (!rc) { - /* Tuning is successful at this tuning point */ - tuned_phases[tuned_phase_cnt++] = phase; - dev_dbg(mmc_dev(mmc), "%s: Found good phase = %d\n", - mmc_hostname(mmc), phase); - } - } while (++phase < ARRAY_SIZE(tuned_phases)); - - if (tuned_phase_cnt) { - rc = msm_find_most_appropriate_phase(host, tuned_phases, - tuned_phase_cnt); - if (rc < 0) - return rc; - else - phase = rc; - - /* - * Finally set the selected phase in delay - * line hw block. - */ - rc = msm_config_cm_dll_phase(host, phase); - if (rc) - return rc; - dev_dbg(mmc_dev(mmc), "%s: Setting the tuning phase to %d\n", - mmc_hostname(mmc), phase); - } else { - if (--tuning_seq_cnt) - goto retry; - /* Tuning failed */ - dev_dbg(mmc_dev(mmc), "%s: No tuning point found\n", - mmc_hostname(mmc)); - rc = -EIO; - } - - return rc; -} - -static const struct of_device_id sdhci_msm_dt_match[] = { - { .compatible = "qcom,sdhci-msm" }, - {}, -}; - -MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); - -static struct sdhci_ops sdhci_msm_ops = { - .platform_execute_tuning = sdhci_msm_execute_tuning, - .reset = sdhci_reset, - .set_clock = sdhci_set_clock, - .set_bus_width = sdhci_set_bus_width, - .set_uhs_signaling = sdhci_set_uhs_signaling, -}; - -static int sdhci_msm_probe(struct platform_device *pdev) -{ - struct sdhci_host *host; - struct sdhci_pltfm_host *pltfm_host; - struct sdhci_msm_host *msm_host; - struct resource *core_memres; - int ret; - u16 host_version, core_minor; - u32 core_version, caps; - u8 core_major; - - msm_host = devm_kzalloc(&pdev->dev, sizeof(*msm_host), GFP_KERNEL); - if (!msm_host) - return -ENOMEM; - - msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; - host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0); - if (IS_ERR(host)) - return PTR_ERR(host); - - pltfm_host = sdhci_priv(host); - pltfm_host->priv = msm_host; - msm_host->mmc = host->mmc; - msm_host->pdev = pdev; - - ret = mmc_of_parse(host->mmc); - if (ret) - goto pltfm_free; - - sdhci_get_of_property(pdev); - - msm_host->vdd = devm_regulator_get(&pdev->dev, "vdd"); - if (IS_ERR(msm_host->vdd)) { - ret = PTR_ERR(msm_host->vdd); - dev_err(&pdev->dev, "VDD regulator setup failed (%d)\n", ret); - goto pltfm_free; - } - - msm_host->vdd_io = devm_regulator_get(&pdev->dev, "vdd-io"); - if (IS_ERR(msm_host->vdd_io)) { - ret = PTR_ERR(msm_host->vdd_io); - dev_err(&pdev->dev, "VDD-IO regulator setup failed (%d)\n", ret); - goto pltfm_free; - } - - ret = regulator_set_optimum_mode(msm_host->vdd, 500000); - if (ret < 0) { - dev_err(&pdev->dev, "VDD setting current load failed (%d)\n", ret); - goto pltfm_free; - } - - ret = regulator_set_optimum_mode(msm_host->vdd_io, 154000); - if (ret < 0) { - dev_err(&pdev->dev, "VDD-IO setting current load failed (%d)\n", ret); - goto pltfm_free; - } - - ret = regulator_set_voltage(msm_host->vdd, 2950000, 2950000); - if (ret) { - dev_err(&pdev->dev, "VDD setting voltage failed (%d)\n", ret); - goto pltfm_free; - } - - ret = regulator_set_voltage(msm_host->vdd_io, 1800000, 1800000); - if (ret) { - dev_err(&pdev->dev, "VDD-IO setting voltage failed (%d)\n", ret); - goto pltfm_free; - } - - ret = regulator_enable(msm_host->vdd); - if (ret) { - dev_err(&pdev->dev, "VDD enabling regulator failed (%d)\n", ret); - goto pltfm_free; - } - - ret = regulator_enable(msm_host->vdd_io); - if (ret) { - dev_err(&pdev->dev, "VDD-IO enabling regulator failed (%d)\n", ret); - goto reg_vdd_disable; - } - - /* Setup SDCC bus voter clock. */ - msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); - if (!IS_ERR(msm_host->bus_clk)) { - /* Vote for max. clk rate for max. performance */ - ret = clk_set_rate(msm_host->bus_clk, INT_MAX); - if (ret) - goto reg_vdd_io_disable; - ret = clk_prepare_enable(msm_host->bus_clk); - if (ret) - goto reg_vdd_io_disable; - } - - /* Setup main peripheral bus clock */ - msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk"); - if (IS_ERR(msm_host->pclk)) { - ret = PTR_ERR(msm_host->pclk); - dev_err(&pdev->dev, "Perpheral clk setup failed (%d)\n", ret); - goto bus_clk_disable; - } - - ret = clk_prepare_enable(msm_host->pclk); - if (ret) - goto bus_clk_disable; - - /* Setup SDC MMC clock */ - msm_host->clk = devm_clk_get(&pdev->dev, "core_clk"); - if (IS_ERR(msm_host->clk)) { - ret = PTR_ERR(msm_host->clk); - dev_err(&pdev->dev, "SDC MMC clk setup failed (%d)\n", ret); - goto pclk_disable; - } - - /* Vote for maximum clock rate for maximum performance */ - ret = clk_set_rate(msm_host->clk, INT_MAX); - if (ret) - dev_warn(&pdev->dev, "core clock boost failed\n"); - - ret = clk_prepare_enable(msm_host->clk); - if (ret) - goto pclk_disable; - - core_memres = platform_get_resource(pdev, IORESOURCE_MEM, 1); - msm_host->core_mem = devm_ioremap_resource(&pdev->dev, core_memres); - - if (IS_ERR(msm_host->core_mem)) { - dev_err(&pdev->dev, "Failed to remap registers\n"); - ret = PTR_ERR(msm_host->core_mem); - goto clk_disable; - } - - /* Disable mode selection via the vendor specific register */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) - & ~CORE_HC_SELECT_IN_EN - & ~CORE_HC_SELECT_IN_MASK), - host->ioaddr + CORE_VENDOR_SPEC); - - /* Reset the core and Enable SDHC mode */ - writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | - CORE_SW_RST, msm_host->core_mem + CORE_POWER); - - /* SW reset can take upto 10HCLK + 15MCLK cycles. (min 40us) */ - usleep_range(1000, 5000); - if (readl(msm_host->core_mem + CORE_POWER) & CORE_SW_RST) { - dev_err(&pdev->dev, "Stuck in reset\n"); - ret = -ETIMEDOUT; - goto clk_disable; - } - - /* Set HC_MODE_EN bit in HC_MODE register */ - writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); - - writel_relaxed((readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | - CORE_3_0V_SUPPORT | CORE_8_BIT_SUPPORT), - host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); - - host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; - host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; - host->quirks2 |= SDHCI_QUIRK2_BROKEN_HS200; - - host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); - dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", - host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> - SDHCI_VENDOR_VER_SHIFT)); - - core_version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION); - core_major = (core_version & CORE_VERSION_MAJOR_MASK) >> - CORE_VERSION_MAJOR_SHIFT; - core_minor = core_version & CORE_VERSION_MINOR_MASK; - dev_dbg(&pdev->dev, "MCI Version: 0x%08x, major: 0x%04x, minor: 0x%02x\n", - core_version, core_major, core_minor); - - /* - * Support for some capabilities is not advertised by newer - * controller versions and must be explicitly enabled. - */ - if (core_major >= 1 && core_minor != 0x11 && core_minor != 0x12) { - caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES); - caps |= SDHCI_CAN_VDD_300 | SDHCI_CAN_DO_8BIT; - writel_relaxed(caps, host->ioaddr + - CORE_VENDOR_SPEC_CAPABILITIES0); - } - - ret = sdhci_add_host(host); - if (ret) - goto clk_disable; - - return 0; - -clk_disable: - clk_disable_unprepare(msm_host->clk); -pclk_disable: - clk_disable_unprepare(msm_host->pclk); -bus_clk_disable: - if (!IS_ERR(msm_host->bus_clk)) - clk_disable_unprepare(msm_host->bus_clk); -reg_vdd_io_disable: - regulator_disable(msm_host->vdd_io); -reg_vdd_disable: - regulator_disable(msm_host->vdd); -pltfm_free: - sdhci_pltfm_free(pdev); - return ret; -} - -static int sdhci_msm_remove(struct platform_device *pdev) -{ - struct sdhci_host *host = platform_get_drvdata(pdev); - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct sdhci_msm_host *msm_host = pltfm_host->priv; - int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == - 0xffffffff); - - sdhci_remove_host(host, dead); - sdhci_pltfm_free(pdev); - clk_disable_unprepare(msm_host->clk); - clk_disable_unprepare(msm_host->pclk); - if (!IS_ERR(msm_host->bus_clk)) - clk_disable_unprepare(msm_host->bus_clk); - return 0; -} - -static struct platform_driver sdhci_msm_driver = { - .probe = sdhci_msm_probe, - .remove = sdhci_msm_remove, - .driver = { - .name = "sdhci_msm", - .of_match_table = sdhci_msm_dt_match, - }, -}; - -module_platform_driver(sdhci_msm_driver); - -MODULE_DESCRIPTION("Qualcomm Secure Digital Host Controller Interface driver"); -MODULE_LICENSE("GPL v2"); From 880b6f69f6c5b910cac0b6ad0a6716f00ffc8e19 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 18 Dec 2012 16:14:02 +0530 Subject: [PATCH 011/472] mmc: host: add SDHCI platform driver for msm chipsets This platform driver adds the support of Secure Digital Host Controller Interface compliant controller in MSM chipsets. Change-Id: Ide3a658ad51a3c3d4a05c47c0e8f013f647c9516 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fix trivial merge conflicts and Changed Qualcomm to Qualcomm Technologies, Inc.] Signed-off-by: Subhash Jadavani --- .../devicetree/bindings/mmc/sdhci-msm.txt | 112 +- drivers/mmc/host/Kconfig | 13 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sdhci-msm.c | 985 ++++++++++++++++++ 4 files changed, 1068 insertions(+), 43 deletions(-) create mode 100644 drivers/mmc/host/sdhci-msm.c diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 485483a63d8c..6111c88b04c4 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -1,55 +1,81 @@ -* Qualcomm SDHCI controller (sdhci-msm) +Qualcomm Technologies, Inc. Standard Secure Digital Host Controller (SDHC) -This file documents differences between the core properties in mmc.txt -and the properties used by the sdhci-msm driver. +Secure Digital Host Controller provides standard host interface to SD/MMC/SDIO cards. Required properties: -- compatible: Should contain "qcom,sdhci-msm-v4". -- reg: Base address and length of the register in the following order: - - Host controller register map (required) - - SD Core register map (required) -- interrupts: Should contain an interrupt-specifiers for the interrupts: - - Host controller interrupt (required) -- pinctrl-names: Should contain only one value - "default". -- pinctrl-0: Should specify pin control groups used for this controller. -- clocks: A list of phandle + clock-specifier pairs for the clocks listed in clock-names. -- clock-names: Should contain the following: - "iface" - Main peripheral bus clock (PCLK/HCLK - AHB Bus clock) (required) - "core" - SDC MMC clock (MCLK) (required) - "bus" - SDCC bus voter clock (optional) + - compatible : should be "qcom,sdhci-msm" + - reg : should contain SDHC, SD Core register map. + - reg-names : indicates various resources passed to driver (via reg proptery) by name. + Required "reg-names" are "hc_mem" and "core_mem" + - interrupts : should contain SDHC interrupts. + - interrupt-names : indicates interrupts passed to driver (via interrupts property) by name. + Required "interrupt-names" are "hc_irq" and "pwr_irq". + - -supply: phandle to the regulator device tree node + Required "supply-name" are "vdd" and "vdd-io". + +Required alias: +- The slot number is specified via an alias with the following format + 'sdhc{n}' where n is the slot number. + +Optional Properties: + - interrupt-names - "status_irq". This status_irq will be used for card + detection. + - qcom,bus-width - defines the bus I/O width that controller supports. + Units - number of bits. The valid bus-width values are + 1, 4 and 8. + - qcom,nonremovable - specifies whether the card in slot is + hot pluggable or hard wired. + - qcom,bus-speed-mode - specifies supported bus speed modes by host. + The supported bus speed modes are : + "HS200_1p8v" - indicates that host can support HS200 at 1.8v. + "HS200_1p2v" - indicates that host can support HS200 at 1.2v. + "DDR_1p8v" - indicates that host can support DDR mode at 1.8v. + "DDR_1p2v" - indicates that host can support DDR mode at 1.2v. + +In the following, can be vdd (flash core voltage) or vdd-io (I/O voltage). + - qcom,-always-on - specifies whether supply should be kept "on" always. + - qcom,-lpm_sup - specifies whether supply can be kept in low power mode (lpm). + - qcom,-voltage_level - specifies voltage levels for supply. Should be + specified in pairs (min, max), units uV. + - qcom,-current_level - specifies load levels for supply in lpm or + high power mode (hpm). Should be specified in + pairs (lpm, hpm), units uA. + + - gpios - specifies gpios assigned for sdhc slot. + - qcom,gpio-names - a list of strings that map in order to the list of gpios Example: - sdhc_1: sdhci@f9824900 { - compatible = "qcom,sdhci-msm-v4"; - reg = <0xf9824900 0x11c>, <0xf9824000 0x800>; - interrupts = <0 123 0>; - bus-width = <8>; - non-removable; - - vmmc-supply = <&pm8941_l20>; - vqmmc-supply = <&pm8941_s3>; - - pinctrl-names = "default"; - pinctrl-0 = <&sdc1_clk &sdc1_cmd &sdc1_data>; - - clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>; - clock-names = "core", "iface"; + aliases { + sdhc1 = &sdhc_1; }; - sdhc_2: sdhci@f98a4900 { - compatible = "qcom,sdhci-msm-v4"; - reg = <0xf98a4900 0x11c>, <0xf98a4000 0x800>; - interrupts = <0 125 0>; - bus-width = <4>; - cd-gpios = <&msmgpio 62 0x1>; + sdhc_1: qcom,sdhc@f9824900 { + compatible = "qcom,sdhci-msm"; + reg = <0xf9824900 0x11c>, <0xf9824000 0x800>; + reg-names = "hc_mem", "core_mem"; + interrupts = <0 123 0>, <0 138 0>; + interrupt-names = "hc_irq", "pwr_irq"; - vmmc-supply = <&pm8941_l21>; - vqmmc-supply = <&pm8941_l13>; + vdd-supply = <&pm8941_l21>; + vdd-io-supply = <&pm8941_l13>; + qcom,vdd-voltage-level = <2950000 2950000>; + qcom,vdd-current-level = <9000 800000>; - pinctrl-names = "default"; - pinctrl-0 = <&sdc2_clk &sdc2_cmd &sdc2_data>; + qcom,vdd-io-always-on; + qcom,vdd-io-lpm-sup; + qcom,vdd-io-voltage-level = <1800000 2950000>; + qcom,vdd-io-current-level = <6 22000>; - clocks = <&gcc GCC_SDCC2_APPS_CLK>, <&gcc GCC_SDCC2_AHB_CLK>; - clock-names = "core", "iface"; + qcom,bus-width = <4>; + qcom,nonremovable; + qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v"; + + gpios = <&msmgpio 40 0>, /* CLK */ + <&msmgpio 39 0>, /* CMD */ + <&msmgpio 38 0>, /* DATA0 */ + <&msmgpio 37 0>, /* DATA1 */ + <&msmgpio 36 0>, /* DATA2 */ + <&msmgpio 35 0>; /* DATA3 */ + qcom,gpio-names = "CLK", "CMD", "DAT0", "DAT1", "DAT2", "DAT3"; }; diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 77ff230fbbf3..2e1df582db74 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -404,6 +404,19 @@ config MMC_ATMELMCI If unsure, say N. +config MMC_SDHCI_MSM + tristate "Qualcomm Technologies, Inc. SDHCI Controller Support" + depends on ARCH_QCOM || ARCH_MSM || (ARM && COMPILE_TEST) + depends on MMC_SDHCI_PLTFM + help + This selects the Secure Digital Host Controller Interface (SDHCI) + support present in Qualcomm Technologies, Inc. SOCs. The controller + supports SD/MMC/SDIO devices. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_MSM tristate "Qualcomm SDCC Controller Support" depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50) diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index a3d5b96bb95a..56d31deb82f7 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o +obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c new file mode 100644 index 000000000000..b916373a25bb --- /dev/null +++ b/drivers/mmc/host/sdhci-msm.c @@ -0,0 +1,985 @@ +/* + * drivers/mmc/host/sdhci-msm.c - Qualcomm Technologies, Inc. MSM SDHCI Platform + * driver source file + * + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdhci-pltfm.h" + +#define CORE_HC_MODE 0x78 +#define HC_MODE_EN 0x1 + +#define CORE_POWER 0x0 +#define CORE_SW_RST (1 << 7) + +#define CORE_PWRCTL_STATUS 0xDC +#define CORE_PWRCTL_MASK 0xE0 +#define CORE_PWRCTL_CLEAR 0xE4 +#define CORE_PWRCTL_CTL 0xE8 + +#define CORE_PWRCTL_BUS_OFF 0x01 +#define CORE_PWRCTL_BUS_ON (1 << 1) +#define CORE_PWRCTL_IO_LOW (1 << 2) +#define CORE_PWRCTL_IO_HIGH (1 << 3) + +#define CORE_PWRCTL_BUS_SUCCESS 0x01 +#define CORE_PWRCTL_BUS_FAIL (1 << 1) +#define CORE_PWRCTL_IO_SUCCESS (1 << 2) +#define CORE_PWRCTL_IO_FAIL (1 << 3) + +#define INT_MASK 0xF + +/* This structure keeps information per regulator */ +struct sdhci_msm_reg_data { + /* voltage regulator handle */ + struct regulator *reg; + /* regulator name */ + const char *name; + /* voltage level to be set */ + u32 low_vol_level; + u32 high_vol_level; + /* Load values for low power and high power mode */ + u32 lpm_uA; + u32 hpm_uA; + + /* is this regulator enabled? */ + bool is_enabled; + /* is this regulator needs to be always on? */ + bool is_always_on; + /* is low power mode setting required for this regulator? */ + bool lpm_sup; +}; + +/* + * This structure keeps information for all the + * regulators required for a SDCC slot. + */ +struct sdhci_msm_slot_reg_data { + /* keeps VDD/VCC regulator info */ + struct sdhci_msm_reg_data *vdd_data; + /* keeps VDD IO regulator info */ + struct sdhci_msm_reg_data *vdd_io_data; +}; + +struct sdhci_msm_gpio { + u32 no; + const char *name; + bool is_enabled; +}; + +struct sdhci_msm_gpio_data { + struct sdhci_msm_gpio *gpio; + u8 size; +}; + +struct sdhci_msm_pin_data { + /* + * = 1 if controller pins are using gpios + * = 0 if controller has dedicated MSM pads + */ + bool cfg_sts; + struct sdhci_msm_gpio_data *gpio_data; +}; + +struct sdhci_msm_pltfm_data { + /* Supported UHS-I Modes */ + u32 caps; + + /* More capabilities */ + u32 caps2; + + unsigned long mmc_bus_width; + u32 max_clk; + struct sdhci_msm_slot_reg_data *vreg_data; + bool nonremovable; + struct sdhci_msm_pin_data *pin_data; +}; + +struct sdhci_msm_host { + void __iomem *core_mem; /* MSM SDCC mapped address */ + struct clk *clk; /* main SD/MMC bus clock */ + struct clk *pclk; /* SDHC peripheral bus clock */ + struct clk *bus_clk; /* SDHC bus voter clock */ + struct sdhci_msm_pltfm_data *pdata; + struct mmc_host *mmc; + struct sdhci_pltfm_data sdhci_msm_pdata; + wait_queue_head_t pwr_irq_wait; +}; + +enum vdd_io_level { + /* set vdd_io_data->low_vol_level */ + VDD_IO_LOW, + /* set vdd_io_data->high_vol_level */ + VDD_IO_HIGH, + /* + * set whatever there in voltage_level (third argument) of + * sdhci_msm_set_vdd_io_vol() function. + */ + VDD_IO_SET_LEVEL, +}; + +static int sdhci_msm_setup_gpio(struct sdhci_msm_pltfm_data *pdata, bool enable) +{ + struct sdhci_msm_gpio_data *curr; + int i, ret = 0; + + curr = pdata->pin_data->gpio_data; + for (i = 0; i < curr->size; i++) { + if (!gpio_is_valid(curr->gpio[i].no)) { + ret = -EINVAL; + pr_err("%s: Invalid gpio = %d\n", __func__, + curr->gpio[i].no); + goto free_gpios; + } + if (enable) { + ret = gpio_request(curr->gpio[i].no, + curr->gpio[i].name); + if (ret) { + pr_err("%s: gpio_request(%d, %s) failed %d\n", + __func__, curr->gpio[i].no, + curr->gpio[i].name, ret); + goto free_gpios; + } + curr->gpio[i].is_enabled = true; + } else { + gpio_free(curr->gpio[i].no); + curr->gpio[i].is_enabled = false; + } + } + return ret; + +free_gpios: + for (i--; i >= 0; i--) { + gpio_free(curr->gpio[i].no); + curr->gpio[i].is_enabled = false; + } + return ret; +} + +static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable) +{ + int ret = 0; + + if (!pdata->pin_data || (pdata->pin_data->cfg_sts == enable)) + return 0; + + ret = sdhci_msm_setup_gpio(pdata, enable); + if (!ret) + pdata->pin_data->cfg_sts = enable; + + return ret; +} + +#define MAX_PROP_SIZE 32 +static int sdhci_msm_dt_parse_vreg_info(struct device *dev, + struct sdhci_msm_reg_data **vreg_data, const char *vreg_name) +{ + int len, ret = 0; + const __be32 *prop; + char prop_name[MAX_PROP_SIZE]; + struct sdhci_msm_reg_data *vreg; + struct device_node *np = dev->of_node; + + snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name); + if (!of_parse_phandle(np, prop_name, 0)) { + dev_err(dev, "No vreg data found for %s\n", vreg_name); + ret = -EINVAL; + return ret; + } + + vreg = devm_kzalloc(dev, sizeof(*vreg), GFP_KERNEL); + if (!vreg) { + dev_err(dev, "No memory for vreg: %s\n", vreg_name); + ret = -ENOMEM; + return ret; + } + + vreg->name = vreg_name; + + snprintf(prop_name, MAX_PROP_SIZE, + "qcom,%s-always-on", vreg_name); + if (of_get_property(np, prop_name, NULL)) + vreg->is_always_on = true; + + snprintf(prop_name, MAX_PROP_SIZE, + "qcom,%s-lpm-sup", vreg_name); + if (of_get_property(np, prop_name, NULL)) + vreg->lpm_sup = true; + + snprintf(prop_name, MAX_PROP_SIZE, + "qcom,%s-voltage-level", vreg_name); + prop = of_get_property(np, prop_name, &len); + if (!prop || (len != (2 * sizeof(__be32)))) { + dev_warn(dev, "%s %s property\n", + prop ? "invalid format" : "no", prop_name); + } else { + vreg->low_vol_level = be32_to_cpup(&prop[0]); + vreg->high_vol_level = be32_to_cpup(&prop[1]); + } + + snprintf(prop_name, MAX_PROP_SIZE, + "qcom,%s-current-level", vreg_name); + prop = of_get_property(np, prop_name, &len); + if (!prop || (len != (2 * sizeof(__be32)))) { + dev_warn(dev, "%s %s property\n", + prop ? "invalid format" : "no", prop_name); + } else { + vreg->lpm_uA = be32_to_cpup(&prop[0]); + vreg->hpm_uA = be32_to_cpup(&prop[1]); + } + + *vreg_data = vreg; + dev_dbg(dev, "%s: %s %s vol=[%d %d]uV, curr=[%d %d]uA\n", + vreg->name, vreg->is_always_on ? "always_on," : "", + vreg->lpm_sup ? "lpm_sup," : "", vreg->low_vol_level, + vreg->high_vol_level, vreg->lpm_uA, vreg->hpm_uA); + + return ret; +} + +#define GPIO_NAME_MAX_LEN 32 +static int sdhci_msm_dt_parse_gpio_info(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + int ret = 0, cnt, i; + struct sdhci_msm_pin_data *pin_data; + struct device_node *np = dev->of_node; + + pin_data = devm_kzalloc(dev, sizeof(*pin_data), GFP_KERNEL); + if (!pin_data) { + dev_err(dev, "No memory for pin_data\n"); + ret = -ENOMEM; + goto out; + } + + cnt = of_gpio_count(np); + if (cnt > 0) { + pin_data->gpio_data = devm_kzalloc(dev, + sizeof(struct sdhci_msm_gpio_data), GFP_KERNEL); + if (!pin_data->gpio_data) { + dev_err(dev, "No memory for gpio_data\n"); + ret = -ENOMEM; + goto out; + } + pin_data->gpio_data->size = cnt; + pin_data->gpio_data->gpio = devm_kzalloc(dev, cnt * + sizeof(struct sdhci_msm_gpio), GFP_KERNEL); + + if (!pin_data->gpio_data->gpio) { + dev_err(dev, "No memory for gpio\n"); + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < cnt; i++) { + const char *name = NULL; + char result[GPIO_NAME_MAX_LEN]; + pin_data->gpio_data->gpio[i].no = of_get_gpio(np, i); + of_property_read_string_index(np, + "qcom,gpio-names", i, &name); + + snprintf(result, GPIO_NAME_MAX_LEN, "%s-%s", + dev_name(dev), name ? name : "?"); + pin_data->gpio_data->gpio[i].name = result; + dev_dbg(dev, "%s: gpio[%s] = %d\n", __func__, + pin_data->gpio_data->gpio[i].name, + pin_data->gpio_data->gpio[i].no); + pdata->pin_data = pin_data; + } + } + +out: + if (ret) + dev_err(dev, "%s failed with err %d\n", __func__, ret); + return ret; +} + +/* Parse platform data */ +static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) +{ + struct sdhci_msm_pltfm_data *pdata = NULL; + struct device_node *np = dev->of_node; + u32 bus_width = 0; + int len, i; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "failed to allocate memory for platform data\n"); + goto out; + } + + of_property_read_u32(np, "qcom,bus-width", &bus_width); + if (bus_width == 8) + pdata->mmc_bus_width = MMC_CAP_8_BIT_DATA; + else if (bus_width == 4) + pdata->mmc_bus_width = MMC_CAP_4_BIT_DATA; + else { + dev_notice(dev, "invalid bus-width, default to 1-bit mode\n"); + pdata->mmc_bus_width = 0; + } + + pdata->vreg_data = devm_kzalloc(dev, sizeof(struct + sdhci_msm_slot_reg_data), + GFP_KERNEL); + if (!pdata->vreg_data) { + dev_err(dev, "failed to allocate memory for vreg data\n"); + goto out; + } + + if (sdhci_msm_dt_parse_vreg_info(dev, &pdata->vreg_data->vdd_data, + "vdd")) { + dev_err(dev, "failed parsing vdd data\n"); + goto out; + } + if (sdhci_msm_dt_parse_vreg_info(dev, + &pdata->vreg_data->vdd_io_data, + "vdd-io")) { + dev_err(dev, "failed parsing vdd-io data\n"); + goto out; + } + + if (sdhci_msm_dt_parse_gpio_info(dev, pdata)) { + dev_err(dev, "failed parsing gpio data\n"); + goto out; + } + + of_property_read_u32(np, "qcom,max-clk-rate", &pdata->max_clk); + + len = of_property_count_strings(np, "qcom,bus-speed-mode"); + + for (i = 0; i < len; i++) { + const char *name = NULL; + + of_property_read_string_index(np, + "qcom,bus-speed-mode", i, &name); + if (!name) + continue; + + if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v"))) + pdata->caps2 |= MMC_CAP2_HS200_1_8V_SDR; + else if (!strncmp(name, "HS200_1p2v", sizeof("HS200_1p2v"))) + pdata->caps2 |= MMC_CAP2_HS200_1_2V_SDR; + else if (!strncmp(name, "DDR_1p8v", sizeof("DDR_1p8v"))) + pdata->caps |= MMC_CAP_1_8V_DDR + | MMC_CAP_UHS_DDR50; + else if (!strncmp(name, "DDR_1p2v", sizeof("DDR_1p2v"))) + pdata->caps |= MMC_CAP_1_2V_DDR + | MMC_CAP_UHS_DDR50; + } + + if (of_get_property(np, "qcom,nonremovable", NULL)) + pdata->nonremovable = true; + + return pdata; +out: + return NULL; +} + +/* Regulator utility functions */ +static int sdhci_msm_vreg_init_reg(struct device *dev, + struct sdhci_msm_reg_data *vreg) +{ + int ret = 0; + + /* check if regulator is already initialized? */ + if (vreg->reg) + goto out; + + /* Get the regulator handle */ + vreg->reg = devm_regulator_get(dev, vreg->name); + if (IS_ERR(vreg->reg)) { + ret = PTR_ERR(vreg->reg); + pr_err("%s: devm_regulator_get(%s) failed. ret=%d\n", + __func__, vreg->name, ret); + goto out; + } + + /* sanity check */ + if (!vreg->high_vol_level || !vreg->hpm_uA) { + pr_err("%s: %s invalid constraints specified\n", + __func__, vreg->name); + ret = -EINVAL; + } + +out: + return ret; +} + +static void sdhci_msm_vreg_deinit_reg(struct sdhci_msm_reg_data *vreg) +{ + if (vreg->reg) + devm_regulator_put(vreg->reg); +} + +static int sdhci_msm_vreg_set_optimum_mode(struct sdhci_msm_reg_data + *vreg, int uA_load) +{ + int ret = 0; + + /* + * regulators that do not support regulator_set_voltage also + * do not support regulator_set_optimum_mode + */ + ret = regulator_set_optimum_mode(vreg->reg, uA_load); + if (ret < 0) + pr_err("%s: regulator_set_optimum_mode(reg=%s,uA_load=%d) failed. ret=%d\n", + __func__, vreg->name, uA_load, ret); + else + /* + * regulator_set_optimum_mode() can return non zero + * value even for success case. + */ + ret = 0; + return ret; +} + +static int sdhci_msm_vreg_set_voltage(struct sdhci_msm_reg_data *vreg, + int min_uV, int max_uV) +{ + int ret = 0; + + ret = regulator_set_voltage(vreg->reg, min_uV, max_uV); + if (ret) { + pr_err("%s: regulator_set_voltage(%s)failed. min_uV=%d,max_uV=%d,ret=%d\n", + __func__, vreg->name, min_uV, max_uV, ret); + } + + return ret; +} + +static int sdhci_msm_vreg_enable(struct sdhci_msm_reg_data *vreg) +{ + int ret = 0; + + /* Put regulator in HPM (high power mode) */ + ret = sdhci_msm_vreg_set_optimum_mode(vreg, vreg->hpm_uA); + if (ret < 0) + return ret; + + if (!vreg->is_enabled) { + /* Set voltage level */ + ret = sdhci_msm_vreg_set_voltage(vreg, vreg->high_vol_level, + vreg->high_vol_level); + if (ret) + return ret; + } + ret = regulator_enable(vreg->reg); + if (ret) { + pr_err("%s: regulator_enable(%s) failed. ret=%d\n", + __func__, vreg->name, ret); + return ret; + } + vreg->is_enabled = true; + return ret; +} + +static int sdhci_msm_vreg_disable(struct sdhci_msm_reg_data *vreg) +{ + int ret = 0; + + /* Never disable regulator marked as always_on */ + if (vreg->is_enabled && !vreg->is_always_on) { + ret = regulator_disable(vreg->reg); + if (ret) { + pr_err("%s: regulator_disable(%s) failed. ret=%d\n", + __func__, vreg->name, ret); + goto out; + } + vreg->is_enabled = false; + + ret = sdhci_msm_vreg_set_optimum_mode(vreg, 0); + if (ret < 0) + goto out; + + /* Set min. voltage level to 0 */ + ret = sdhci_msm_vreg_set_voltage(vreg, 0, vreg->high_vol_level); + if (ret) + goto out; + } else if (vreg->is_enabled && vreg->is_always_on) { + if (vreg->lpm_sup) { + /* Put always_on regulator in LPM (low power mode) */ + ret = sdhci_msm_vreg_set_optimum_mode(vreg, + vreg->lpm_uA); + if (ret < 0) + goto out; + } + } +out: + return ret; +} + +static int sdhci_msm_setup_vreg(struct sdhci_msm_pltfm_data *pdata, + bool enable, bool is_init) +{ + int ret = 0, i; + struct sdhci_msm_slot_reg_data *curr_slot; + struct sdhci_msm_reg_data *vreg_table[2]; + + curr_slot = pdata->vreg_data; + if (!curr_slot) { + pr_debug("%s: vreg info unavailable,assuming the slot is powered by always on domain\n", + __func__); + goto out; + } + + vreg_table[0] = curr_slot->vdd_data; + vreg_table[1] = curr_slot->vdd_io_data; + + for (i = 0; i < ARRAY_SIZE(vreg_table); i++) { + if (vreg_table[i]) { + if (enable) + ret = sdhci_msm_vreg_enable(vreg_table[i]); + else + ret = sdhci_msm_vreg_disable(vreg_table[i]); + if (ret) + goto out; + } + } +out: + return ret; +} + +/* + * Reset vreg by ensuring it is off during probe. A call + * to enable vreg is needed to balance disable vreg + */ +static int sdhci_msm_vreg_reset(struct sdhci_msm_pltfm_data *pdata) +{ + int ret; + + ret = sdhci_msm_setup_vreg(pdata, 1, true); + if (ret) + return ret; + ret = sdhci_msm_setup_vreg(pdata, 0, true); + return ret; +} + +/* This init function should be called only once for each SDHC slot */ +static int sdhci_msm_vreg_init(struct device *dev, + struct sdhci_msm_pltfm_data *pdata, + bool is_init) +{ + int ret = 0; + struct sdhci_msm_slot_reg_data *curr_slot; + struct sdhci_msm_reg_data *curr_vdd_reg, *curr_vdd_io_reg; + + curr_slot = pdata->vreg_data; + if (!curr_slot) + goto out; + + curr_vdd_reg = curr_slot->vdd_data; + curr_vdd_io_reg = curr_slot->vdd_io_data; + + if (!is_init) + /* Deregister all regulators from regulator framework */ + goto vdd_io_reg_deinit; + + /* + * Get the regulator handle from voltage regulator framework + * and then try to set the voltage level for the regulator + */ + if (curr_vdd_reg) { + ret = sdhci_msm_vreg_init_reg(dev, curr_vdd_reg); + if (ret) + goto out; + } + if (curr_vdd_io_reg) { + ret = sdhci_msm_vreg_init_reg(dev, curr_vdd_io_reg); + if (ret) + goto vdd_reg_deinit; + } + ret = sdhci_msm_vreg_reset(pdata); + if (ret) + dev_err(dev, "vreg reset failed (%d)\n", ret); + goto out; + +vdd_io_reg_deinit: + if (curr_vdd_io_reg) + sdhci_msm_vreg_deinit_reg(curr_vdd_io_reg); +vdd_reg_deinit: + if (curr_vdd_reg) + sdhci_msm_vreg_deinit_reg(curr_vdd_reg); +out: + return ret; +} + + +static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata, + enum vdd_io_level level, + unsigned int voltage_level) +{ + int ret = 0; + int set_level; + struct sdhci_msm_reg_data *vdd_io_reg; + + if (!pdata->vreg_data) + return ret; + + vdd_io_reg = pdata->vreg_data->vdd_io_data; + if (vdd_io_reg && vdd_io_reg->is_enabled) { + switch (level) { + case VDD_IO_LOW: + set_level = vdd_io_reg->low_vol_level; + break; + case VDD_IO_HIGH: + set_level = vdd_io_reg->high_vol_level; + break; + case VDD_IO_SET_LEVEL: + set_level = voltage_level; + break; + default: + pr_err("%s: invalid argument level = %d", + __func__, level); + ret = -EINVAL; + return ret; + } + ret = sdhci_msm_vreg_set_voltage(vdd_io_reg, set_level, + set_level); + } + return ret; +} + +static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) +{ + struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)data; + u8 irq_status = 0; + u8 irq_ack = 0; + int ret = 0; + + irq_status = readb_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS); + pr_debug("%s: Received IRQ(%d), status=0x%x\n", + mmc_hostname(msm_host->mmc), irq, irq_status); + + /* Clear the interrupt */ + writeb_relaxed(irq_status, (msm_host->core_mem + CORE_PWRCTL_CLEAR)); + /* + * SDHC has core_mem and hc_mem device memory and these memory + * addresses do not fall within 1KB region. Hence, any update to + * core_mem address space would require an mb() to ensure this gets + * completed before its next update to registers within hc_mem. + */ + mb(); + + /* Handle BUS ON/OFF*/ + if (irq_status & CORE_PWRCTL_BUS_ON) { + ret = sdhci_msm_setup_vreg(msm_host->pdata, true, false); + if (!ret) + ret = sdhci_msm_setup_pins(msm_host->pdata, true); + if (ret) + irq_ack |= CORE_PWRCTL_BUS_FAIL; + else + irq_ack |= CORE_PWRCTL_BUS_SUCCESS; + } + if (irq_status & CORE_PWRCTL_BUS_OFF) { + ret = sdhci_msm_setup_vreg(msm_host->pdata, false, false); + if (!ret) + ret = sdhci_msm_setup_pins(msm_host->pdata, false); + if (ret) + irq_ack |= CORE_PWRCTL_BUS_FAIL; + else + irq_ack |= CORE_PWRCTL_BUS_SUCCESS; + } + /* Handle IO LOW/HIGH */ + if (irq_status & CORE_PWRCTL_IO_LOW) { + /* Switch voltage Low */ + ret = sdhci_msm_set_vdd_io_vol(msm_host->pdata, VDD_IO_LOW, 0); + if (ret) + irq_ack |= CORE_PWRCTL_IO_FAIL; + else + irq_ack |= CORE_PWRCTL_IO_SUCCESS; + } + if (irq_status & CORE_PWRCTL_IO_HIGH) { + /* Switch voltage High */ + ret = sdhci_msm_set_vdd_io_vol(msm_host->pdata, VDD_IO_HIGH, 0); + if (ret) + irq_ack |= CORE_PWRCTL_IO_FAIL; + else + irq_ack |= CORE_PWRCTL_IO_SUCCESS; + } + + /* ACK status to the core */ + writeb_relaxed(irq_ack, (msm_host->core_mem + CORE_PWRCTL_CTL)); + /* + * SDHC has core_mem and hc_mem device memory and these memory + * addresses do not fall within 1KB region. Hence, any update to + * core_mem address space would require an mb() to ensure this gets + * completed before its next update to registers within hc_mem. + */ + mb(); + + pr_debug("%s: Handled IRQ(%d), ret=%d, ack=0x%x\n", + mmc_hostname(msm_host->mmc), irq, ret, irq_ack); + wake_up_interruptible(&msm_host->pwr_irq_wait); + return IRQ_HANDLED; +} + +static void sdhci_msm_check_power_status(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int ret = 0; + + pr_debug("%s: %s: power status before waiting 0x%x\n", + mmc_hostname(host->mmc), __func__, + readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL)); + + ret = wait_event_interruptible(msm_host->pwr_irq_wait, + (readb_relaxed(msm_host->core_mem + + CORE_PWRCTL_CTL)) != 0x0); + if (ret) + pr_warning("%s: %s: returned due to error %d\n", + mmc_hostname(host->mmc), __func__, ret); + pr_debug("%s: %s: ret %d power status after handling power IRQ 0x%x\n", + mmc_hostname(host->mmc), __func__, ret, + readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL)); +} + +static struct sdhci_ops sdhci_msm_ops = { + .check_power_status = sdhci_msm_check_power_status, +}; + +static int sdhci_msm_probe(struct platform_device *pdev) +{ + struct sdhci_host *host; + struct sdhci_pltfm_host *pltfm_host; + struct sdhci_msm_host *msm_host; + struct resource *core_memres = NULL; + int ret = 0, pwr_irq = 0, dead = 0; + + pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); + msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), + GFP_KERNEL); + if (!msm_host) { + ret = -ENOMEM; + goto out; + } + init_waitqueue_head(&msm_host->pwr_irq_wait); + + msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; + host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata); + if (IS_ERR(host)) { + ret = PTR_ERR(host); + goto out; + } + + pltfm_host = sdhci_priv(host); + pltfm_host->priv = msm_host; + msm_host->mmc = host->mmc; + + /* Extract platform data */ + if (pdev->dev.of_node) { + msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev); + if (!msm_host->pdata) { + dev_err(&pdev->dev, "DT parsing error\n"); + goto pltfm_free; + } + } else { + dev_err(&pdev->dev, "No device tree node\n"); + goto pltfm_free; + } + + /* Setup Clocks */ + + /* Setup SDCC bus voter clock. */ + msm_host->bus_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { + /* Vote for max. clk rate for max. performance */ + ret = clk_set_rate(msm_host->bus_clk, INT_MAX); + if (ret) + goto pltfm_free; + ret = clk_prepare_enable(msm_host->bus_clk); + if (ret) + goto pltfm_free; + } + + /* Setup main peripheral bus clock */ + msm_host->pclk = devm_clk_get(&pdev->dev, "iface_clk"); + if (!IS_ERR(msm_host->pclk)) { + ret = clk_prepare_enable(msm_host->pclk); + if (ret) + goto bus_clk_disable; + } + + /* Setup SDC MMC clock */ + msm_host->clk = devm_clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(msm_host->clk)) { + ret = PTR_ERR(msm_host->clk); + goto pclk_disable; + } + + ret = clk_prepare_enable(msm_host->clk); + if (ret) + goto pclk_disable; + + /* Setup regulators */ + ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true); + if (ret) { + dev_err(&pdev->dev, "Regulator setup failed (%d)\n", ret); + goto clk_disable; + } + + /* Reset the core and Enable SDHC mode */ + core_memres = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "core_mem"); + msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start, + resource_size(core_memres)); + + if (!msm_host->core_mem) { + dev_err(&pdev->dev, "Failed to remap registers\n"); + ret = -ENOMEM; + goto vreg_deinit; + } + + /* Set SW_RST bit in POWER register (Offset 0x0) */ + writel_relaxed(CORE_SW_RST, msm_host->core_mem + CORE_POWER); + /* Set HC_MODE_EN bit in HC_MODE register */ + writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + + /* + * Following are the deviations from SDHC spec v3.0 - + * 1. Card detection is handled using separate GPIO. + * 2. Bus power control is handled by interacting with PMIC. + */ + host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; + host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; + + pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); + if (pwr_irq < 0) { + dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n", + pwr_irq); + goto vreg_deinit; + } + ret = devm_request_threaded_irq(&pdev->dev, pwr_irq, NULL, + sdhci_msm_pwr_irq, IRQF_ONESHOT, + dev_name(&pdev->dev), msm_host); + if (ret) { + dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n", + pwr_irq, ret); + goto vreg_deinit; + } + + /* Enable pwr irq interrupts */ + writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); + + /* Set host capabilities */ + msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; + msm_host->mmc->caps |= msm_host->pdata->caps; + + msm_host->mmc->caps2 |= msm_host->pdata->caps2; + msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR; + msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; + msm_host->mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC | + MMC_CAP2_DETECT_ON_ERR); + msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL; + msm_host->mmc->caps2 |= MMC_CAP2_INIT_BKOPS; + msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; + + if (msm_host->pdata->nonremovable) + msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; + + ret = sdhci_add_host(host); + if (ret) { + dev_err(&pdev->dev, "Add host failed (%d)\n", ret); + goto vreg_deinit; + } + + /* Set core clk rate, optionally override from dts */ + if (msm_host->pdata->max_clk) + host->max_clk = msm_host->pdata->max_clk; + ret = clk_set_rate(msm_host->clk, host->max_clk); + if (ret) { + dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret); + goto remove_host; + } + + /* Successful initialization */ + goto out; + +remove_host: + dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + sdhci_remove_host(host, dead); +vreg_deinit: + sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); +clk_disable: + if (!IS_ERR(msm_host->clk)) + clk_disable_unprepare(msm_host->clk); +pclk_disable: + if (!IS_ERR(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); +bus_clk_disable: + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); +pltfm_free: + sdhci_pltfm_free(pdev); +out: + pr_debug("%s: Exit %s\n", dev_name(&pdev->dev), __func__); + return ret; +} + +static int sdhci_msm_remove(struct platform_device *pdev) +{ + struct sdhci_host *host = platform_get_drvdata(pdev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct sdhci_msm_pltfm_data *pdata = msm_host->pdata; + int dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == + 0xffffffff); + + pr_debug("%s: %s\n", dev_name(&pdev->dev), __func__); + sdhci_remove_host(host, dead); + sdhci_pltfm_free(pdev); + sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); + if (!IS_ERR(msm_host->clk)) + clk_disable_unprepare(msm_host->clk); + if (!IS_ERR(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); + if (pdata->pin_data) + sdhci_msm_setup_gpio(pdata, false); + return 0; +} + +static const struct of_device_id sdhci_msm_dt_match[] = { + {.compatible = "qcom,sdhci-msm"}, +}; +MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); + +static struct platform_driver sdhci_msm_driver = { + .probe = sdhci_msm_probe, + .remove = sdhci_msm_remove, + .driver = { + .name = "sdhci_msm", + .owner = THIS_MODULE, + .of_match_table = sdhci_msm_dt_match, + }, +}; + +module_platform_driver(sdhci_msm_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Secure Digital Host Controller Interface driver"); +MODULE_LICENSE("GPL v2"); From 1370edc0b20767c949ca77433f21414d0c35b29a Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 21 Dec 2012 12:21:42 +0530 Subject: [PATCH 012/472] mmc: host: add pad and tlmm configuration This patch adds the pad and tlmm configuration to msm-sdhci driver. Change-Id: Ic2b9beffdb555598bdc15b4b03c8adb78fbd0c2c Signed-off-by: Asutosh Das Signed-off-by: Sahitya Tummala --- .../devicetree/bindings/mmc/sdhci-msm.txt | 26 ++ drivers/mmc/host/sdhci-msm.c | 294 +++++++++++++++++- 2 files changed, 312 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 6111c88b04c4..f8bc7a294a70 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -44,10 +44,18 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag - gpios - specifies gpios assigned for sdhc slot. - qcom,gpio-names - a list of strings that map in order to the list of gpios + A slot has either gpios or dedicated tlmm pins as represented below. + - qcom,pad-pull-on - Active pull configuration for sdc tlmm pins + - qcom,pad-pull-off - Suspend pull configuration for sdc tlmm pins. + - qcom,pad-drv-on - Active drive strength configuration for sdc tlmm pins. + - qcom,pad-drv-off - Suspend drive strength configuration for sdc tlmm pins. + Tlmm pins are specified as + Example: aliases { sdhc1 = &sdhc_1; + sdhc2 = &sdhc_2; }; sdhc_1: qcom,sdhc@f9824900 { @@ -79,3 +87,21 @@ Example: <&msmgpio 35 0>; /* DATA3 */ qcom,gpio-names = "CLK", "CMD", "DAT0", "DAT1", "DAT2", "DAT3"; }; + + sdhc_2: qcom,sdhc@f98a4900 { + compatible = "qcom,sdhci-msm"; + reg = <0xf9824900 0x11c>, <0xf9824000 0x800>; + reg-names = "hc_mem", "core_mem"; + interrupts = <0 123 0>, <0 138 0>; + interrupt-names = "hc_irq", "pwr_irq"; + + vdd-supply = <&pm8941_l21>; + vdd-io-supply = <&pm8941_l13>; + + qcom,bus-width = <4>; + + qcom,pad-pull-on = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */ + qcom,pad-pull-off = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */ + qcom,pad-drv-on = <0x7 0x4 0x4>; /* 16mA, 10mA, 10mA */ + qcom,pad-drv-off = <0x0 0x0 0x0>; /* 2mA, 2mA, 2mA */ + }; diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b916373a25bb..6b45dd682760 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -96,13 +96,43 @@ struct sdhci_msm_gpio_data { u8 size; }; +struct sdhci_msm_pad_pull { + enum msm_tlmm_pull_tgt no; + u32 val; +}; + +struct sdhci_msm_pad_pull_data { + struct sdhci_msm_pad_pull *on; + struct sdhci_msm_pad_pull *off; + u8 size; +}; + +struct sdhci_msm_pad_drv { + enum msm_tlmm_hdrive_tgt no; + u32 val; +}; + +struct sdhci_msm_pad_drv_data { + struct sdhci_msm_pad_drv *on; + struct sdhci_msm_pad_drv *off; + u8 size; +}; + +struct sdhci_msm_pad_data { + struct sdhci_msm_pad_pull_data *pull; + struct sdhci_msm_pad_drv_data *drv; +}; + + struct sdhci_msm_pin_data { /* * = 1 if controller pins are using gpios * = 0 if controller has dedicated MSM pads */ + u8 is_gpio; bool cfg_sts; struct sdhci_msm_gpio_data *gpio_data; + struct sdhci_msm_pad_data *pad_data; }; struct sdhci_msm_pltfm_data { @@ -180,20 +210,88 @@ free_gpios: return ret; } +static int sdhci_msm_setup_pad(struct sdhci_msm_pltfm_data *pdata, bool enable) +{ + struct sdhci_msm_pad_data *curr; + int i; + + curr = pdata->pin_data->pad_data; + for (i = 0; i < curr->drv->size; i++) { + if (enable) + msm_tlmm_set_hdrive(curr->drv->on[i].no, + curr->drv->on[i].val); + else + msm_tlmm_set_hdrive(curr->drv->off[i].no, + curr->drv->off[i].val); + } + + for (i = 0; i < curr->pull->size; i++) { + if (enable) + msm_tlmm_set_pull(curr->pull->on[i].no, + curr->pull->on[i].val); + else + msm_tlmm_set_pull(curr->pull->off[i].no, + curr->pull->off[i].val); + } + + return 0; +} + static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable) { int ret = 0; if (!pdata->pin_data || (pdata->pin_data->cfg_sts == enable)) return 0; + if (pdata->pin_data->is_gpio) + ret = sdhci_msm_setup_gpio(pdata, enable); + else + ret = sdhci_msm_setup_pad(pdata, enable); - ret = sdhci_msm_setup_gpio(pdata, enable); if (!ret) pdata->pin_data->cfg_sts = enable; return ret; } +static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name, + u32 **out, int *len, u32 size) +{ + int ret = 0; + struct device_node *np = dev->of_node; + size_t sz; + u32 *arr = NULL; + + if (!of_get_property(np, prop_name, len)) { + ret = -EINVAL; + goto out; + } + sz = *len = *len / sizeof(*arr); + if (sz <= 0 || (size > 0 && (sz != size))) { + dev_err(dev, "%s invalid size\n", prop_name); + ret = -EINVAL; + goto out; + } + + arr = devm_kzalloc(dev, sz * sizeof(*arr), GFP_KERNEL); + if (!arr) { + dev_err(dev, "%s failed allocating memory\n", prop_name); + ret = -ENOMEM; + goto out; + } + + ret = of_property_read_u32_array(np, prop_name, arr, sz); + if (ret < 0) { + dev_err(dev, "%s failed reading array %d\n", prop_name, ret); + goto out; + } + *out = arr; +out: + if (ret) + *len = 0; + return ret; +} + #define MAX_PROP_SIZE 32 static int sdhci_msm_dt_parse_vreg_info(struct device *dev, struct sdhci_msm_reg_data **vreg_data, const char *vreg_name) @@ -261,11 +359,164 @@ static int sdhci_msm_dt_parse_vreg_info(struct device *dev, return ret; } +/* GPIO/Pad data extraction */ +static int sdhci_msm_dt_get_pad_pull_info(struct device *dev, int id, + struct sdhci_msm_pad_pull_data **pad_pull_data) +{ + int ret = 0, base = 0, len, i; + u32 *tmp; + struct sdhci_msm_pad_pull_data *pull_data; + struct sdhci_msm_pad_pull *pull; + + switch (id) { + case 1: + base = TLMM_PULL_SDC1_CLK; + break; + case 2: + base = TLMM_PULL_SDC2_CLK; + break; + case 3: + base = TLMM_PULL_SDC3_CLK; + break; + case 4: + base = TLMM_PULL_SDC4_CLK; + break; + default: + dev_err(dev, "%s: Invalid slot id\n", __func__); + ret = -EINVAL; + goto out; + } + + pull_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_pad_pull_data), + GFP_KERNEL); + if (!pull_data) { + dev_err(dev, "No memory for msm_mmc_pad_pull_data\n"); + ret = -ENOMEM; + goto out; + } + pull_data->size = 3; /* array size for clk, cmd, data */ + + /* Allocate on, off configs for clk, cmd, data */ + pull = devm_kzalloc(dev, 2 * pull_data->size *\ + sizeof(struct sdhci_msm_pad_pull), GFP_KERNEL); + if (!pull) { + dev_err(dev, "No memory for msm_mmc_pad_pull\n"); + ret = -ENOMEM; + goto out; + } + pull_data->on = pull; + pull_data->off = pull + pull_data->size; + + ret = sdhci_msm_dt_get_array(dev, "qcom,pad-pull-on", + &tmp, &len, pull_data->size); + if (ret) + goto out; + + for (i = 0; i < len; i++) { + pull_data->on[i].no = base + i; + pull_data->on[i].val = tmp[i]; + dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, + i, pull_data->on[i].val); + } + + ret = sdhci_msm_dt_get_array(dev, "qcom,pad-pull-off", + &tmp, &len, pull_data->size); + if (ret) + goto out; + + for (i = 0; i < len; i++) { + pull_data->off[i].no = base + i; + pull_data->off[i].val = tmp[i]; + dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, + i, pull_data->off[i].val); + } + + *pad_pull_data = pull_data; +out: + return ret; +} + +static int sdhci_msm_dt_get_pad_drv_info(struct device *dev, int id, + struct sdhci_msm_pad_drv_data **pad_drv_data) +{ + int ret = 0, base = 0, len, i; + u32 *tmp; + struct sdhci_msm_pad_drv_data *drv_data; + struct sdhci_msm_pad_drv *drv; + + switch (id) { + case 1: + base = TLMM_HDRV_SDC1_CLK; + break; + case 2: + base = TLMM_HDRV_SDC2_CLK; + break; + case 3: + base = TLMM_HDRV_SDC3_CLK; + break; + case 4: + base = TLMM_HDRV_SDC4_CLK; + break; + default: + dev_err(dev, "%s: Invalid slot id\n", __func__); + ret = -EINVAL; + goto out; + } + + drv_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_pad_drv_data), + GFP_KERNEL); + if (!drv_data) { + dev_err(dev, "No memory for msm_mmc_pad_drv_data\n"); + ret = -ENOMEM; + goto out; + } + drv_data->size = 3; /* array size for clk, cmd, data */ + + /* Allocate on, off configs for clk, cmd, data */ + drv = devm_kzalloc(dev, 2 * drv_data->size *\ + sizeof(struct sdhci_msm_pad_drv), GFP_KERNEL); + if (!drv) { + dev_err(dev, "No memory msm_mmc_pad_drv\n"); + ret = -ENOMEM; + goto out; + } + drv_data->on = drv; + drv_data->off = drv + drv_data->size; + + ret = sdhci_msm_dt_get_array(dev, "qcom,pad-drv-on", + &tmp, &len, drv_data->size); + if (ret) + goto out; + + for (i = 0; i < len; i++) { + drv_data->on[i].no = base + i; + drv_data->on[i].val = tmp[i]; + dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, + i, drv_data->on[i].val); + } + + ret = sdhci_msm_dt_get_array(dev, "qcom,pad-drv-off", + &tmp, &len, drv_data->size); + if (ret) + goto out; + + for (i = 0; i < len; i++) { + drv_data->off[i].no = base + i; + drv_data->off[i].val = tmp[i]; + dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, + i, drv_data->off[i].val); + } + + *pad_drv_data = drv_data; +out: + return ret; +} + #define GPIO_NAME_MAX_LEN 32 static int sdhci_msm_dt_parse_gpio_info(struct device *dev, struct sdhci_msm_pltfm_data *pdata) { - int ret = 0, cnt, i; + int ret = 0, id = 0, cnt, i; struct sdhci_msm_pin_data *pin_data; struct device_node *np = dev->of_node; @@ -278,6 +529,7 @@ static int sdhci_msm_dt_parse_gpio_info(struct device *dev, cnt = of_gpio_count(np); if (cnt > 0) { + pin_data->is_gpio = true; pin_data->gpio_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_gpio_data), GFP_KERNEL); if (!pin_data->gpio_data) { @@ -294,7 +546,6 @@ static int sdhci_msm_dt_parse_gpio_info(struct device *dev, ret = -ENOMEM; goto out; } - for (i = 0; i < cnt; i++) { const char *name = NULL; char result[GPIO_NAME_MAX_LEN]; @@ -306,12 +557,39 @@ static int sdhci_msm_dt_parse_gpio_info(struct device *dev, dev_name(dev), name ? name : "?"); pin_data->gpio_data->gpio[i].name = result; dev_dbg(dev, "%s: gpio[%s] = %d\n", __func__, - pin_data->gpio_data->gpio[i].name, - pin_data->gpio_data->gpio[i].no); - pdata->pin_data = pin_data; + pin_data->gpio_data->gpio[i].name, + pin_data->gpio_data->gpio[i].no); + } + } else { + pin_data->pad_data = + devm_kzalloc(dev, + sizeof(struct sdhci_msm_pad_data), + GFP_KERNEL); + if (!pin_data->pad_data) { + dev_err(dev, + "No memory for pin_data->pad_data\n"); + ret = -ENOMEM; + goto out; } - } + ret = of_alias_get_id(np, "sdhc"); + if (ret < 0) { + dev_err(dev, "Failed to get slot index %d\n", ret); + goto out; + } + id = ret; + + ret = sdhci_msm_dt_get_pad_pull_info( + dev, id, &pin_data->pad_data->pull); + if (ret) + goto out; + ret = sdhci_msm_dt_get_pad_drv_info( + dev, id, &pin_data->pad_data->drv); + if (ret) + goto out; + + } + pdata->pin_data = pin_data; out: if (ret) dev_err(dev, "%s failed with err %d\n", __func__, ret); @@ -960,7 +1238,7 @@ static int sdhci_msm_remove(struct platform_device *pdev) if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); if (pdata->pin_data) - sdhci_msm_setup_gpio(pdata, false); + sdhci_msm_setup_pins(pdata, false); return 0; } From 226107c254a8ddd29097a78d99c4ab8432a5f40f Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 17 Sep 2012 16:00:15 -0700 Subject: [PATCH 013/472] mmc: sdhci: Add SW workarounds for HW bugs Initial version of Qualcomm SDHC has the following two h/w issues. This patch adds s/w workarounds for the same. H/W issue: Read Transfer Active/ Write Transfer Active may be not de-asserted after end of transaction. S/W workaround: Set Software Reset for DAT line in Software Reset Register (Bit 2). Added a quirk SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT to enable this workaround. H/W issue: Slow interrupt clearance at 400KHz may cause host controller driver interrupt handler to be called twice. S/W Workaround: Add 40us delay in interrupt handler when operating at initialization frequency(400KHz). Added a quirk SDHCI_QUIRK2_SLOW_INT_CLR to enable this workaround. Change-Id: I8b4062f101085adadd66560f77b98b04d75cb836 Signed-off-by: Venkat Gopalakrishnan Signed-off-by: Asutosh Das Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fix trivial merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 21 +++++++++++++++++++++ drivers/mmc/host/sdhci.c | 15 +++++++++++++-- drivers/mmc/host/sdhci.h | 12 ++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 6b45dd682760..e9d811043f0b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -30,6 +30,7 @@ #include "sdhci-pltfm.h" +#define SDHCI_VER_100 0x2B #define CORE_HC_MODE 0x78 #define HC_MODE_EN 0x1 @@ -1047,6 +1048,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) struct sdhci_msm_host *msm_host; struct resource *core_memres = NULL; int ret = 0, pwr_irq = 0, dead = 0; + u32 host_version; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -1145,6 +1147,25 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; + host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); + dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", + host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> + SDHCI_VENDOR_VER_SHIFT)); + if (((host_version & SDHCI_VENDOR_VER_MASK) >> + SDHCI_VENDOR_VER_SHIFT) == SDHCI_VER_100) { + /* + * Add 40us delay in interrupt handler when + * operating at initialization frequency(400KHz). + */ + host->quirks2 |= SDHCI_QUIRK2_SLOW_INT_CLR; + /* + * Set Software Reset for DAT line in Software + * Reset Register (Bit 2). + */ + host->quirks2 |= SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT; + } + + /* Setup PWRCTL irq */ pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); if (pwr_irq < 0) { dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n", diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 9323d7fbef1d..9a0a9069a15b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2272,6 +2272,9 @@ static void sdhci_tasklet_finish(unsigned long param) controllers do not like that. */ sdhci_do_reset(host, SDHCI_RESET_CMD); sdhci_do_reset(host, SDHCI_RESET_DATA); + } else { + if (host->quirks2 & SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT) + sdhci_reset(host, SDHCI_RESET_DATA); } host->mrq = NULL; @@ -2588,12 +2591,20 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) result = IRQ_WAKE_THREAD; } - if (intmask & SDHCI_INT_CMD_MASK) + if (intmask & SDHCI_INT_CMD_MASK) { + if ((host->quirks2 & SDHCI_QUIRK2_SLOW_INT_CLR) && + (host->clock <= 400000)) + udelay(40); sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK, &intmask); + } - if (intmask & SDHCI_INT_DATA_MASK) + if (intmask & SDHCI_INT_DATA_MASK) { + if ((host->quirks2 & SDHCI_QUIRK2_SLOW_INT_CLR) && + (host->clock <= 400000)) + udelay(40); sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK); + } if (intmask & SDHCI_INT_BUS_POWER) pr_err("%s: Card is consuming too much power!\n", diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 9d4aa31b683a..d348e1d917cb 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -418,6 +418,18 @@ struct sdhci_host { */ #define SDHCI_QUIRK2_NEED_DELAY_AFTER_INT_CLK_RST (1<<16) +/* + * Read Transfer Active/ Write Transfer Active may be not + * de-asserted after end of transaction. Issue reset for DAT line. + */ +#define SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT (1<<17) +/* + * Slow interrupt clearance at 400KHz may cause + * host controller driver interrupt handler to + * be called twice. + */ +#define SDHCI_QUIRK2_SLOW_INT_CLR (1<<18) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From ce8e870d5d09c7058f3f564e6d75fa5005836d83 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 25 Feb 2013 15:50:08 +0530 Subject: [PATCH 014/472] mmc: sdhci: Add a quirk to ignore CMD CRC err for tuning commands MSM SDHCI controller doesn't support tuning as specified by the Standard Host Controller 3.0 spec. As a result of which, CMD CRC errors are expected for tuning commands. Hence, add a new quirk SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING to ignore those errors for tuning commands. Change-Id: Id43d300bf8fabea921c80931fbf45cd3782ff3fa Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fix trivial merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 + drivers/mmc/host/sdhci.c | 20 ++++++++++++++++++++ drivers/mmc/host/sdhci.h | 3 +++ 3 files changed, 24 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e9d811043f0b..71589f3c7554 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1146,6 +1146,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) */ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; + host->quirks2 |= SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING; host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 9a0a9069a15b..0237da8bf1f9 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2347,6 +2347,16 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) SDHCI_INT_INDEX)) host->cmd->error = -EILSEQ; + if (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING) { + if ((host->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) || + (host->cmd->opcode == MMC_SEND_TUNING_BLOCK)) { + if (intmask & SDHCI_INT_CRC) { + sdhci_reset(host, SDHCI_RESET_CMD); + host->cmd->error = 0; + } + } + } + if (host->cmd->error) { tasklet_schedule(&host->finish_tasklet); return; @@ -2381,6 +2391,16 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) *mask &= ~SDHCI_INT_DATA_END; } + if (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING) { + if ((host->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) || + (host->cmd->opcode == MMC_SEND_TUNING_BLOCK)) { + if (intmask & SDHCI_INT_CRC) { + sdhci_finish_command(host); + return; + } + } + } + if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host); } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index d348e1d917cb..9287f1099cf4 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -430,6 +430,9 @@ struct sdhci_host { */ #define SDHCI_QUIRK2_SLOW_INT_CLR (1<<18) +/* Ignore CMD CRC errors for tuning commands */ +#define SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING (1<<6) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From 3638c6d3e032f898d75ca8b593c4606a7965657e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 11 Sep 2012 16:13:31 -0700 Subject: [PATCH 015/472] mmc: sdhci-msm: Implement platform_execute_tuning and toggle_cdr callbacks Implement platform_execute_tuning and toggle_cdr callbacks that are needed to support HS200 and SDR104 bus speed modes. Also, set IO_PAD_PWR_SWITCH control bit in vendor specific register if the IO voltage level is within low voltage range (1.7v - 1.9v). Change-Id: If41704758d097229ffc0204d581886e137e8b581 Signed-off-by: Asutosh Das [venkatg@codeaurora.org: Rename tuning ops fn to platform_execute_tuning] Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 467 ++++++++++++++++++++++++++++++++++- 1 file changed, 465 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 71589f3c7554..6c640769720c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -27,6 +27,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include "sdhci-pltfm.h" @@ -53,6 +59,40 @@ #define CORE_PWRCTL_IO_FAIL (1 << 3) #define INT_MASK 0xF +#define MAX_PHASES 16 + +#define CORE_DLL_LOCK (1 << 7) +#define CORE_DLL_EN (1 << 16) +#define CORE_CDR_EN (1 << 17) +#define CORE_CK_OUT_EN (1 << 18) +#define CORE_CDR_EXT_EN (1 << 19) +#define CORE_DLL_PDN (1 << 29) +#define CORE_DLL_RST (1 << 30) +#define CORE_DLL_CONFIG 0x100 +#define CORE_DLL_TEST_CTL 0x104 +#define CORE_DLL_STATUS 0x108 + +#define CORE_VENDOR_SPEC 0x10C +#define CORE_CLK_PWRSAVE (1 << 1) +#define CORE_IO_PAD_PWR_SWITCH (1 << 16) + +static const u32 tuning_block_64[] = { + 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, + 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, + 0xF0FFF0FF, 0x3CCCFC0F, 0xCFCC33CC, 0xEEFFEFFF, + 0xFDFFFDFF, 0xFFBFFFDF, 0xFFF7FFBB, 0xDE7B7FF7 +}; + +static const u32 tuning_block_128[] = { + 0xFF00FFFF, 0x0000FFFF, 0xCCCCFFFF, 0xCCCC33CC, + 0xCC3333CC, 0xFFFFCCCC, 0xFFFFEEFF, 0xFFEEEEFF, + 0xFFDDFFFF, 0xDDDDFFFF, 0xBBFFFFFF, 0xBBFFFFFF, + 0xFFFFFFBB, 0xFFFFFF77, 0x77FF7777, 0xFFEEDDBB, + 0x00FFFFFF, 0x00FFFFFF, 0xCCFFFF00, 0xCC33CCCC, + 0x3333CCCC, 0xFFCCCCCC, 0xFFEEFFFF, 0xEEEEFFFF, + 0xDDFFFFFF, 0xDDFFFFFF, 0xFFFFFFDD, 0xFFFFFFBB, + 0xFFFFBBBB, 0xFFFF77FF, 0xFF7777FF, 0xEEDDBB77 +}; /* This structure keeps information per regulator */ struct sdhci_msm_reg_data { @@ -173,6 +213,403 @@ enum vdd_io_level { VDD_IO_SET_LEVEL, }; +/* MSM platform specific tuning */ +static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, + u8 poll) +{ + int rc = 0; + u32 wait_cnt = 50; + u8 ck_out_en = 0; + struct mmc_host *mmc = host->mmc; + + /* poll for CK_OUT_EN bit. max. poll time = 50us */ + ck_out_en = !!(readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & + CORE_CK_OUT_EN); + + while (ck_out_en != poll) { + if (--wait_cnt == 0) { + pr_err("%s: %s: CK_OUT_EN bit is not %d\n", + mmc_hostname(mmc), __func__, poll); + rc = -ETIMEDOUT; + goto out; + } + udelay(1); + + ck_out_en = !!(readl_relaxed(host->ioaddr + + CORE_DLL_CONFIG) & CORE_CK_OUT_EN); + } +out: + return rc; +} + +static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase) +{ + int rc = 0; + u8 grey_coded_phase_table[] = {0x0, 0x1, 0x3, 0x2, 0x6, 0x7, 0x5, 0x4, + 0xC, 0xD, 0xF, 0xE, 0xA, 0xB, 0x9, + 0x8}; + unsigned long flags; + u32 config; + struct mmc_host *mmc = host->mmc; + + pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); + spin_lock_irqsave(&host->lock, flags); + + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config &= ~(CORE_CDR_EN | CORE_CK_OUT_EN); + config |= (CORE_CDR_EXT_EN | CORE_DLL_EN); + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '0' */ + rc = msm_dll_poll_ck_out_en(host, 0); + if (rc) + goto err_out; + + /* + * Write the selected DLL clock output phase (0 ... 15) + * to CDR_SELEXT bit field of DLL_CONFIG register. + */ + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~(0xF << 20)) + | (grey_coded_phase_table[phase] << 20)), + host->ioaddr + CORE_DLL_CONFIG); + + /* Set CK_OUT_EN bit of DLL_CONFIG register to 1. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); + + /* Wait until CK_OUT_EN bit of DLL_CONFIG register becomes '1' */ + rc = msm_dll_poll_ck_out_en(host, 1); + if (rc) + goto err_out; + + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config |= CORE_CDR_EN; + config &= ~CORE_CDR_EXT_EN; + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + goto out; + +err_out: + pr_err("%s: %s: Failed to set DLL phase: %d\n", + mmc_hostname(mmc), __func__, phase); +out: + spin_unlock_irqrestore(&host->lock, flags); + pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); + return rc; +} + +/* + * Find out the greatest range of consecuitive selected + * DLL clock output phases that can be used as sampling + * setting for SD3.0 UHS-I card read operation (in SDR104 + * timing mode) or for eMMC4.5 card read operation (in HS200 + * timing mode). + * Select the 3/4 of the range and configure the DLL with the + * selected DLL clock output phase. + */ + +static int msm_find_most_appropriate_phase(struct sdhci_host *host, + u8 *phase_table, u8 total_phases) +{ + int ret; + u8 ranges[MAX_PHASES][MAX_PHASES] = { {0}, {0} }; + u8 phases_per_row[MAX_PHASES] = {0}; + int row_index = 0, col_index = 0, selected_row_index = 0, curr_max = 0; + int i, cnt, phase_0_raw_index = 0, phase_15_raw_index = 0; + bool phase_0_found = false, phase_15_found = false; + struct mmc_host *mmc = host->mmc; + + pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); + if (!total_phases || (total_phases > MAX_PHASES)) { + pr_err("%s: %s: invalid argument: total_phases=%d\n", + mmc_hostname(mmc), __func__, total_phases); + return -EINVAL; + } + + for (cnt = 0; cnt < total_phases; cnt++) { + ranges[row_index][col_index] = phase_table[cnt]; + phases_per_row[row_index] += 1; + col_index++; + + if ((cnt + 1) == total_phases) { + continue; + /* check if next phase in phase_table is consecutive or not */ + } else if ((phase_table[cnt] + 1) != phase_table[cnt + 1]) { + row_index++; + col_index = 0; + } + } + + if (row_index >= MAX_PHASES) + return -EINVAL; + + /* Check if phase-0 is present in first valid window? */ + if (!ranges[0][0]) { + phase_0_found = true; + phase_0_raw_index = 0; + /* Check if cycle exist between 2 valid windows */ + for (cnt = 1; cnt <= row_index; cnt++) { + if (phases_per_row[cnt]) { + for (i = 0; i < phases_per_row[cnt]; i++) { + if (ranges[cnt][i] == 15) { + phase_15_found = true; + phase_15_raw_index = cnt; + break; + } + } + } + } + } + + /* If 2 valid windows form cycle then merge them as single window */ + if (phase_0_found && phase_15_found) { + /* number of phases in raw where phase 0 is present */ + u8 phases_0 = phases_per_row[phase_0_raw_index]; + /* number of phases in raw where phase 15 is present */ + u8 phases_15 = phases_per_row[phase_15_raw_index]; + + if (phases_0 + phases_15 >= MAX_PHASES) + /* + * If there are more than 1 phase windows then total + * number of phases in both the windows should not be + * more than or equal to MAX_PHASES. + */ + return -EINVAL; + + /* Merge 2 cyclic windows */ + i = phases_15; + for (cnt = 0; cnt < phases_0; cnt++) { + ranges[phase_15_raw_index][i] = + ranges[phase_0_raw_index][cnt]; + if (++i >= MAX_PHASES) + break; + } + + phases_per_row[phase_0_raw_index] = 0; + phases_per_row[phase_15_raw_index] = phases_15 + phases_0; + } + + for (cnt = 0; cnt <= row_index; cnt++) { + if (phases_per_row[cnt] > curr_max) { + curr_max = phases_per_row[cnt]; + selected_row_index = cnt; + } + } + + i = ((curr_max * 3) / 4); + if (i) + i--; + + ret = (int)ranges[selected_row_index][i]; + + if (ret >= MAX_PHASES) { + ret = -EINVAL; + pr_err("%s: %s: invalid phase selected=%d\n", + mmc_hostname(mmc), __func__, ret); + } + + pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); + return ret; +} + +static inline void msm_cm_dll_set_freq(struct sdhci_host *host) +{ + u32 mclk_freq = 0; + + /* Program the MCLK value to MCLK_FREQ bit field */ + if (host->clock <= 112000000) + mclk_freq = 0; + else if (host->clock <= 125000000) + mclk_freq = 1; + else if (host->clock <= 137000000) + mclk_freq = 2; + else if (host->clock <= 150000000) + mclk_freq = 3; + else if (host->clock <= 162000000) + mclk_freq = 4; + else if (host->clock <= 175000000) + mclk_freq = 5; + else if (host->clock <= 187000000) + mclk_freq = 6; + else if (host->clock <= 200000000) + mclk_freq = 7; + + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~(7 << 24)) | (mclk_freq << 24)), + host->ioaddr + CORE_DLL_CONFIG); +} + +/* Initialize the DLL (Programmable Delay Line ) */ +static int msm_init_cm_dll(struct sdhci_host *host) +{ + struct mmc_host *mmc = host->mmc; + int rc = 0; + unsigned long flags; + u32 wait_cnt; + + pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); + spin_lock_irqsave(&host->lock, flags); + + /* + * Make sure that clock is always enabled when DLL + * tuning is in progress. Keeping PWRSAVE ON may + * turn off the clock. So let's disable the PWRSAVE + * here and re-enable it once tuning is completed. + */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_CLK_PWRSAVE), + host->ioaddr + CORE_VENDOR_SPEC); + + /* Write 1 to DLL_RST bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); + + /* Write 1 to DLL_PDN bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); + msm_cm_dll_set_freq(host); + + /* Write 0 to DLL_RST bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); + + /* Write 0 to DLL_PDN bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); + + /* Set DLL_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG); + + /* Set CK_OUT_EN bit to 1. */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); + + wait_cnt = 50; + /* Wait until DLL_LOCK bit of DLL_STATUS register becomes '1' */ + while (!(readl_relaxed(host->ioaddr + CORE_DLL_STATUS) & + CORE_DLL_LOCK)) { + /* max. wait for 50us sec for LOCK bit to be set */ + if (--wait_cnt == 0) { + pr_err("%s: %s: DLL failed to LOCK\n", + mmc_hostname(mmc), __func__); + rc = -ETIMEDOUT; + goto out; + } + /* wait for 1us before polling again */ + udelay(1); + } + +out: + /* re-enable PWRSAVE */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | + CORE_CLK_PWRSAVE), + host->ioaddr + CORE_VENDOR_SPEC); + spin_unlock_irqrestore(&host->lock, flags); + pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); + return rc; +} + +int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) +{ + unsigned long flags; + u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0; + const u32 *tuning_block_pattern = tuning_block_64; + int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */ + int rc; + struct mmc_host *mmc = host->mmc; + + pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); + /* Tuning is only required for SDR104 modes */ + spin_lock_irqsave(&host->lock, flags); + + if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) && + (mmc->ios.bus_width == MMC_BUS_WIDTH_8)) { + tuning_block_pattern = tuning_block_128; + size = sizeof(tuning_block_128); + } + spin_unlock_irqrestore(&host->lock, flags); + + /* first of all reset the tuning block */ + rc = msm_init_cm_dll(host); + if (rc) + goto out; + + data_buf = kmalloc(size, GFP_KERNEL); + if (!data_buf) { + rc = -ENOMEM; + goto out; + } + + phase = 0; + do { + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + struct mmc_request mrq = { + .cmd = &cmd, + .data = &data + }; + struct scatterlist sg; + + /* set the phase in delay line hw block */ + rc = msm_config_cm_dll_phase(host, phase); + if (rc) + goto kfree; + + cmd.opcode = opcode; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = size; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.timeout_ns = 1000 * 1000 * 1000; /* 1 sec */ + + data.sg = &sg; + data.sg_len = 1; + sg_init_one(&sg, data_buf, size); + memset(data_buf, 0, size); + mmc_wait_for_req(mmc, &mrq); + + if (!cmd.error && !data.error && + !memcmp(data_buf, tuning_block_pattern, size)) { + /* tuning is successful at this tuning point */ + tuned_phases[tuned_phase_cnt++] = phase; + pr_debug("%s: %s: found good phase = %d\n", + mmc_hostname(mmc), __func__, phase); + } + } while (++phase < 16); + + if (tuned_phase_cnt) { + rc = msm_find_most_appropriate_phase(host, tuned_phases, + tuned_phase_cnt); + if (rc < 0) + goto kfree; + else + phase = (u8)rc; + + /* + * Finally set the selected phase in delay + * line hw block. + */ + rc = msm_config_cm_dll_phase(host, phase); + if (rc) + goto kfree; + pr_debug("%s: %s: finally setting the tuning phase to %d\n", + mmc_hostname(mmc), __func__, phase); + } else { + /* tuning failed */ + pr_err("%s: %s: no tuning point found\n", + mmc_hostname(mmc), __func__); + rc = -EAGAIN; + } + +kfree: + kfree(data_buf); +out: + pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); + return rc; +} + static int sdhci_msm_setup_gpio(struct sdhci_msm_pltfm_data *pdata, bool enable) { struct sdhci_msm_gpio_data *curr; @@ -944,7 +1381,9 @@ static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata, static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) { - struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)data; + struct sdhci_host *host = (struct sdhci_host *)data; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; u8 irq_status = 0; u8 irq_ack = 0; int ret = 0; @@ -1010,6 +1449,16 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) */ mb(); + if (irq_status & CORE_PWRCTL_IO_HIGH) + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & + ~CORE_IO_PAD_PWR_SWITCH), + host->ioaddr + CORE_VENDOR_SPEC); + if (irq_status & CORE_PWRCTL_IO_LOW) + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | + CORE_IO_PAD_PWR_SWITCH), + host->ioaddr + CORE_VENDOR_SPEC); + mb(); + pr_debug("%s: Handled IRQ(%d), ret=%d, ack=0x%x\n", mmc_hostname(msm_host->mmc), irq, ret, irq_ack); wake_up_interruptible(&msm_host->pwr_irq_wait); @@ -1037,8 +1486,22 @@ static void sdhci_msm_check_power_status(struct sdhci_host *host) readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL)); } +static void sdhci_msm_toggle_cdr(struct sdhci_host *host, bool enable) +{ + if (enable) + writel_relaxed((readl_relaxed(host->ioaddr + + CORE_DLL_CONFIG) | CORE_CDR_EN), + host->ioaddr + CORE_DLL_CONFIG); + else + writel_relaxed((readl_relaxed(host->ioaddr + + CORE_DLL_CONFIG) & ~CORE_CDR_EN), + host->ioaddr + CORE_DLL_CONFIG); +} + static struct sdhci_ops sdhci_msm_ops = { .check_power_status = sdhci_msm_check_power_status, + .platform_execute_tuning = sdhci_msm_execute_tuning, + .toggle_cdr = sdhci_msm_toggle_cdr, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -1175,7 +1638,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) } ret = devm_request_threaded_irq(&pdev->dev, pwr_irq, NULL, sdhci_msm_pwr_irq, IRQF_ONESHOT, - dev_name(&pdev->dev), msm_host); + dev_name(&pdev->dev), host); if (ret) { dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n", pwr_irq, ret); From 19891e6b3229ab36b88e25deb078b69b73992704 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 28 Feb 2013 12:21:58 +0530 Subject: [PATCH 016/472] mmc: sdhci: Enable MMC_CAP_HW_RESET capability Enable MMC_CAP_HW_RESET capability so that MMC block layer can reset the hardware during error recovery scenarios. Change-Id: I6100a3c6c34ee4c965595e422f793b195a758a46 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 6c640769720c..e0498847d08f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1651,7 +1651,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; - + msm_host->mmc->caps |= MMC_CAP_HW_RESET; msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; From a507b5a6c4f7b46189b04bf81bf1d66e2fd371f0 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 10 Jan 2013 21:11:04 +0530 Subject: [PATCH 017/472] mmc: sdhci-msm: configure adma descriptors for large request size This patch adds a function to configure adma descriptors to support request size upto 512MB. Change-Id: Ie2ad32106422bb5bdbf72b08d1ecdd74d9a93c19 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e0498847d08f..5b5cbdfa706b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -76,6 +76,9 @@ #define CORE_CLK_PWRSAVE (1 << 1) #define CORE_IO_PAD_PWR_SWITCH (1 << 16) +/* 8KB descriptors */ +#define SDHCI_MSM_MAX_SEGMENTS (1 << 13) + static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, @@ -1498,10 +1501,16 @@ static void sdhci_msm_toggle_cdr(struct sdhci_host *host, bool enable) host->ioaddr + CORE_DLL_CONFIG); } +static unsigned int sdhci_msm_max_segs(void) +{ + return SDHCI_MSM_MAX_SEGMENTS; +} + static struct sdhci_ops sdhci_msm_ops = { .check_power_status = sdhci_msm_check_power_status, .platform_execute_tuning = sdhci_msm_execute_tuning, .toggle_cdr = sdhci_msm_toggle_cdr, + .get_max_segments = sdhci_msm_max_segs, }; static int sdhci_msm_probe(struct platform_device *pdev) From 15685a80dbe71dc8f3fc3f2448e5b340e1d9e50c Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 11 Jan 2013 11:30:45 +0530 Subject: [PATCH 018/472] mmc: sdhci: Add host driver support to enable clock gating Enable config MMC_CLKGATE to enable aggressive clock gating framework that will disable clocks when the host is not in use for 200ms. Change-Id: I6bef5dc18b561871689b3d730fd3486323b12520 Signed-off-by: Sahitya Tummala [venkatg@codeaurora.org: sdhci_set_clock is now a library function thats called from platform clock handler, make changes to address that] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 75 +++++++++++++++++++++++++++++++++--- drivers/mmc/host/sdhci.c | 33 ++++++++++------ 2 files changed, 90 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 5b5cbdfa706b..c92b61b15779 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -78,6 +78,7 @@ /* 8KB descriptors */ #define SDHCI_MSM_MAX_SEGMENTS (1 << 13) +#define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, @@ -198,6 +199,7 @@ struct sdhci_msm_host { struct clk *clk; /* main SD/MMC bus clock */ struct clk *pclk; /* SDHC peripheral bus clock */ struct clk *bus_clk; /* SDHC bus voter clock */ + atomic_t clks_on; /* Set if clocks are enabled */ struct sdhci_msm_pltfm_data *pdata; struct mmc_host *mmc; struct sdhci_pltfm_data sdhci_msm_pdata; @@ -1506,11 +1508,73 @@ static unsigned int sdhci_msm_max_segs(void) return SDHCI_MSM_MAX_SEGMENTS; } +void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) +{ + int rc; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + unsigned long flags; + + if (clock && !atomic_read(&msm_host->clks_on)) { + pr_debug("%s: request to enable clock at rate %u\n", + mmc_hostname(host->mmc), clock); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { + rc = clk_prepare_enable(msm_host->bus_clk); + if (rc) { + pr_err("%s: %s: failed to enable the bus-clock with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto out; + } + } + if (!IS_ERR(msm_host->pclk)) { + rc = clk_prepare_enable(msm_host->pclk); + if (rc) { + pr_err("%s: %s: failed to enable the pclk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_bus_clk; + } + } + rc = clk_prepare_enable(msm_host->clk); + if (rc) { + pr_err("%s: %s: failed to enable the host-clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_pclk; + } + mb(); + atomic_set(&msm_host->clks_on, 1); + + } else if (!clock && atomic_read(&msm_host->clks_on)) { + pr_debug("%s: request to disable clocks\n", + mmc_hostname(host->mmc)); + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + mb(); + clk_disable_unprepare(msm_host->clk); + if (!IS_ERR(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); + atomic_set(&msm_host->clks_on, 0); + } + spin_lock_irqsave(&host->lock, flags); + host->clock = clock; + spin_unlock_irqrestore(&host->lock, flags); + goto out; +disable_pclk: + if (!IS_ERR_OR_NULL(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); +disable_bus_clk: + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) + clk_disable_unprepare(msm_host->bus_clk); +out: + return; +} + static struct sdhci_ops sdhci_msm_ops = { .check_power_status = sdhci_msm_check_power_status, .platform_execute_tuning = sdhci_msm_execute_tuning, .toggle_cdr = sdhci_msm_toggle_cdr, .get_max_segments = sdhci_msm_max_segs, + .set_clock = sdhci_msm_set_clock, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -1587,6 +1651,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) goto pclk_disable; + atomic_set(&msm_host->clks_on, 1); /* Setup regulators */ ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true); if (ret) { @@ -1657,6 +1722,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Enable pwr irq interrupts */ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); + /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */ + msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY; + /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; @@ -1725,12 +1793,7 @@ static int sdhci_msm_remove(struct platform_device *pdev) sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); - if (!IS_ERR(msm_host->clk)) - clk_disable_unprepare(msm_host->clk); - if (!IS_ERR(msm_host->pclk)) - clk_disable_unprepare(msm_host->pclk); - if (!IS_ERR_OR_NULL(msm_host->bus_clk)) - clk_disable_unprepare(msm_host->bus_clk); + if (pdata->pin_data) sdhci_msm_setup_pins(pdata, false); return 0; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0237da8bf1f9..2ed69b8ac9b5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1168,7 +1168,8 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) host->mmc->actual_clock = 0; - sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); + if (host->clock) + sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); if (host->quirks2 & SDHCI_QUIRK2_NEED_DELAY_AFTER_INT_CLK_RST) mdelay(1); @@ -1472,22 +1473,16 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) return; } - /* - * Reset the chip on each power off. - * Should clear out any weird states. - */ - if (ios->power_mode == MMC_POWER_OFF) { - sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); - sdhci_reinit(host); - } - if (host->version >= SDHCI_SPEC_300 && (ios->power_mode == MMC_POWER_UP) && !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) sdhci_enable_preset_value(host, false); + spin_lock_irqsave(&host->lock, flags); if (!ios->clock || ios->clock != host->clock) { + spin_unlock_irqrestore(&host->lock, flags); host->ops->set_clock(host, ios->clock); + spin_lock_irqsave(&host->lock, flags); host->clock = ios->clock; if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK && @@ -1502,8 +1497,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) host->mmc->max_busy_timeout /= host->timeout_clk; } } + spin_unlock_irqrestore(&host->lock, flags); - sdhci_set_power(host, ios->power_mode, ios->vdd); + if (ios->power_mode & (MMC_POWER_UP | MMC_POWER_ON)) + sdhci_set_power(host, ios->power_mode, ios->vdd); if (host->ops->platform_send_init_74_clocks) host->ops->platform_send_init_74_clocks(host, ios->power_mode); @@ -1602,6 +1599,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } else sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); + spin_unlock_irqrestore(&host->lock, flags); /* * Some (ENE) controllers go apeshit on some ios operation, * signalling timeout and CRC errors even on CMD0. Resetting @@ -1610,8 +1608,19 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) if (host->quirks & SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS) sdhci_do_reset(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + /* + * Reset the chip on each power off. + * Should clear out any weird states. + */ + if (ios->power_mode == MMC_POWER_OFF) { + sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); + sdhci_reinit(host); + sdhci_set_power(host, ios->power_mode, ios->vdd); + } + if (!ios->clock) + sdhci_set_clock(host, ios->clock); + mmiowb(); - spin_unlock_irqrestore(&host->lock, flags); } static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) From 9df78479e4fd99ed6502a8be222c0b00e6e6615b Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 27 Apr 2016 17:19:29 -0700 Subject: [PATCH 019/472] Revert "mmc: core: Remove the ->enable|disable() callbacks" This reverts commit 40433267331bc6b9d70d5cdd14bfa2c8e3e5f0ec as MSM platforms still needs ->enable/disable() callbacks. Conflicts: drivers/mmc/core/core.c Change-Id: Ifd986825c10f1475bfcdac37ea1f3b99e5f6daaf Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 6 ++++++ include/linux/mmc/host.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 55a0e84d004c..1a662cf93cb0 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -988,6 +988,9 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) if (pm) pm_runtime_get_sync(mmc_dev(host)); + if (host->ops->enable && !stop && host->claim_cnt == 1) + host->ops->enable(host); + return stop; } EXPORT_SYMBOL(__mmc_claim_host); @@ -1005,6 +1008,9 @@ void mmc_release_host(struct mmc_host *host) WARN_ON(!host->claimed); + if (host->ops->disable && host->claim_cnt == 1) + host->ops->disable(host); + spin_lock_irqsave(&host->lock, flags); if (--host->claim_cnt) { /* Release for nested claim */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 2f0d67880f44..2b6159a85a15 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -80,6 +80,12 @@ struct mmc_ios { }; struct mmc_host_ops { + /* + * 'enable' is called when the host is claimed and 'disable' is called + * when the host is released. 'enable' and 'disable' are deprecated. + */ + int (*enable)(struct mmc_host *host); + int (*disable)(struct mmc_host *host); /* * It is optional for the host to implement pre_req and post_req in * order to support double buffering of requests (prepare one From 880b1e731dd9e8e5620fa5129a891db0f6cc1692 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Sun, 10 Mar 2013 07:03:17 +0530 Subject: [PATCH 020/472] mmc: sdhci: Vote for PM QOS Vote for PM QOS by specifying the acceptable CPU to DMA latency so that system can enter into the possible power states without affecting the SDHC performance. Change-Id: I5fcf9aa93da690c6e64ab70ea5b039ca663c80ad Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict and compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 8 ++++++++ drivers/mmc/host/sdhci.c | 29 ++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.h | 3 +++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c92b61b15779..d96524761540 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -192,6 +192,7 @@ struct sdhci_msm_pltfm_data { struct sdhci_msm_slot_reg_data *vreg_data; bool nonremovable; struct sdhci_msm_pin_data *pin_data; + u32 cpu_dma_latency_us; }; struct sdhci_msm_host { @@ -1045,6 +1046,7 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) struct sdhci_msm_pltfm_data *pdata = NULL; struct device_node *np = dev->of_node; u32 bus_width = 0; + u32 cpu_dma_latency; int len, i; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); @@ -1063,6 +1065,10 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) pdata->mmc_bus_width = 0; } + if (!of_property_read_u32(np, "qcom,cpu-dma-latency-us", + &cpu_dma_latency)) + pdata->cpu_dma_latency_us = cpu_dma_latency; + pdata->vreg_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_slot_reg_data), GFP_KERNEL); @@ -1741,6 +1747,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; + host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; + ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)\n", ret); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2ed69b8ac9b5..49107102083a 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1363,6 +1363,26 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, * * \*****************************************************************************/ +static int sdhci_enable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->cpu_dma_latency_us) + pm_qos_update_request(&host->pm_qos_req_dma, + host->cpu_dma_latency_us); + return 0; +} + +static int sdhci_disable(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->cpu_dma_latency_us) + pm_qos_update_request(&host->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + return 0; +} + static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct sdhci_host *host; @@ -2230,6 +2250,8 @@ static const struct mmc_host_ops sdhci_ops = { .select_drive_strength = sdhci_select_drive_strength, .card_event = sdhci_card_event, .card_busy = sdhci_card_busy, + .enable = sdhci_enable, + .disable = sdhci_disable, }; /*****************************************************************************\ @@ -3444,6 +3466,9 @@ int sdhci_add_host(struct sdhci_host *host) mmiowb(); + if (host->cpu_dma_latency_us) + pm_qos_add_request(&host->pm_qos_req_dma, + PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); mmc_add_host(mmc); pr_info("%s: SDHCI controller on %s [%s] using %s\n", @@ -3494,7 +3519,9 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) sdhci_disable_card_detection(host); - mmc_remove_host(mmc); + if (host->cpu_dma_latency_us) + pm_qos_remove_request(&host->pm_qos_req_dma); + mmc_remove_host(host->mmc); #ifdef SDHCI_USE_LEDS_CLASS led_classdev_unregister(&host->led); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 9287f1099cf4..8d0ae224402d 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -525,6 +526,8 @@ struct sdhci_host { unsigned int tuning_count; /* Timer count for re-tuning */ unsigned int tuning_mode; /* Re-tuning mode supported by host */ #define SDHCI_TUNING_MODE_1 0 + unsigned int cpu_dma_latency_us; + struct pm_qos_request pm_qos_req_dma; unsigned long private[0] ____cacheline_aligned; }; From ca2adf7a7601c23b1922e6ec055ba01ba3d4b963 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Sun, 10 Mar 2013 14:12:52 +0530 Subject: [PATCH 021/472] mmc: sdhci: Vote for the required MSM bus bandwidth Vote for the MSM bus bandwidth required by SDHC driver based on the speed and bus width of the card. Otherwise, the system clocks may run at minimum clock speed and thus affecting the performance. Change-Id: Icf0c8710adbe2770f4eae283a50f4a13671f703f Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 292 ++++++++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.c | 6 + drivers/mmc/host/sdhci.h | 1 + 3 files changed, 298 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d96524761540..ccddf5560795 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -180,6 +181,12 @@ struct sdhci_msm_pin_data { struct sdhci_msm_pad_data *pad_data; }; +struct sdhci_msm_bus_voting_data { + struct msm_bus_scale_pdata *bus_pdata; + unsigned int *bw_vecs; + unsigned int bw_vecs_size; +}; + struct sdhci_msm_pltfm_data { /* Supported UHS-I Modes */ u32 caps; @@ -193,9 +200,21 @@ struct sdhci_msm_pltfm_data { bool nonremovable; struct sdhci_msm_pin_data *pin_data; u32 cpu_dma_latency_us; + struct sdhci_msm_bus_voting_data *voting_data; +}; + +struct sdhci_msm_bus_vote { + uint32_t client_handle; + uint32_t curr_vote; + int min_bw_vote; + int max_bw_vote; + bool is_max_bw_needed; + struct delayed_work vote_work; + struct device_attribute max_bus_bw; }; struct sdhci_msm_host { + struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ struct clk *clk; /* main SD/MMC bus clock */ struct clk *pclk; /* SDHC peripheral bus clock */ @@ -205,6 +224,7 @@ struct sdhci_msm_host { struct mmc_host *mmc; struct sdhci_pltfm_data sdhci_msm_pdata; wait_queue_head_t pwr_irq_wait; + struct sdhci_msm_bus_vote msm_bus_vote; }; enum vdd_io_level { @@ -1126,6 +1146,218 @@ out: return NULL; } +/* Returns required bandwidth in Bytes per Sec */ +static unsigned int sdhci_get_bw_required(struct sdhci_host *host, + struct mmc_ios *ios) +{ + unsigned int bw; + + bw = host->clock; + /* + * For DDR mode, SDCC controller clock will be at + * the double rate than the actual clock that goes to card. + */ + if (ios->bus_width == MMC_BUS_WIDTH_4) + bw /= 2; + else if (ios->bus_width == MMC_BUS_WIDTH_1) + bw /= 8; + + return bw; +} + +static int sdhci_msm_bus_get_vote_for_bw(struct sdhci_msm_host *host, + unsigned int bw) +{ + unsigned int *table = host->pdata->voting_data->bw_vecs; + unsigned int size = host->pdata->voting_data->bw_vecs_size; + int i; + + if (host->msm_bus_vote.is_max_bw_needed && bw) + return host->msm_bus_vote.max_bw_vote; + + for (i = 0; i < size; i++) { + if (bw <= table[i]) + break; + } + + if (i && (i == size)) + i--; + + return i; +} + +/* + * This function must be called with host lock acquired. + * Caller of this function should also ensure that msm bus client + * handle is not null. + */ +static inline int sdhci_msm_bus_set_vote(struct sdhci_msm_host *msm_host, + int vote, + unsigned long flags) +{ + struct sdhci_host *host = platform_get_drvdata(msm_host->pdev); + int rc = 0; + + if (vote != msm_host->msm_bus_vote.curr_vote) { + spin_unlock_irqrestore(&host->lock, flags); + rc = msm_bus_scale_client_update_request( + msm_host->msm_bus_vote.client_handle, vote); + spin_lock_irqsave(&host->lock, flags); + if (rc) { + pr_err("%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n", + mmc_hostname(host->mmc), + msm_host->msm_bus_vote.client_handle, vote, rc); + goto out; + } + msm_host->msm_bus_vote.curr_vote = vote; + } +out: + return rc; +} + +/* + * Internal work. Work to set 0 bandwidth for msm bus. + */ +static void sdhci_msm_bus_work(struct work_struct *work) +{ + struct sdhci_msm_host *msm_host; + struct sdhci_host *host; + unsigned long flags; + + msm_host = container_of(work, struct sdhci_msm_host, + msm_bus_vote.vote_work.work); + host = platform_get_drvdata(msm_host->pdev); + + if (!msm_host->msm_bus_vote.client_handle) + return; + + spin_lock_irqsave(&host->lock, flags); + /* don't vote for 0 bandwidth if any request is in progress */ + if (!host->mrq) { + sdhci_msm_bus_set_vote(msm_host, + msm_host->msm_bus_vote.min_bw_vote, flags); + } else + pr_warning("%s: %s: Transfer in progress. skipping bus voting to 0 bandwidth\n", + mmc_hostname(host->mmc), __func__); + spin_unlock_irqrestore(&host->lock, flags); +} + +/* + * This function cancels any scheduled delayed work and sets the bus + * vote based on bw (bandwidth) argument. + */ +static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host, + unsigned int bw) +{ + int vote; + unsigned long flags; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + cancel_delayed_work_sync(&msm_host->msm_bus_vote.vote_work); + spin_lock_irqsave(&host->lock, flags); + vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw); + sdhci_msm_bus_set_vote(msm_host, vote, flags); + spin_unlock_irqrestore(&host->lock, flags); +} + +#define MSM_MMC_BUS_VOTING_DELAY 200 /* msecs */ + +/* This function queues a work which will set the bandwidth requiement to 0 */ +static void sdhci_msm_bus_queue_work(struct sdhci_host *host) +{ + unsigned long flags; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + spin_lock_irqsave(&host->lock, flags); + if (msm_host->msm_bus_vote.min_bw_vote != + msm_host->msm_bus_vote.curr_vote) + queue_delayed_work(system_wq, + &msm_host->msm_bus_vote.vote_work, + msecs_to_jiffies(MSM_MMC_BUS_VOTING_DELAY)); + spin_unlock_irqrestore(&host->lock, flags); +} + +static int sdhci_msm_bus_register(struct sdhci_msm_host *host, + struct platform_device *pdev) +{ + int rc = 0; + struct msm_bus_scale_pdata *bus_pdata; + + struct sdhci_msm_bus_voting_data *data; + struct device *dev = &pdev->dev; + + data = devm_kzalloc(dev, + sizeof(struct sdhci_msm_bus_voting_data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, + "%s: failed to allocate memory\n", __func__); + rc = -ENOMEM; + goto out; + } + data->bus_pdata = msm_bus_cl_get_pdata(pdev); + if (data->bus_pdata) { + rc = sdhci_msm_dt_get_array(dev, "qcom,bus-bw-vectors-bps", + &data->bw_vecs, &data->bw_vecs_size, 0); + if (rc) { + dev_err(&pdev->dev, + "%s: Failed to get bus-bw-vectors-bps\n", + __func__); + goto out; + } + host->pdata->voting_data = data; + } + if (host->pdata->voting_data && + host->pdata->voting_data->bus_pdata && + host->pdata->voting_data->bw_vecs && + host->pdata->voting_data->bw_vecs_size) { + + bus_pdata = host->pdata->voting_data->bus_pdata; + host->msm_bus_vote.client_handle = + msm_bus_scale_register_client(bus_pdata); + if (!host->msm_bus_vote.client_handle) { + dev_err(&pdev->dev, "msm_bus_scale_register_client()\n"); + rc = -EFAULT; + goto out; + } + /* cache the vote index for minimum and maximum bandwidth */ + host->msm_bus_vote.min_bw_vote = + sdhci_msm_bus_get_vote_for_bw(host, 0); + host->msm_bus_vote.max_bw_vote = + sdhci_msm_bus_get_vote_for_bw(host, UINT_MAX); + } else { + devm_kfree(dev, data); + } + +out: + return rc; +} + +static void sdhci_msm_bus_unregister(struct sdhci_msm_host *host) +{ + if (host->msm_bus_vote.client_handle) + msm_bus_scale_unregister_client( + host->msm_bus_vote.client_handle); +} + +static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct mmc_ios *ios = &host->mmc->ios; + unsigned int bw; + + if (!msm_host->msm_bus_vote.client_handle) + return; + + bw = sdhci_get_bw_required(host, ios); + if (enable) + sdhci_msm_bus_cancel_work_and_set_vote(host, bw); + else + sdhci_msm_bus_queue_work(host); +} + /* Regulator utility functions */ static int sdhci_msm_vreg_init_reg(struct device *dev, struct sdhci_msm_reg_data *vreg) @@ -1476,6 +1708,36 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) return IRQ_HANDLED; } +static ssize_t +show_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + return snprintf(buf, PAGE_SIZE, "%u\n", + msm_host->msm_bus_vote.is_max_bw_needed); +} + +static ssize_t +store_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + uint32_t value; + unsigned long flags; + + if (!kstrtou32(buf, 0, &value)) { + spin_lock_irqsave(&host->lock, flags); + msm_host->msm_bus_vote.is_max_bw_needed = !!value; + spin_unlock_irqrestore(&host->lock, flags); + } + return count; +} + static void sdhci_msm_check_power_status(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -1581,6 +1843,7 @@ static struct sdhci_ops sdhci_msm_ops = { .toggle_cdr = sdhci_msm_toggle_cdr, .get_max_segments = sdhci_msm_max_segs, .set_clock = sdhci_msm_set_clock, + .platform_bus_voting = sdhci_msm_bus_voting, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -1611,6 +1874,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) pltfm_host = sdhci_priv(host); pltfm_host->priv = msm_host; msm_host->mmc = host->mmc; + msm_host->pdev = pdev; /* Extract platform data */ if (pdev->dev.of_node) { @@ -1749,10 +2013,18 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; + ret = sdhci_msm_bus_register(msm_host, pdev); + if (ret) + goto vreg_deinit; + + if (msm_host->msm_bus_vote.client_handle) + INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, + sdhci_msm_bus_work); + ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)\n", ret); - goto vreg_deinit; + goto bus_unregister; } /* Set core clk rate, optionally override from dts */ @@ -1764,12 +2036,24 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto remove_host; } + msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw; + msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw; + sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr); + msm_host->msm_bus_vote.max_bus_bw.attr.name = "max_bus_bw"; + msm_host->msm_bus_vote.max_bus_bw.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(&pdev->dev, + &msm_host->msm_bus_vote.max_bus_bw); + if (ret) + goto remove_host; + /* Successful initialization */ goto out; remove_host: dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); sdhci_remove_host(host, dead); +bus_unregister: + sdhci_msm_bus_unregister(msm_host); vreg_deinit: sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); clk_disable: @@ -1798,12 +2082,18 @@ static int sdhci_msm_remove(struct platform_device *pdev) 0xffffffff); pr_debug("%s: %s\n", dev_name(&pdev->dev), __func__); + device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); if (pdata->pin_data) sdhci_msm_setup_pins(pdata, false); + + if (msm_host->msm_bus_vote.client_handle) { + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + sdhci_msm_bus_unregister(msm_host); + } return 0; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 49107102083a..2cc859cdffcf 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1370,6 +1370,9 @@ static int sdhci_enable(struct mmc_host *mmc) if (host->cpu_dma_latency_us) pm_qos_update_request(&host->pm_qos_req_dma, host->cpu_dma_latency_us); + if (host->ops->platform_bus_voting) + host->ops->platform_bus_voting(host, 1); + return 0; } @@ -1380,6 +1383,9 @@ static int sdhci_disable(struct mmc_host *mmc) if (host->cpu_dma_latency_us) pm_qos_update_request(&host->pm_qos_req_dma, PM_QOS_DEFAULT_VALUE); + if (host->ops->platform_bus_voting) + host->ops->platform_bus_voting(host, 0); + return 0; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 8d0ae224402d..9f1a25241220 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -562,6 +562,7 @@ struct sdhci_ops { void (*adma_workaround)(struct sdhci_host *host, u32 intmask); void (*platform_init)(struct sdhci_host *host); void (*card_event)(struct sdhci_host *host); + void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, struct mmc_card *card, From cff2042613214c2406de6ff81a6d937991e6f4fc Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 12 Mar 2013 14:57:46 +0530 Subject: [PATCH 022/472] mmc: sdhci-msm: Add support for hardware based card detection Add support for hardware based card detection for external SD card slot. Change-Id: I3e081f2eff54d6932a89f826cc85c201c52ca840 Signed-off-by: Sahitya Tummala [venkatg@codeaurora.org: Fix arguments for mmc_gpio_request_cd as the signature had changed in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ccddf5560795..feddbaf8f370 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -200,6 +201,7 @@ struct sdhci_msm_pltfm_data { bool nonremovable; struct sdhci_msm_pin_data *pin_data; u32 cpu_dma_latency_us; + int status_gpio; /* card detection GPIO that is configured as IRQ */ struct sdhci_msm_bus_voting_data *voting_data; }; @@ -1075,6 +1077,8 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) goto out; } + pdata->status_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, 0); + of_property_read_u32(np, "qcom,bus-width", &bus_width); if (bus_width == 8) pdata->mmc_bus_width = MMC_CAP_8_BIT_DATA; @@ -2021,10 +2025,20 @@ static int sdhci_msm_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, sdhci_msm_bus_work); + if (gpio_is_valid(msm_host->pdata->status_gpio)) { + ret = mmc_gpio_request_cd(msm_host->mmc, + msm_host->pdata->status_gpio, 0); + if (ret) { + dev_err(&pdev->dev, "%s: Failed to request card detection IRQ %d\n", + __func__, ret); + goto bus_unregister; + } + } + ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)\n", ret); - goto bus_unregister; + goto free_cd_gpio; } /* Set core clk rate, optionally override from dts */ @@ -2052,6 +2066,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) remove_host: dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); sdhci_remove_host(host, dead); +free_cd_gpio: + if (gpio_is_valid(msm_host->pdata->status_gpio)) + mmc_gpio_free_cd(msm_host->mmc); bus_unregister: sdhci_msm_bus_unregister(msm_host); vreg_deinit: @@ -2085,6 +2102,10 @@ static int sdhci_msm_remove(struct platform_device *pdev) device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); + + if (gpio_is_valid(msm_host->pdata->status_gpio)) + mmc_gpio_free_cd(msm_host->mmc); + sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); if (pdata->pin_data) From 8bbdb3514b5416b7592bbdf746f8def34309bf77 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 20 Mar 2013 19:34:59 +0530 Subject: [PATCH 023/472] mmc: sdhci-msm: update dma_mask for SDHC device Set the dma_mask to 0xffffffff to indicate full 32-bit address space and thus avoiding unnecessary buffers bouncing from high to low memory. Change-Id: Idaffe14d4e54a27b15e5a5d82dad41d843714d57 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index feddbaf8f370..2709904fbcde 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -2035,6 +2036,13 @@ static int sdhci_msm_probe(struct platform_device *pdev) } } + if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) { + host->dma_mask = DMA_BIT_MASK(32); + mmc_dev(host->mmc)->dma_mask = &host->dma_mask; + } else { + dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__); + } + ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)\n", ret); From d9daffaae9e9c9334fdf61a2fa733d579f868b93 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 21 Mar 2013 11:13:25 +0530 Subject: [PATCH 024/472] mmc: sdhci-msm: Implement uhs_set_signaling to select right mode The MSM SDHCI controller requires SDR104 mode to be set for HS200 cards. To handle this case, implement uhs_set_signaling so that the mode selection for MSM SDHCI doesn't happen in sdhci driver. Change-Id: I901dc82312b4299e86a7812dd74d3682650966a2 Signed-off-by: Sahitya Tummala [venkatg@codeaurora.org: Fix fn signature for set_uhs_signaling that changed as part of 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 2709904fbcde..217f7c34dfa8 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1842,7 +1842,32 @@ out: return; } +static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, + unsigned int uhs) +{ + u16 ctrl_2; + + ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); + /* Select Bus Speed Mode for host */ + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + if (uhs == MMC_TIMING_MMC_HS200) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (uhs == MMC_TIMING_UHS_SDR12) + ctrl_2 |= SDHCI_CTRL_UHS_SDR12; + else if (uhs == MMC_TIMING_UHS_SDR25) + ctrl_2 |= SDHCI_CTRL_UHS_SDR25; + else if (uhs == MMC_TIMING_UHS_SDR50) + ctrl_2 |= SDHCI_CTRL_UHS_SDR50; + else if (uhs == MMC_TIMING_UHS_SDR104) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (uhs == MMC_TIMING_UHS_DDR50) + ctrl_2 |= SDHCI_CTRL_UHS_DDR50; + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); + +} + static struct sdhci_ops sdhci_msm_ops = { + .set_uhs_signaling = sdhci_msm_set_uhs_signaling, .check_power_status = sdhci_msm_check_power_status, .platform_execute_tuning = sdhci_msm_execute_tuning, .toggle_cdr = sdhci_msm_toggle_cdr, From 97bdc9cac7be74f770c8a7da5f2491e551a85194 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 28 Feb 2013 19:50:51 +0530 Subject: [PATCH 025/472] mmc: sdhci: Enable clock scaling feature Add support for enabling clock scaling feature and indicate the same to MMC core layer by setting the capability MMC_CAP2_CLK_SCALE. Change-Id: I24f144d3f727160c302966888fb439b3a39a0dde Signed-off-by: Sahitya Tummala [venkatg@codeaurora.org: sdhci_set_clock is now a library function thats called from platform clock handler, make changes to address that] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 175 +++++++++++++++++++++++++++++------ drivers/mmc/host/sdhci.c | 4 + drivers/mmc/host/sdhci.h | 2 + 3 files changed, 155 insertions(+), 26 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 217f7c34dfa8..a21dde2d694e 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -197,13 +197,14 @@ struct sdhci_msm_pltfm_data { u32 caps2; unsigned long mmc_bus_width; - u32 max_clk; struct sdhci_msm_slot_reg_data *vreg_data; bool nonremovable; struct sdhci_msm_pin_data *pin_data; u32 cpu_dma_latency_us; int status_gpio; /* card detection GPIO that is configured as IRQ */ struct sdhci_msm_bus_voting_data *voting_data; + u32 *sup_clk_table; + unsigned char sup_clk_cnt; }; struct sdhci_msm_bus_vote { @@ -228,6 +229,7 @@ struct sdhci_msm_host { struct sdhci_pltfm_data sdhci_msm_pdata; wait_queue_head_t pwr_irq_wait; struct sdhci_msm_bus_vote msm_bus_vote; + u32 clk_rate; /* Keeps track of current clock rate that is set */ }; enum vdd_io_level { @@ -547,9 +549,18 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */ int rc; struct mmc_host *mmc = host->mmc; + struct mmc_ios ios = host->mmc->ios; + + /* + * Tuning is required for SDR104 and HS200 cards and if clock frequency + * is greater than 100MHz in these modes. + */ + if (host->clock <= (100 * 1000 * 1000) || + !(ios.timing == MMC_TIMING_MMC_HS200 || + ios.timing == MMC_TIMING_UHS_SDR104)) + return 0; pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); - /* Tuning is only required for SDR104 modes */ spin_lock_irqsave(&host->lock, flags); if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) && @@ -1071,6 +1082,8 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) u32 bus_width = 0; u32 cpu_dma_latency; int len, i; + int clk_table_len; + u32 *clk_table = NULL; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { @@ -1094,6 +1107,18 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) &cpu_dma_latency)) pdata->cpu_dma_latency_us = cpu_dma_latency; + if (sdhci_msm_dt_get_array(dev, "qcom,clk-rates", + &clk_table, &clk_table_len, 0)) { + dev_err(dev, "failed parsing supported clock rates\n"); + goto out; + } + if (!clk_table || !clk_table_len) { + dev_err(dev, "Invalid clock table\n"); + goto out; + } + pdata->sup_clk_table = clk_table; + pdata->sup_clk_cnt = clk_table_len; + pdata->vreg_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_slot_reg_data), GFP_KERNEL); @@ -1119,8 +1144,6 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) goto out; } - of_property_read_u32(np, "qcom,max-clk-rate", &pdata->max_clk); - len = of_property_count_strings(np, "qcom,bus-speed-mode"); for (i = 0; i < len; i++) { @@ -1781,16 +1804,58 @@ static unsigned int sdhci_msm_max_segs(void) return SDHCI_MSM_MAX_SEGMENTS; } -void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) +static unsigned int sdhci_msm_get_min_clock(struct sdhci_host *host) { - int rc; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; - unsigned long flags; - if (clock && !atomic_read(&msm_host->clks_on)) { - pr_debug("%s: request to enable clock at rate %u\n", - mmc_hostname(host->mmc), clock); + return msm_host->pdata->sup_clk_table[0]; +} + +static unsigned int sdhci_msm_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int max_clk_index = msm_host->pdata->sup_clk_cnt; + + return msm_host->pdata->sup_clk_table[max_clk_index - 1]; +} + +static unsigned int sdhci_msm_get_sup_clk_rate(struct sdhci_host *host, + u32 req_clk) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + unsigned int sel_clk = -1; + unsigned char cnt; + + if (req_clk < sdhci_msm_get_min_clock(host)) { + sel_clk = sdhci_msm_get_min_clock(host); + return sel_clk; + } + + for (cnt = 0; cnt < msm_host->pdata->sup_clk_cnt; cnt++) { + if (msm_host->pdata->sup_clk_table[cnt] > req_clk) { + break; + } else if (msm_host->pdata->sup_clk_table[cnt] == req_clk) { + sel_clk = msm_host->pdata->sup_clk_table[cnt]; + break; + } else { + sel_clk = msm_host->pdata->sup_clk_table[cnt]; + } + } + return sel_clk; +} + +static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int rc = 0; + + if (enable && !atomic_read(&msm_host->clks_on)) { + pr_debug("%s: request to enable clocks\n", + mmc_hostname(host->mmc)); if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { rc = clk_prepare_enable(msm_host->bus_clk); if (rc) { @@ -1814,9 +1879,8 @@ void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) goto disable_pclk; } mb(); - atomic_set(&msm_host->clks_on, 1); - } else if (!clock && atomic_read(&msm_host->clks_on)) { + } else if (!enable && atomic_read(&msm_host->clks_on)) { pr_debug("%s: request to disable clocks\n", mmc_hostname(host->mmc)); sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); @@ -1826,11 +1890,8 @@ void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) clk_disable_unprepare(msm_host->pclk); if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); - atomic_set(&msm_host->clks_on, 0); } - spin_lock_irqsave(&host->lock, flags); - host->clock = clock; - spin_unlock_irqrestore(&host->lock, flags); + atomic_set(&msm_host->clks_on, enable); goto out; disable_pclk: if (!IS_ERR_OR_NULL(msm_host->pclk)) @@ -1839,7 +1900,54 @@ disable_bus_clk: if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); out: - return; + return rc; +} + +static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) +{ + int rc; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct mmc_ios curr_ios = host->mmc->ios; + u32 sup_clock, ddr_clock; + + if (!clock) { + sdhci_msm_prepare_clocks(host, false); + host->clock = clock; + goto out; + } + + rc = sdhci_msm_prepare_clocks(host, true); + if (rc) + goto out; + + sup_clock = sdhci_msm_get_sup_clk_rate(host, clock); + if (curr_ios.timing == MMC_TIMING_UHS_DDR50) { + /* + * The SDHC requires internal clock frequency to be double the + * actual clock that will be set for DDR mode. The controller + * uses the faster clock(100MHz) for some of its parts and send + * the actual required clock (50MHz) to the card. + */ + ddr_clock = clock * 2; + sup_clock = sdhci_msm_get_sup_clk_rate(host, + ddr_clock); + } + if (sup_clock != msm_host->clk_rate) { + pr_debug("%s: %s: setting clk rate to %u\n", + mmc_hostname(host->mmc), __func__, sup_clock); + rc = clk_set_rate(msm_host->clk, sup_clock); + if (rc) { + pr_err("%s: %s: Failed to set rate %u for host-clk : %d\n", + mmc_hostname(host->mmc), __func__, + sup_clock, rc); + goto out; + } + msm_host->clk_rate = sup_clock; + host->clock = clock; + } +out: + sdhci_set_clock(host, clock); } static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, @@ -1862,6 +1970,17 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, ctrl_2 |= SDHCI_CTRL_UHS_SDR104; else if (uhs == MMC_TIMING_UHS_DDR50) ctrl_2 |= SDHCI_CTRL_UHS_DDR50; + /* + * When clock frquency is less than 100MHz, the feedback clock must be + * provided and DLL must not be used so that tuning can be skipped. To + * provide feedback clock, the mode selection can be any value less + * than 3'b011 in bits [2:0] of HOST CONTROL2 register. + */ + if (host->clock <= (100 * 1000 * 1000) && + (uhs == MMC_TIMING_MMC_HS200 || + uhs == MMC_TIMING_UHS_SDR104)) + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); } @@ -1874,6 +1993,8 @@ static struct sdhci_ops sdhci_msm_ops = { .get_max_segments = sdhci_msm_max_segs, .set_clock = sdhci_msm_set_clock, .platform_bus_voting = sdhci_msm_bus_voting, + .get_min_clock = sdhci_msm_get_min_clock, + .get_max_clock = sdhci_msm_get_max_clock, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -1951,7 +2072,15 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) goto pclk_disable; + /* Set to the minimum supported clock frequency */ + ret = clk_set_rate(msm_host->clk, sdhci_msm_get_min_clock(host)); + if (ret) { + dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret); + goto clk_disable; + } + msm_host->clk_rate = sdhci_msm_get_min_clock(host); atomic_set(&msm_host->clks_on, 1); + /* Setup regulators */ ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true); if (ret) { @@ -1983,6 +2112,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) */ host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; + host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; + host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; host->quirks2 |= SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING; host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); @@ -2037,6 +2168,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL; msm_host->mmc->caps2 |= MMC_CAP2_INIT_BKOPS; msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; + msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; @@ -2074,15 +2206,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto free_cd_gpio; } - /* Set core clk rate, optionally override from dts */ - if (msm_host->pdata->max_clk) - host->max_clk = msm_host->pdata->max_clk; - ret = clk_set_rate(msm_host->clk, host->max_clk); - if (ret) { - dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret); - goto remove_host; - } - msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw; msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw; sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2cc859cdffcf..5adddc4b30ce 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1253,6 +1253,10 @@ void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) clock_set: if (real_div) host->mmc->actual_clock = (host->max_clk * clk_mul) / real_div; + + if (host->quirks2 & SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK) + div = 0; + 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/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 9f1a25241220..1329531767ec 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -434,6 +434,8 @@ struct sdhci_host { /* Ignore CMD CRC errors for tuning commands */ #define SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING (1<<6) +#define SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK (1<<19) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From c071dd5ae5fd4d69e4d73edc034a77f5af8c7355 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 11 Mar 2013 12:17:57 -0700 Subject: [PATCH 026/472] mmc: sdhci-msm: Disable SDHC slots at bootup if required Add support to disable available SDHC slots at bootup controlled via a module param with a bit mask of slots to disable. QDSS is one use case where SDHC slot is disabled for trace output. Example Usage: Passing sdhci_msm.disable_slots=1 as kernel command line argument would disable SDHC slot 1, whereas passing sdhci_msm.disable_slots=3 would disable both slots 1 and 2. Change-Id: I97bc517adfe4a1a81a97a2789d77404b0f22b124 Signed-off-by: Venkat Gopalakrishnan Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index a21dde2d694e..8c077135d1c2 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -101,6 +101,10 @@ static const u32 tuning_block_128[] = { 0xFFFFBBBB, 0xFFFF77FF, 0xFF7777FF, 0xEEDDBB77 }; +static int disable_slots; +/* root can write, others read */ +module_param(disable_slots, int, S_IRUGO|S_IWUSR); + /* This structure keeps information per regulator */ struct sdhci_msm_reg_data { /* voltage regulator handle */ @@ -2029,6 +2033,19 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Extract platform data */ if (pdev->dev.of_node) { + ret = of_alias_get_id(pdev->dev.of_node, "sdhc"); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get slot index %d\n", + ret); + goto pltfm_free; + } + if (disable_slots & (1 << (ret - 1))) { + dev_info(&pdev->dev, "%s: Slot %d disabled\n", __func__, + ret); + ret = -ENODEV; + goto pltfm_free; + } + msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev); if (!msm_host->pdata) { dev_err(&pdev->dev, "DT parsing error\n"); From f55253f3e8b7b8adec1f7d582c44331d6919f671 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 29 Mar 2013 11:24:56 +0530 Subject: [PATCH 027/472] mmc: sdhci-msm: disable BKOPS feature The BKOPS feature is supported for eMMC cards of version 4.41 and higher. The BKOPS feature is one time programmable and once it was enabled on a certain MMC card is cannot be disabled. LA builds are often being used to verify phones that are targeted for other HLOSes. Since not all the HLOSes implement the BKOPS features, enabling this feature by default can cause instability when the phone will be used by HLOSes other than LA. Change-Id: I7b9eab0d04a86dfeaf7565dcda8bc9d2035fe02d Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 8c077135d1c2..7c62538ed0ee 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2183,7 +2183,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC | MMC_CAP2_DETECT_ON_ERR); msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL; - msm_host->mmc->caps2 |= MMC_CAP2_INIT_BKOPS; msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; From a93eca74ce9b74cb6e6611664b1cc7f86030511b Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 20 Mar 2013 22:53:40 +0530 Subject: [PATCH 028/472] mmc: sdhci: Check device state before starting a request This patch checks the device state before starting a request. It also prints out useful information in case of error conditions. Change-Id: Iaf87bb069c3ffb13c9b3f174c07c25d612bdcee9 Signed-off-by: Asutosh Das [venkatg@codeaurora.org: remove pm related stuff] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 96 ++++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 19 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 5adddc4b30ce..7c52867bc5c3 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -79,60 +79,80 @@ static void sdhci_runtime_pm_bus_off(struct sdhci_host *host) } #endif +static void sdhci_dump_state(struct sdhci_host *host) +{ + struct mmc_host *mmc = host->mmc; + + #ifdef CONFIG_MMC_CLKGATE + pr_info("%s: clk: %d clk-gated: %d claimer: %s pwr: %d\n", + mmc_hostname(mmc), host->clock, mmc->clk_gated, + mmc->claimer->comm, host->pwr); + #else + pr_info("%s: clk: %d claimer: %s pwr: %d\n", + mmc_hostname(mmc), host->clock, + mmc->claimer->comm, host->pwr); + #endif + pr_info("%s: rpmstatus[pltfm](runtime-suspend:usage_count:disable_depth)(%d:%d:%d)\n", + mmc_hostname(mmc), mmc->parent->power.runtime_status, + atomic_read(&mmc->parent->power.usage_count), + mmc->parent->power.disable_depth); +} + static void sdhci_dumpregs(struct sdhci_host *host) { - pr_debug(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", + pr_info(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", mmc_hostname(host->mmc)); - pr_debug(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", + pr_info(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", sdhci_readl(host, SDHCI_DMA_ADDRESS), sdhci_readw(host, SDHCI_HOST_VERSION)); - pr_debug(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", + pr_info(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", sdhci_readw(host, SDHCI_BLOCK_SIZE), sdhci_readw(host, SDHCI_BLOCK_COUNT)); - pr_debug(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", + pr_info(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", sdhci_readl(host, SDHCI_ARGUMENT), sdhci_readw(host, SDHCI_TRANSFER_MODE)); - pr_debug(DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", + pr_info(DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", sdhci_readl(host, SDHCI_PRESENT_STATE), sdhci_readb(host, SDHCI_HOST_CONTROL)); - pr_debug(DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", + pr_info(DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", sdhci_readb(host, SDHCI_POWER_CONTROL), sdhci_readb(host, SDHCI_BLOCK_GAP_CONTROL)); - pr_debug(DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", + pr_info(DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", sdhci_readb(host, SDHCI_WAKE_UP_CONTROL), sdhci_readw(host, SDHCI_CLOCK_CONTROL)); - pr_debug(DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", + pr_info(DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", sdhci_readb(host, SDHCI_TIMEOUT_CONTROL), sdhci_readl(host, SDHCI_INT_STATUS)); - pr_debug(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", + pr_info(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", sdhci_readl(host, SDHCI_INT_ENABLE), sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); - pr_debug(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", + pr_info(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", sdhci_readw(host, SDHCI_ACMD12_ERR), sdhci_readw(host, SDHCI_SLOT_INT_STATUS)); - pr_debug(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", + pr_info(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", sdhci_readl(host, SDHCI_CAPABILITIES), sdhci_readl(host, SDHCI_CAPABILITIES_1)); - pr_debug(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", + pr_info(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", sdhci_readw(host, SDHCI_COMMAND), sdhci_readl(host, SDHCI_MAX_CURRENT)); - pr_debug(DRIVER_NAME ": Host ctl2: 0x%08x\n", + pr_info(DRIVER_NAME ": Host ctl2: 0x%08x\n", sdhci_readw(host, SDHCI_HOST_CONTROL2)); if (host->flags & SDHCI_USE_ADMA) { if (host->flags & SDHCI_USE_64_BIT_DMA) - pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x%08x\n", + pr_info(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x%08x\n", readl(host->ioaddr + SDHCI_ADMA_ERROR), readl(host->ioaddr + SDHCI_ADMA_ADDRESS_HI), readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); else - pr_debug(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", + pr_info(DRIVER_NAME ": ADMA Err: 0x%08x | ADMA Ptr: 0x%08x\n", readl(host->ioaddr + SDHCI_ADMA_ERROR), readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); } - pr_debug(DRIVER_NAME ": ===========================================\n"); + sdhci_dump_state(host); + pr_info(DRIVER_NAME ": ===========================================\n"); } /*****************************************************************************\ @@ -1393,6 +1413,14 @@ static int sdhci_disable(struct mmc_host *mmc) return 0; } +static bool sdhci_check_state(struct sdhci_host *host) +{ + if (!host->clock || !host->pwr) + return true; + else + return false; +} + static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct sdhci_host *host; @@ -1402,6 +1430,15 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host = mmc_priv(mmc); sdhci_runtime_pm_get(host); + if (sdhci_check_state(host)) { + sdhci_dump_state(host); + WARN(1, "sdhci in bad state"); + mrq->cmd->error = -EIO; + if (mrq->data) + mrq->data->error = -EIO; + tasklet_schedule(&host->finish_tasklet); + return; + } /* Firstly check card presence */ present = sdhci_do_get_cd(host); @@ -2348,6 +2385,11 @@ static void sdhci_timeout_timer(unsigned long data) sdhci_dumpregs(host); if (host->data) { + pr_info("%s: bytes to transfer: %d transferred: %d\n", + mmc_hostname(host->mmc), + (host->data->blksz * host->data->blocks), + (sdhci_readw(host, SDHCI_BLOCK_SIZE) & 0xFFF) * + sdhci_readw(host, SDHCI_BLOCK_COUNT)); host->data->error = -ETIMEDOUT; sdhci_finish_data(host); } else { @@ -2482,6 +2524,7 @@ static void sdhci_adma_show_error(struct sdhci_host *host) { } static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) { u32 command; + bool pr_msg = false; BUG_ON(intmask == 0); /* CMD19 generates _only_ Buffer Read Ready interrupt */ @@ -2544,10 +2587,25 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) if (host->ops->adma_workaround) host->ops->adma_workaround(host, intmask); } - - if (host->data->error) + if (host->data->error) { + if ((intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT)) && + (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING)) { + command = SDHCI_GET_CMD(sdhci_readw(host, + SDHCI_COMMAND)); + if ((command != MMC_SEND_TUNING_BLOCK_HS200) && + (command != MMC_SEND_TUNING_BLOCK)) + pr_msg = true; + } else { + pr_msg = true; + } + if (pr_msg) { + pr_err("%s: data txfr (0x%08x) error: %d\n", + mmc_hostname(host->mmc), intmask, + host->data->error); + sdhci_dumpregs(host); + } sdhci_finish_data(host); - else { + } else { if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) sdhci_transfer_pio(host); From 84756b990eddeda15c1e769b8dc8c8b86c11a248 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 1 Apr 2013 21:01:59 -0700 Subject: [PATCH 029/472] mmc: sdhci-msm: add quirk for max_discard calculation The SDHCi driver by default specifies a parameter that causes the core layer to calculate a max discard value which will be set on the mmc queue. Unfortunately the value calculated because of this would be very small compared to what comes in by default. As a result of this, any secure discard kind of operations are very slow. Instead add quirk so that any SDHCi hosts that would like to use the default value can do so. Change-Id: Ifa1343c3e7f14b031da30b06203a8831ba544889 Signed-off-by: Krishna Konda [venkatg@codeaurora.org: change max_discard_to was renamed to max_busy_timeout in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 + drivers/mmc/host/sdhci.c | 4 ++++ drivers/mmc/host/sdhci.h | 2 ++ 3 files changed, 7 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 7c62538ed0ee..c6f885895906 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2132,6 +2132,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; host->quirks2 |= SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING; + host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 7c52867bc5c3..a9a24f460274 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3235,6 +3235,10 @@ int sdhci_add_host(struct sdhci_host *host) if (override_timeout_clk) host->timeout_clk = override_timeout_clk; + + if (!(host->quirks2 & SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE)) + mmc->max_busy_timeout = (1 << 27) / host->timeout_clk; + mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23; mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 1329531767ec..c08ed7fac8df 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -436,6 +436,8 @@ struct sdhci_host { #define SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK (1<<19) +#define SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE (1<<20) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From a84208333511162c064d3adbbf8d8d126722ede5 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 21 Feb 2013 10:09:49 +0530 Subject: [PATCH 030/472] mmc: sdhci: Add check_power_status host operation MSM SDHCI doesn't control power as specified by the Standard Host Controller 3.0 spec. Writing to power control register/ reset register/voltage bit of host control register would trigger an IRQ with appropriate status bits set. Hence, use host op check_power_status after writing to power control register to check the status and wait until the IRQ is handled. Change-Id: Ied1a82e385547f7f5d60807fc896ea5a13084657 Signed-off-by: Sahitya Tummala [venkatg@codeaurora.org: fix trivial merge conflicts] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed minor merge conflict & compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 28 ++++++++++++++++++++++------ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a9a24f460274..54e61d4a4933 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -209,6 +209,10 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) /* Wait max 100 ms */ timeout = 100; + if (host->ops->check_power_status && host->pwr && + (mask & SDHCI_RESET_ALL)) + host->ops->check_power_status(host); + /* hw clears the bit when it's done */ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { if (timeout == 0) { @@ -1346,6 +1350,8 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, if (pwr == 0) { sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); + if (host->ops->check_power_status) + host->ops->check_power_status(host); if (host->quirks2 & SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON) sdhci_runtime_pm_bus_off(host); vdd = 0; @@ -1354,20 +1360,27 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, * Spec says that we should clear the power reg before setting * a new value. Some controllers don't seem to like this though. */ - if (!(host->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE)) + if (!(host->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE)) { sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); - + if (host->ops->check_power_status) + host->ops->check_power_status(host); + } /* * At least the Marvell CaFe chip gets confused if we set the * voltage and set turn on power at the same time, so set the * voltage first. */ - if (host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER) + if (host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER) { sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); + if (host->ops->check_power_status) + host->ops->check_power_status(host); + } pwr |= SDHCI_POWER_ON; sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); + if (host->ops->check_power_status) + host->ops->check_power_status(host); if (host->quirks2 & SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON) sdhci_runtime_pm_bus_on(host); @@ -1530,10 +1543,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) u8 ctrl; struct mmc_host *mmc = host->mmc; - spin_lock_irqsave(&host->lock, flags); - if (host->flags & SDHCI_DEVICE_DEAD) { - spin_unlock_irqrestore(&host->lock, flags); if (!IS_ERR(mmc->supply.vmmc) && ios->power_mode == MMC_POWER_OFF) mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); @@ -1569,6 +1579,8 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) if (ios->power_mode & (MMC_POWER_UP | MMC_POWER_ON)) sdhci_set_power(host, ios->power_mode, ios->vdd); + spin_lock_irqsave(&host->lock, flags); + if (host->ops->platform_send_init_74_clocks) host->ops->platform_send_init_74_clocks(host, ios->power_mode); @@ -1851,6 +1863,8 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, /* 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 (host->ops->check_power_status) + host->ops->check_power_status(host); if (!IS_ERR(mmc->supply.vqmmc)) { ret = regulator_set_voltage(mmc->supply.vqmmc, 2700000, @@ -1890,6 +1904,8 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, */ ctrl |= SDHCI_CTRL_VDD_180; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + if (host->ops->check_power_status) + host->ops->check_power_status(host); /* Some controller need to do more when switching */ if (host->ops->voltage_switch) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index c08ed7fac8df..5d19879237ce 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -567,6 +567,7 @@ struct sdhci_ops { void (*platform_init)(struct sdhci_host *host); void (*card_event)(struct sdhci_host *host); void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); + void (*check_power_status)(struct sdhci_host *host); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, struct mmc_card *card, From 18986103dcad122db816a0b4c34292a01dd3c278 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 20 Mar 2013 19:24:01 +0530 Subject: [PATCH 031/472] mmc: sdhci: Fix issues with power IRQ handling The SDHC core power control IRQ gets triggered when - * there is a state change in power control bit (bit 0) of SDHCI_POWER_CONTROL register. * there is a state change in 1.8V enable bit (bit 3) of SDHCI_HOST_CONTROL2 register. * Bit 1 of SDHCI_SOFTWARE_RESET is set. This patch addresses the following 2 issues - The reset state of 1.8V enable bit in SDHCI_HOST_CONTROL2 is 0 which indicates 3.3V IO voltage. So, when MMC core layer tries to set it to 3.3V before card detection, the IRQ doesn't get triggered as there is no state change in this bit. Hence, with the current code, the VDD IO voltage is never getting set to 3.3V. This patch fixes this issue by setting the VDD IO voltage to 3.3V whenever SDHC gets powered up. We get different IRQ ACK status for each of these requests - power on, power off, IO high, IO low. As of now, the driver is not considering the IRQ ACK for IO high and IO low requests and is returing prematurely from check_power_status() based on the previous ACK for power on/off requests. This is resulting voltage switch errors during voltage switch sequence for SD/eMMC cards. This issue is fixed by passing the request type to check_power_status host op so that driver can wait for its corresponding ACK from power IRQ. Change-Id: I07707ac5df731a0d3e4abead28076f0bbbf75c0a Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 81 ++++++++++++++++++++++++++---------- drivers/mmc/host/sdhci.c | 14 +++---- drivers/mmc/host/sdhci.h | 6 ++- 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c6f885895906..6fe66c936e5f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -231,7 +231,9 @@ struct sdhci_msm_host { struct sdhci_msm_pltfm_data *pdata; struct mmc_host *mmc; struct sdhci_pltfm_data sdhci_msm_pdata; - wait_queue_head_t pwr_irq_wait; + u32 curr_pwr_state; + u32 curr_io_level; + struct completion pwr_irq_completion; struct sdhci_msm_bus_vote msm_bus_vote; u32 clk_rate; /* Keeps track of current clock rate that is set */ }; @@ -1662,6 +1664,8 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) u8 irq_status = 0; u8 irq_ack = 0; int ret = 0; + int pwr_state = 0, io_level = 0; + unsigned long flags; irq_status = readb_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS); pr_debug("%s: Received IRQ(%d), status=0x%x\n", @@ -1680,21 +1684,33 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) /* Handle BUS ON/OFF*/ if (irq_status & CORE_PWRCTL_BUS_ON) { ret = sdhci_msm_setup_vreg(msm_host->pdata, true, false); - if (!ret) + if (!ret) { ret = sdhci_msm_setup_pins(msm_host->pdata, true); + ret |= sdhci_msm_set_vdd_io_vol(msm_host->pdata, + VDD_IO_HIGH, 0); + } if (ret) irq_ack |= CORE_PWRCTL_BUS_FAIL; else irq_ack |= CORE_PWRCTL_BUS_SUCCESS; + + pwr_state = REQ_BUS_ON; + io_level = REQ_IO_HIGH; } if (irq_status & CORE_PWRCTL_BUS_OFF) { ret = sdhci_msm_setup_vreg(msm_host->pdata, false, false); - if (!ret) + if (!ret) { ret = sdhci_msm_setup_pins(msm_host->pdata, false); + ret |= sdhci_msm_set_vdd_io_vol(msm_host->pdata, + VDD_IO_LOW, 0); + } if (ret) irq_ack |= CORE_PWRCTL_BUS_FAIL; else irq_ack |= CORE_PWRCTL_BUS_SUCCESS; + + pwr_state = REQ_BUS_OFF; + io_level = REQ_IO_LOW; } /* Handle IO LOW/HIGH */ if (irq_status & CORE_PWRCTL_IO_LOW) { @@ -1704,6 +1720,8 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) irq_ack |= CORE_PWRCTL_IO_FAIL; else irq_ack |= CORE_PWRCTL_IO_SUCCESS; + + io_level = REQ_IO_LOW; } if (irq_status & CORE_PWRCTL_IO_HIGH) { /* Switch voltage High */ @@ -1712,6 +1730,8 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) irq_ack |= CORE_PWRCTL_IO_FAIL; else irq_ack |= CORE_PWRCTL_IO_SUCCESS; + + io_level = REQ_IO_HIGH; } /* ACK status to the core */ @@ -1724,11 +1744,11 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) */ mb(); - if (irq_status & CORE_PWRCTL_IO_HIGH) + if (io_level & REQ_IO_HIGH) writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & ~CORE_IO_PAD_PWR_SWITCH), host->ioaddr + CORE_VENDOR_SPEC); - if (irq_status & CORE_PWRCTL_IO_LOW) + else if (io_level & REQ_IO_LOW) writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | CORE_IO_PAD_PWR_SWITCH), host->ioaddr + CORE_VENDOR_SPEC); @@ -1736,7 +1756,14 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) pr_debug("%s: Handled IRQ(%d), ret=%d, ack=0x%x\n", mmc_hostname(msm_host->mmc), irq, ret, irq_ack); - wake_up_interruptible(&msm_host->pwr_irq_wait); + spin_lock_irqsave(&host->lock, flags); + if (pwr_state) + msm_host->curr_pwr_state = pwr_state; + if (io_level) + msm_host->curr_io_level = io_level; + complete(&msm_host->pwr_irq_completion); + spin_unlock_irqrestore(&host->lock, flags); + return IRQ_HANDLED; } @@ -1770,25 +1797,36 @@ store_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr, return count; } -static void sdhci_msm_check_power_status(struct sdhci_host *host) +static void sdhci_msm_check_power_status(struct sdhci_host *host, u32 req_type) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; - int ret = 0; + unsigned long flags; + bool done = false; - pr_debug("%s: %s: power status before waiting 0x%x\n", - mmc_hostname(host->mmc), __func__, - readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL)); + spin_lock_irqsave(&host->lock, flags); + pr_debug("%s: %s: request %d curr_pwr_state %x curr_io_level %x\n", + mmc_hostname(host->mmc), __func__, req_type, + msm_host->curr_pwr_state, msm_host->curr_io_level); + if ((req_type & msm_host->curr_pwr_state) || + (req_type & msm_host->curr_io_level)) + done = true; + spin_unlock_irqrestore(&host->lock, flags); - ret = wait_event_interruptible(msm_host->pwr_irq_wait, - (readb_relaxed(msm_host->core_mem + - CORE_PWRCTL_CTL)) != 0x0); - if (ret) - pr_warning("%s: %s: returned due to error %d\n", - mmc_hostname(host->mmc), __func__, ret); - pr_debug("%s: %s: ret %d power status after handling power IRQ 0x%x\n", - mmc_hostname(host->mmc), __func__, ret, - readb_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL)); + /* + * This is needed here to hanlde a case where IRQ gets + * triggered even before this function is called so that + * x->done counter of completion gets reset. Otherwise, + * next call to wait_for_completion returns immediately + * without actually waiting for the IRQ to be handled. + */ + if (done) + init_completion(&msm_host->pwr_irq_completion); + else + wait_for_completion(&msm_host->pwr_irq_completion); + + pr_debug("%s: %s: request %d done\n", mmc_hostname(host->mmc), + __func__, req_type); } static void sdhci_msm_toggle_cdr(struct sdhci_host *host, bool enable) @@ -2017,7 +2055,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) ret = -ENOMEM; goto out; } - init_waitqueue_head(&msm_host->pwr_irq_wait); msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata); @@ -2200,6 +2237,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, sdhci_msm_bus_work); + init_completion(&msm_host->pwr_irq_completion); + if (gpio_is_valid(msm_host->pdata->status_gpio)) { ret = mmc_gpio_request_cd(msm_host->mmc, msm_host->pdata->status_gpio, 0); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 54e61d4a4933..204a5c070a18 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -211,7 +211,7 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) if (host->ops->check_power_status && host->pwr && (mask & SDHCI_RESET_ALL)) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_BUS_OFF); /* hw clears the bit when it's done */ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { @@ -1351,7 +1351,7 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, if (pwr == 0) { sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); if (host->ops->check_power_status) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_BUS_OFF); if (host->quirks2 & SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON) sdhci_runtime_pm_bus_off(host); vdd = 0; @@ -1363,7 +1363,7 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, if (!(host->quirks & SDHCI_QUIRK_SINGLE_POWER_WRITE)) { sdhci_writeb(host, 0, SDHCI_POWER_CONTROL); if (host->ops->check_power_status) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_BUS_OFF); } /* * At least the Marvell CaFe chip gets confused if we set the @@ -1373,14 +1373,14 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, if (host->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER) { sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); if (host->ops->check_power_status) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_BUS_ON); } pwr |= SDHCI_POWER_ON; sdhci_writeb(host, pwr, SDHCI_POWER_CONTROL); if (host->ops->check_power_status) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_BUS_ON); if (host->quirks2 & SDHCI_QUIRK2_CARD_ON_NEEDS_BUS_ON) sdhci_runtime_pm_bus_on(host); @@ -1864,7 +1864,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, ctrl &= ~SDHCI_CTRL_VDD_180; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); if (host->ops->check_power_status) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_IO_HIGH); if (!IS_ERR(mmc->supply.vqmmc)) { ret = regulator_set_voltage(mmc->supply.vqmmc, 2700000, @@ -1905,7 +1905,7 @@ static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, ctrl |= SDHCI_CTRL_VDD_180; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); if (host->ops->check_power_status) - host->ops->check_power_status(host); + host->ops->check_power_status(host, REQ_IO_LOW); /* Some controller need to do more when switching */ if (host->ops->voltage_switch) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 5d19879237ce..4d4765c3bfea 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -565,9 +565,13 @@ struct sdhci_ops { void (*hw_reset)(struct sdhci_host *host); void (*adma_workaround)(struct sdhci_host *host, u32 intmask); void (*platform_init)(struct sdhci_host *host); +#define REQ_BUS_OFF (1 << 0) +#define REQ_BUS_ON (1 << 1) +#define REQ_IO_LOW (1 << 2) +#define REQ_IO_HIGH (1 << 3) void (*card_event)(struct sdhci_host *host); void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); - void (*check_power_status)(struct sdhci_host *host); + void (*check_power_status)(struct sdhci_host *host, u32 req_type); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, struct mmc_card *card, From 5e38ca325072821fa875559b5df4df7c5df9d5b2 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 16 Apr 2013 18:06:06 +0530 Subject: [PATCH 032/472] mmc: sdhci: Fix issues with msm 3.9 kernel This patch fixes the following issues in sdhci driver from msm 3.9 kernel - 1. gpio_get_value_cansleep() is used from atomic context resulting in warning from gpio driver. Move it to non-atomic context. 2. Move sdhci_enable_preset_value() in set_ios callback after clocks are enabled otherwise it would result in access to SDHCI registers if clocks are disabled due to clock gating or suspend. Change-Id: I231aa6e5c02669cf1aa3f21764642fa7da9a01ff Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 204a5c070a18..2ff24f7f9e0a 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1453,8 +1453,22 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) return; } - /* Firstly check card presence */ + /* + * Firstly check card presence from cd-gpio. The return could + * be one of the following possibilities: + * negative: cd-gpio is not available + * zero: cd-gpio is used, and card is removed + * one: cd-gpio is used, and card is present + */ present = sdhci_do_get_cd(host); + if (present < 0) { + /* If polling, assume that the card is always present. */ + if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) + present = 1; + else + present = sdhci_readl(host, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT; + } spin_lock_irqsave(&host->lock, flags); From 45110a0fe81ebdc27d3fd797ae8ffdd0e5dfdd79 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 3 Apr 2013 18:03:31 +0530 Subject: [PATCH 033/472] mmc: sdhci-msm: Fix issue with MSM bus bandwidth voting The driver is using wrong clock rate to calculate the required bandwidth and due to this voting is happening for more bandwidth than it is required. This is ultimately preventing system core voltage from entering into low power mode. The sdhci_host clock indicates the clock rate as requested by MMC core layer and the actual rate that is set is indicated by clk_rate within struct sdhci_msm_host. As of now, sdhci_host clock is being used to calculate the bandwidth whereas bus-bw-vectors-bps indicates only supported bandwidths and hence a mismatch. Fix this by using the right clock rate which is clk_rate within struct sdhci_msm_host. Change-Id: If7d81e44a9b479c4c8e9fbaa7e092af2afb9cb9f Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 6fe66c936e5f..8636a4f12474 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1184,9 +1184,12 @@ out: static unsigned int sdhci_get_bw_required(struct sdhci_host *host, struct mmc_ios *ios) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + unsigned int bw; - bw = host->clock; + bw = msm_host->clk_rate; /* * For DDR mode, SDCC controller clock will be at * the double rate than the actual clock that goes to card. From eea2294326cb1ff44143fae9cc0cb483175b6100 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 8 Apr 2013 12:53:44 +0530 Subject: [PATCH 034/472] mmc: sdhci: Add timestamp debug info for data timeout error This helps check the controller timeout logic in case of data timeout errors. Change-Id: Ia30757192e49865698c5f52940e1dc5d97746185 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 7 +++++-- drivers/mmc/host/sdhci.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2ff24f7f9e0a..c12bd71d39a9 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1104,6 +1104,8 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) flags |= SDHCI_CMD_DATA; + if (cmd->data) + host->data_start_time = ktime_get(); sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); } EXPORT_SYMBOL_GPL(sdhci_send_command); @@ -2629,9 +2631,10 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) pr_msg = true; } if (pr_msg) { - pr_err("%s: data txfr (0x%08x) error: %d\n", + pr_err("%s: data txfr (0x%08x) error: %d after %lld ms\n", mmc_hostname(host->mmc), intmask, - host->data->error); + host->data->error, ktime_to_ms(ktime_sub( + ktime_get(), host->data_start_time))); sdhci_dumpregs(host); } sdhci_finish_data(host); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 4d4765c3bfea..46bee820fb91 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -532,6 +532,7 @@ struct sdhci_host { #define SDHCI_TUNING_MODE_1 0 unsigned int cpu_dma_latency_us; struct pm_qos_request pm_qos_req_dma; + ktime_t data_start_time; unsigned long private[0] ____cacheline_aligned; }; From 61b4aff312c9c4d483b3355ec7e4f29c17d56f9c Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 12 Apr 2013 11:49:11 +0530 Subject: [PATCH 035/472] mmc: sdhci-msm: Ignore data timeout error for R1B commands Ignore data timeout error for R1B commands as there will be no data associated and the busy timeout value for these commands could be lager than the maximum timeout value that controller can handle. CRs-fixed: 473435 Change-Id: I61f7463cf09648ad9fab83437abf5004effc7758 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 + drivers/mmc/host/sdhci.c | 3 +++ drivers/mmc/host/sdhci.h | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 8636a4f12474..4759927abff4 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2173,6 +2173,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; host->quirks2 |= SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING; host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; + host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index c12bd71d39a9..c17ac505bc2e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2594,6 +2594,9 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) host->busy_handle = 1; return; } + if (host->quirks2 & + SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD) + return; } pr_err("%s: Got data interrupt 0x%08x even " diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 46bee820fb91..1af827f3556e 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -438,6 +438,14 @@ struct sdhci_host { #define SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE (1<<20) +/* + * Ignore data timeout error for R1B commands as there will be no + * data associated and the busy timeout value for these commands + * could be lager than the maximum timeout value that controller + * can handle. + */ +#define SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD (1<<21) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From 3ee34aae161d82aefe775805a8ce66caffbe5569 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 12 Apr 2013 12:11:20 +0530 Subject: [PATCH 036/472] mmc: sdhci-msm: Do not enable preset value If preset value (bit 15) is enabled in sdhci host control2 register (0x3E), then the preset value registers(0x6F-0x60) would be used for some of the settings such as clock and drive strength. These are HW initialized registers and are not properly initialized by MSM SDHCI controller. This is resulting in low throughput for some of the modes such as DDR50/SDR50. Hence, do not enable it for MSM SDHCI. CRs-fixed: 474518 Change-Id: Iee1241355d14e6bcebc66c3a43abf1ec94d869c3 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 + drivers/mmc/host/sdhci.c | 3 +++ drivers/mmc/host/sdhci.h | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4759927abff4..ab15b402f3b2 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2174,6 +2174,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING; host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; + host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index c17ac505bc2e..e7b3ea1807e7 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2208,6 +2208,9 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable) if (host->version < SDHCI_SPEC_300) return; + if (host->quirks2 & SDHCI_QUIRK2_BROKEN_PRESET_VALUE) + return; + /* * We only enable or disable Preset Value if they are not already * enabled or disabled respectively. Otherwise, we bail out. diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 1af827f3556e..1b5ec41e1957 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -446,6 +446,13 @@ struct sdhci_host { */ #define SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD (1<<21) +/* + * The preset value registers are not properly initialized by + * some hardware and hence preset value must not be enabled for + * such controllers. + */ +#define SDHCI_QUIRK2_BROKEN_PRESET_VALUE (1<<22) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From 6a7d2df0b0fa6daf46e6b9c6deaa441494d705ae Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 12 Apr 2013 11:59:25 +0530 Subject: [PATCH 037/472] mmc: sdhci-msm: Use maximum possible data timeout value The MSM SDHCI controller defines the usage of 0xF in data timeout counter register (0x2E) which is actually a reserved bit as per specification. This would result in maximum of 5.3 secs timeout value. For some buggy SD cards, the core layer wants to set the data timeout to 3 secs and on our MSM SDHCI if we configure data timeout counter value to 0xE, then we would get only 2.67 secs. Observed data timeout errors for CMD25 on SDR104 card. Hence program data timeout counter to 0xF, to achieve at least 3 secs timeout value. CRs-fixed: 470661 Change-Id: Ie1e192eb9c38ca3922bb1f518073a8ff0cb57f0c Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 + drivers/mmc/host/sdhci.c | 10 ++++++---- drivers/mmc/host/sdhci.h | 6 ++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ab15b402f3b2..0d2ce449b139 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2175,6 +2175,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; + host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT; host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index e7b3ea1807e7..0179edd2186f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -711,10 +711,12 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) break; } - if (count >= 0xF) { - DBG("%s: Too large timeout 0x%x requested for CMD%d!\n", - mmc_hostname(host->mmc), count, cmd->opcode); - count = 0xE; + if (!(host->quirks2 & SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT)) { + if (count >= 0xF) { + DBG("%s: Too large timeout 0x%x requested for CMD%d!\n", + mmc_hostname(host->mmc), count, cmd->opcode); + count = 0xE; + } } return count; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 1b5ec41e1957..4335837f7c17 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -452,6 +452,12 @@ struct sdhci_host { * such controllers. */ #define SDHCI_QUIRK2_BROKEN_PRESET_VALUE (1<<22) +/* + * Some controllers define the usage of 0xF in data timeout counter + * register (0x2E) which is actually a reserved bit as per + * specification. + */ +#define SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT (1<<23) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From 2f5949b9d2b8a852870c4b26ff84224d75fb46ad Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Wed, 24 Apr 2013 14:19:46 -0700 Subject: [PATCH 038/472] mmc: sdhci-msm: Read version register properly The version register is only 16 bits wide but we use a readl to read it. Normally this wouldn't be a problem, but the register offset is 0xfe, something that is not word aligned. This causes crashes on THUMB2 kernels. Use readw instead to read the register properly and avoid any alignment issues. Change-Id: I3b8b14ce2f741631ef7554e3763d1d7f145077a8 Signed-off-by: Stephen Boyd --- drivers/mmc/host/sdhci-msm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 0d2ce449b139..4c8631d5e3dc 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2049,7 +2049,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) struct sdhci_msm_host *msm_host; struct resource *core_memres = NULL; int ret = 0, pwr_irq = 0, dead = 0; - u32 host_version; + u16 host_version; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -2177,7 +2177,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT; - host_version = readl_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); + host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> SDHCI_VENDOR_VER_SHIFT)); From f957bdbd793ed262bf43a2a3059ff75f17b89c83 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 25 Apr 2013 11:50:56 +0530 Subject: [PATCH 039/472] mmc: sdhci-msm: wait for SW reset to be complete Wait for SW reset to be complete before proceeding further in probe. Otherwise, any register writes immediately after the reset would be ignored/reset. Change-Id: If1c7f5debfca6f45a0fdb08bc759ad04b96fd86c Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4c8631d5e3dc..ea8614707ca5 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2050,6 +2050,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) struct resource *core_memres = NULL; int ret = 0, pwr_irq = 0, dead = 0; u16 host_version; + u32 pwr; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -2158,7 +2159,21 @@ static int sdhci_msm_probe(struct platform_device *pdev) } /* Set SW_RST bit in POWER register (Offset 0x0) */ - writel_relaxed(CORE_SW_RST, msm_host->core_mem + CORE_POWER); + writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | + CORE_SW_RST, msm_host->core_mem + CORE_POWER); + /* + * SW reset can take upto 10HCLK + 15MCLK cycles. + * Calculating based on min clk rates (hclk = 27MHz, + * mclk = 400KHz) it comes to ~40us. Let's poll for + * max. 1ms for reset completion. + */ + ret = readl_poll_timeout(msm_host->core_mem + CORE_POWER, + pwr, !(pwr & CORE_SW_RST), 100, 10); + + if (ret) { + dev_err(&pdev->dev, "reset failed (%d)\n", ret); + goto vreg_deinit; + } /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); From cecd05aeb2f39469e505a61235a9e566f1a32a42 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 14 May 2013 17:46:43 +0530 Subject: [PATCH 040/472] mmc: sdhci-msm: fix issue with power irq SDCC controller reset (SW_RST) during probe may trigger power irq if previous status of PWRCTL was either BUS_ON or IO_HIGH_V. So before we enable the power irq interrupt in GIC (by registering the interrupt handler), we need to ensure that any pending power irq interrupt status is acknowledged otherwise power irq interrupt handler would be fired prematurely. CRs-Fixed: 487962 Change-Id: If4693869210bc8b361dadb2b68a47b6ac8707e0f Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ea8614707ca5..11ecd98fcbb1 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2050,7 +2050,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) struct resource *core_memres = NULL; int ret = 0, pwr_irq = 0, dead = 0; u16 host_version; - u32 pwr; + u32 pwr, irq_status, irq_ctl; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -2177,6 +2177,27 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + /* + * CORE_SW_RST above may trigger power irq if previous status of PWRCTL + * was either BUS_ON or IO_HIGH_V. So before we enable the power irq + * interrupt in GIC (by registering the interrupt handler), we need to + * ensure that any pending power irq interrupt status is acknowledged + * otherwise power irq interrupt handler would be fired prematurely. + */ + irq_status = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS); + writel_relaxed(irq_status, (msm_host->core_mem + CORE_PWRCTL_CLEAR)); + irq_ctl = readl_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL); + if (irq_status & (CORE_PWRCTL_BUS_ON | CORE_PWRCTL_BUS_OFF)) + irq_ctl |= CORE_PWRCTL_BUS_SUCCESS; + if (irq_status & (CORE_PWRCTL_IO_HIGH | CORE_PWRCTL_IO_LOW)) + irq_ctl |= CORE_PWRCTL_IO_SUCCESS; + writel_relaxed(irq_ctl, (msm_host->core_mem + CORE_PWRCTL_CTL)); + /* + * Ensure that above writes are propogated before interrupt enablement + * in GIC. + */ + mb(); + /* * Following are the deviations from SDHC spec v3.0 - * 1. Card detection is handled using separate GPIO. From 27212a9a828eacf9380fec5b8352676328af126d Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 24 May 2013 14:08:10 +0530 Subject: [PATCH 041/472] mmc: sdhci: Fix sdhci_led_control() path The SDHC driver registers sdhci_led_control() with LED class device and it gets invoked when the sysfs entry - /sys/class/leds/mmcX:: is updated. This function access SDHC Host control register (0x28) and hence, check the driver state (runtime suspended/clocks gated) before accessing it. Otherwise, it may result in unclocked access resulting in system failure. CRs-fixed: 480596 Change-Id: Icef51f02abb54316710df30429fec875030d42d9 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0179edd2186f..176cc39e6dd5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -56,6 +56,7 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); static int sdhci_pre_dma_transfer(struct sdhci_host *host, struct mmc_data *data); static int sdhci_do_get_cd(struct sdhci_host *host); +static bool sdhci_check_state(struct sdhci_host *); #ifdef CONFIG_PM static int sdhci_runtime_pm_get(struct sdhci_host *host); @@ -305,7 +306,7 @@ static void sdhci_led_control(struct led_classdev *led, spin_lock_irqsave(&host->lock, flags); - if (host->runtime_suspended) + if (host->runtime_suspended || sdhci_check_state(host)) goto out; if (brightness == LED_OFF) From 165bd22b550bdf0353299045c562fd675bc4a8b7 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 23 May 2013 15:59:22 +0530 Subject: [PATCH 042/472] mmc: sdhci-msm: Vote for MSM bus clocks before enabling iface_clk The current driver just enables "iface_clk" before accessing its registers but MSM bus clocks are also required for register access without which any register access would result in chip reset. The MSM bus clocks can be enabled by setting vote to MSM bus bandwidth driver. Currently, voting is being done in sdhci_enable/disable but these functions will not be invoked by MMC core layer for some cases such as mmc_power_up/mmc_power_off, which require peripheral register access. To resolve the above mentioned problem, bus voting and de-voting will now be done as part of clock management within the sdhci MSM driver i.e., before enabling SDHC clocks and after disabling SDHC clocks. Change-Id: Iff608fba4c58bf37a6f4ce8eb36876c79969feaf Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 59 +++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 11ecd98fcbb1..572f14676170 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1389,10 +1389,20 @@ static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable) return; bw = sdhci_get_bw_required(host, ios); - if (enable) + if (enable) { sdhci_msm_bus_cancel_work_and_set_vote(host, bw); - else - sdhci_msm_bus_queue_work(host); + } else { + /* + * If clock gating is enabled, then remove the vote + * immediately because clocks will be disabled only + * after SDHCI_MSM_MMC_CLK_GATE_DELAY and thus no + * additional delay is required to remove the bus vote. + */ + if (host->mmc->clkgate_delay) + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + else + sdhci_msm_bus_queue_work(host); + } } /* Regulator utility functions */ @@ -1901,12 +1911,15 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) if (enable && !atomic_read(&msm_host->clks_on)) { pr_debug("%s: request to enable clocks\n", mmc_hostname(host->mmc)); + + sdhci_msm_bus_voting(host, 1); + if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { rc = clk_prepare_enable(msm_host->bus_clk); if (rc) { pr_err("%s: %s: failed to enable the bus-clock with error %d\n", mmc_hostname(host->mmc), __func__, rc); - goto out; + goto remove_vote; } } if (!IS_ERR(msm_host->pclk)) { @@ -1935,6 +1948,8 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) clk_disable_unprepare(msm_host->pclk); if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); + + sdhci_msm_bus_voting(host, 0); } atomic_set(&msm_host->clks_on, enable); goto out; @@ -1944,6 +1959,9 @@ disable_pclk: disable_bus_clk: if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); +remove_vote: + if (msm_host->msm_bus_vote.client_handle) + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); out: return rc; } @@ -1990,6 +2008,11 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) } msm_host->clk_rate = sup_clock; host->clock = clock; + /* + * Update the bus vote in case of frequency change due to + * clock scaling. + */ + sdhci_msm_bus_voting(host, 1); } out: sdhci_set_clock(host, clock); @@ -2037,7 +2060,6 @@ static struct sdhci_ops sdhci_msm_ops = { .toggle_cdr = sdhci_msm_toggle_cdr, .get_max_segments = sdhci_msm_max_segs, .set_clock = sdhci_msm_set_clock, - .platform_bus_voting = sdhci_msm_bus_voting, .get_min_clock = sdhci_msm_get_min_clock, .get_max_clock = sdhci_msm_get_max_clock, }; @@ -2139,11 +2161,20 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->clk_rate = sdhci_msm_get_min_clock(host); atomic_set(&msm_host->clks_on, 1); + ret = sdhci_msm_bus_register(msm_host, pdev); + if (ret) + goto clk_disable; + + if (msm_host->msm_bus_vote.client_handle) + INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, + sdhci_msm_bus_work); + sdhci_msm_bus_voting(host, 1); + /* Setup regulators */ ret = sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, true); if (ret) { dev_err(&pdev->dev, "Regulator setup failed (%d)\n", ret); - goto clk_disable; + goto bus_unregister; } /* Reset the core and Enable SDHC mode */ @@ -2271,14 +2302,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; - ret = sdhci_msm_bus_register(msm_host, pdev); - if (ret) - goto vreg_deinit; - - if (msm_host->msm_bus_vote.client_handle) - INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, - sdhci_msm_bus_work); - init_completion(&msm_host->pwr_irq_completion); if (gpio_is_valid(msm_host->pdata->status_gpio)) { @@ -2287,7 +2310,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) { dev_err(&pdev->dev, "%s: Failed to request card detection IRQ %d\n", __func__, ret); - goto bus_unregister; + goto vreg_deinit; } } @@ -2323,10 +2346,12 @@ remove_host: free_cd_gpio: if (gpio_is_valid(msm_host->pdata->status_gpio)) mmc_gpio_free_cd(msm_host->mmc); -bus_unregister: - sdhci_msm_bus_unregister(msm_host); vreg_deinit: sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); +bus_unregister: + if (msm_host->msm_bus_vote.client_handle) + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + sdhci_msm_bus_unregister(msm_host); clk_disable: if (!IS_ERR(msm_host->clk)) clk_disable_unprepare(msm_host->clk); From b7fc1448ada0b061f94ccc341a54a44085e254c5 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Mon, 3 Jun 2013 09:54:32 +0530 Subject: [PATCH 043/472] mmc: sdhci-msm: Initialize card-detect polarity Enable MMC_CAP2_CD_ACTIVE_HIGH capability if the card-detect gpio polarity is active high. Change-Id: I80e869dd7ecb6e24e81d1cc73ef8101c44110873 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/host/sdhci-msm.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 572f14676170..d36221fb30fa 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1090,6 +1090,7 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) int len, i; int clk_table_len; u32 *clk_table = NULL; + enum of_gpio_flags flags = OF_GPIO_ACTIVE_LOW; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { @@ -1097,7 +1098,9 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) goto out; } - pdata->status_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, 0); + pdata->status_gpio = of_get_named_gpio_flags(np, "cd-gpios", 0, &flags); + if (gpio_is_valid(pdata->status_gpio) & !(flags & OF_GPIO_ACTIVE_LOW)) + pdata->caps2 |= MMC_CAP2_CD_ACTIVE_HIGH; of_property_read_u32(np, "qcom,bus-width", &bus_width); if (bus_width == 8) From 5af7b3e981b3211eb80652aa1bcc224a43582888 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 7 Jun 2013 13:03:07 +0530 Subject: [PATCH 044/472] mmc: sdhci-msm: set the clock rate before enabling it The drivers must do clk_set_rate() before the first clk_prepare_enable() is invoked. Otherwise, the clock driver may give a warning for such clocks. CRs-fixed: 493685 Change-Id: I8342aa2f35d64a4dc7b8396bd9741c0aecaedc5c Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d36221fb30fa..b76bc6b617c8 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2151,16 +2151,16 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto pclk_disable; } - ret = clk_prepare_enable(msm_host->clk); - if (ret) - goto pclk_disable; - /* Set to the minimum supported clock frequency */ ret = clk_set_rate(msm_host->clk, sdhci_msm_get_min_clock(host)); if (ret) { dev_err(&pdev->dev, "MClk rate set failed (%d)\n", ret); - goto clk_disable; + goto pclk_disable; } + ret = clk_prepare_enable(msm_host->clk); + if (ret) + goto pclk_disable; + msm_host->clk_rate = sdhci_msm_get_min_clock(host); atomic_set(&msm_host->clks_on, 1); From cec2ab9ba3a05f03fa390c66cc77ef0e8ef36529 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 28 May 2013 18:21:57 +0530 Subject: [PATCH 045/472] mmc: sdhci-msm: fix pwrsave bit handling SDCC controller provides the PWRSAVE control bit to automatically disable the clock to card when there is no activity with card. During the SDCC DLL tuning, PWRSAVE is disabled to make sure that clock is always running but once the DLL tuning is completed, currently we enable the PWRSAVE unconditionally irrespective of its previous state. This change ensures that we always check if the previous state of pwrsave before really enabling it. Change-Id: I464ab1e0db41af50550bb5a9ea9b909ee0d27dd9 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b76bc6b617c8..d08a2bb718d5 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -483,19 +483,25 @@ static int msm_init_cm_dll(struct sdhci_host *host) int rc = 0; unsigned long flags; u32 wait_cnt; + bool prev_pwrsave, curr_pwrsave; pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); spin_lock_irqsave(&host->lock, flags); - + prev_pwrsave = !!(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & + CORE_CLK_PWRSAVE); + curr_pwrsave = prev_pwrsave; /* * Make sure that clock is always enabled when DLL * tuning is in progress. Keeping PWRSAVE ON may * turn off the clock. So let's disable the PWRSAVE * here and re-enable it once tuning is completed. */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) - & ~CORE_CLK_PWRSAVE), - host->ioaddr + CORE_VENDOR_SPEC); + if (prev_pwrsave) { + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_CLK_PWRSAVE), + host->ioaddr + CORE_VENDOR_SPEC); + curr_pwrsave = false; + } /* Write 1 to DLL_RST bit of DLL_CONFIG register */ writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) @@ -538,10 +544,18 @@ static int msm_init_cm_dll(struct sdhci_host *host) } out: - /* re-enable PWRSAVE */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | - CORE_CLK_PWRSAVE), - host->ioaddr + CORE_VENDOR_SPEC); + /* Restore the correct PWRSAVE state */ + if (prev_pwrsave ^ curr_pwrsave) { + u32 reg = readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC); + + if (prev_pwrsave) + reg |= CORE_CLK_PWRSAVE; + else + reg &= ~CORE_CLK_PWRSAVE; + + writel_relaxed(reg, host->ioaddr + CORE_VENDOR_SPEC); + } + spin_unlock_irqrestore(&host->lock, flags); pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); return rc; From b2f263c3ea2f0dd31343b10b6df5b46f0cddb7f4 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 29 May 2013 15:52:10 +0530 Subject: [PATCH 046/472] mmc: sdhci-msm: enable asynchronous interrupt support in 4-bit mode SDIO 3.0 specification has added the support for asynchronous interrupt period during which card allows the clock to be gated off. As SDCC driver is capable of handling the asynchronous interrupt, advertise MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE capability. Change-Id: Id5a86bc70b7b798b23be3a0fc0d59b2db05e0409 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d08a2bb718d5..ed64d1846d61 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2313,6 +2313,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL; msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; + msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; From a467484ee963486ba9b527c6ca2dafd0f3b34eda Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 10 Jun 2013 16:32:51 +0530 Subject: [PATCH 047/472] mmc: sdhci-msm: calculate timeout value based on the base clock The driver currently uses fixed timeout value from capabilities register (bit 5-0) to calculate the timeout which is advertized as 50MHz. But the driver uses SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK and controls the base clock (MCLK) directly. So during card initialization, the frequency would be 400KHz but still timeout is calculated at 50MHz which is wrong. This patch fixes this by using the current base clock frequency to calculate the timeout. The controller internally multiplies the timeout control register value by 4 with the assumption that driver always uses fixed timeout clock value from capabilities register. Add a quirk SDHCI_QUIRK2_DIVIDE_TOUT_BY_4 to avoid this multiplicaiton in case base clock is used for timeout calculation. CRs-fixed: 498159 Change-Id: I503fd16132bf17e590239997d6970b9b730d4202 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 3 +++ drivers/mmc/host/sdhci.c | 10 +++++++++- drivers/mmc/host/sdhci.h | 13 +++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ed64d1846d61..9cac755d36e2 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2261,6 +2261,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT; + if (host->quirks2 & SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK) + host->quirks2 |= SDHCI_QUIRK2_DIVIDE_TOUT_BY_4; + host_version = readw_relaxed((host->ioaddr + SDHCI_HOST_VERSION)); dev_dbg(&pdev->dev, "Host Version: 0x%x Vendor Version 0x%x\n", host_version, ((host_version & SDHCI_VENDOR_VER_MASK) >> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 176cc39e6dd5..de0bcb9149d2 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -670,6 +670,7 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) u8 count; struct mmc_data *data = cmd->data; unsigned target_timeout, current_timeout; + u32 curr_clk = 0; /* In KHz */ /* * If the host controller provides us with an incorrect timeout @@ -704,7 +705,14 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) * (1) / (2) > 2^6 */ count = 0; - current_timeout = (1 << 13) * 1000 / host->timeout_clk; + if (host->quirks2 & SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK) { + curr_clk = host->clock / 1000; + if (host->quirks2 & SDHCI_QUIRK2_DIVIDE_TOUT_BY_4) + curr_clk /= 4; + current_timeout = (1 << 13) * 1000 / curr_clk; + } else { + current_timeout = (1 << 13) * 1000 / host->timeout_clk; + } while (current_timeout < target_timeout) { count++; current_timeout <<= 1; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 4335837f7c17..1a39c46439c5 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -458,6 +458,19 @@ struct sdhci_host { * specification. */ #define SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT (1<<23) +/* + * This is applicable for controllers that advertize timeout clock + * value in capabilities register (bit 5-0) as just 50MHz whereas the + * base clock frequency is 200MHz. So, the controller internally + * multiplies the value in timeout control register by 4 with the + * assumption that driver always uses fixed timeout clock value from + * capabilities register to calculate the timeout. But when the driver + * uses SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK base clock frequency is directly + * controller by driver and it's rate varies upto max. 200MHz. This new quirk + * will be used in such cases to avoid controller mulplication when timeout is + * calculated based on the base clock. + */ +#define SDHCI_QUIRK2_DIVIDE_TOUT_BY_4 (1 << 23) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From ccbf0292799ead6e17cc175fe40fffecba435a47 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 12 Jun 2013 18:25:26 +0530 Subject: [PATCH 048/472] mmc: sdhci-msm: remove MMC_CAP_HW_RESET capability MMC_CAP_HW_RESET capability was only referring to host driver capability to toggle eMMC RST_n pin so if the host driver is unable to toggle this pin, it shouldn't advertise this capability. CRs-Fixed: 507926 Change-Id: Ia1408d95503d19ae0f7c49c7bb7905b0ddaddbd5 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9cac755d36e2..b0af5d493898 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2307,7 +2307,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; - msm_host->mmc->caps |= MMC_CAP_HW_RESET; msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; From 7bb14bf282fce0829d110dfc05f215f6ad69b7cd Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 20 Jun 2013 14:00:18 +0530 Subject: [PATCH 049/472] mmc: sdhci-msm: Add polling sysfs entry Add support for polling by providing sysfs entry. It can be enabled/disabled, by writing 1/0 respectively to the sysfs node - sys/bus/platform/devices/msm_sdcc./polling. The polling will be available only if hardware based card detection is not supported. Change-Id: Ic58c36665e23cb921d76c482494a168289e83b83 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b0af5d493898..2c71f9cf563b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -235,6 +235,7 @@ struct sdhci_msm_host { u32 curr_io_level; struct completion pwr_irq_completion; struct sdhci_msm_bus_vote msm_bus_vote; + struct device_attribute polling; u32 clk_rate; /* Keeps track of current clock rate that is set */ }; @@ -1797,6 +1798,41 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) return IRQ_HANDLED; } +static ssize_t +show_polling(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + int poll; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + poll = !!(host->mmc->caps & MMC_CAP_NEEDS_POLL); + spin_unlock_irqrestore(&host->lock, flags); + + return snprintf(buf, PAGE_SIZE, "%d\n", poll); +} + +static ssize_t +store_polling(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + int value; + unsigned long flags; + + if (!kstrtou32(buf, 0, &value)) { + spin_lock_irqsave(&host->lock, flags); + if (value) { + host->mmc->caps |= MMC_CAP_NEEDS_POLL; + mmc_detect_change(host->mmc, 0); + } else { + host->mmc->caps &= ~MMC_CAP_NEEDS_POLL; + } + spin_unlock_irqrestore(&host->lock, flags); + } + return count; +} + static ssize_t show_sdhci_max_bus_bw(struct device *dev, struct device_attribute *attr, char *buf) @@ -2357,9 +2393,21 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) goto remove_host; + if (!gpio_is_valid(msm_host->pdata->status_gpio)) { + msm_host->polling.show = show_polling; + msm_host->polling.store = store_polling; + sysfs_attr_init(&msm_host->polling.attr); + msm_host->polling.attr.name = "polling"; + msm_host->polling.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(&pdev->dev, &msm_host->polling); + if (ret) + goto remove_max_bus_bw_file; + } /* Successful initialization */ goto out; +remove_max_bus_bw_file: + device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); remove_host: dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); sdhci_remove_host(host, dead); @@ -2398,6 +2446,8 @@ static int sdhci_msm_remove(struct platform_device *pdev) 0xffffffff); pr_debug("%s: %s\n", dev_name(&pdev->dev), __func__); + if (!gpio_is_valid(msm_host->pdata->status_gpio)) + device_remove_file(&pdev->dev, &msm_host->polling); device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); From 573858a3b64167f27a2968dbebf661007b324439 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 13 Jun 2013 10:36:57 +0530 Subject: [PATCH 050/472] mmc: sdhci-msm: Add retry mechanism in case of tuning failure The specification indicates that the tuning process is normally shorter than 40 exections of tuning command. Hence, retry the tuning sequence for at least 3 times before returning the error. Change-Id: I21724a73af7b997e128b56a2600bdcb12e414996 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 2c71f9cf563b..08558b511af0 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -565,6 +565,7 @@ out: int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) { unsigned long flags; + int tuning_seq_cnt = 3; u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0; const u32 *tuning_block_pattern = tuning_block_64; int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */ @@ -591,17 +592,18 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) } spin_unlock_irqrestore(&host->lock, flags); - /* first of all reset the tuning block */ - rc = msm_init_cm_dll(host); - if (rc) - goto out; - data_buf = kmalloc(size, GFP_KERNEL); if (!data_buf) { rc = -ENOMEM; goto out; } +retry: + /* first of all reset the tuning block */ + rc = msm_init_cm_dll(host); + if (rc) + goto kfree; + phase = 0; do { struct mmc_command cmd = {0}; @@ -658,10 +660,12 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) pr_debug("%s: %s: finally setting the tuning phase to %d\n", mmc_hostname(mmc), __func__, phase); } else { + if (--tuning_seq_cnt) + goto retry; /* tuning failed */ pr_err("%s: %s: no tuning point found\n", mmc_hostname(mmc), __func__); - rc = -EAGAIN; + rc = -EIO; } kfree: From 0c196acfcbb766a07151cfae76c2318eca661c40 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 19 Jun 2013 20:15:37 +0530 Subject: [PATCH 051/472] mmc: sdhci: Defer release of CPU DMA PM QoS vote in high load cases PM QoS vote of default value mean that the CPU is allowed to move into deepest low power mode whenever possible. Currently, if there are back-to-back MMC requests, with a short delay, the PM QoS vote to default value is done immediately which cause the immediate request to have high latency as the CPU might have idle'd and moved to deepest low power mode. To avoid this defer the PM QoS vote till a defined timeout (pm_qos_timeout_us), so that back-to-back requests may not suffer from additional latencies. In addition, if the load on MMC is low, the additional latency may be sustainable. Hence, aggressively release the vote in order to achieve additional power savings. CRs-Fixed: 501712 Change-Id: I82166b0ce9416eb0d519f7da26e5a96956093cb2 Signed-off-by: Sujit Reddy Thumma [subhashj@codeaurora.org: fixed minor merge conflict and fixed compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 49 +++++++++++++++++++++++++++++++++++++--- drivers/mmc/host/sdhci.h | 8 +++++++ include/linux/mmc/host.h | 7 ++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index de0bcb9149d2..f9f22a2fe0dd 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1430,15 +1430,55 @@ static int sdhci_disable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); - if (host->cpu_dma_latency_us) - pm_qos_update_request(&host->pm_qos_req_dma, + if (host->cpu_dma_latency_us) { + /* + * In performance mode, release QoS vote after a timeout to + * make sure back-to-back requests don't suffer from latencies + * that are involved to wake CPU from low power modes in cases + * where the CPU goes into low power mode as soon as QoS vote is + * released. + */ + if (host->power_policy == SDHCI_PERFORMANCE_MODE) + pm_qos_update_request_timeout(&host->pm_qos_req_dma, + host->cpu_dma_latency_us, + host->pm_qos_timeout_us); + else + pm_qos_update_request(&host->pm_qos_req_dma, PM_QOS_DEFAULT_VALUE); + } + if (host->ops->platform_bus_voting) host->ops->platform_bus_voting(host, 0); return 0; } +static inline void sdhci_update_power_policy(struct sdhci_host *host, + enum sdhci_power_policy policy) +{ + host->power_policy = policy; +} + +static int sdhci_notify_load(struct mmc_host *mmc, enum mmc_load state) +{ + int err = 0; + struct sdhci_host *host = mmc_priv(mmc); + + switch (state) { + case MMC_LOAD_HIGH: + sdhci_update_power_policy(host, SDHCI_PERFORMANCE_MODE); + break; + case MMC_LOAD_LOW: + sdhci_update_power_policy(host, SDHCI_POWER_SAVE_MODE); + break; + default: + err = -EINVAL; + break; + } + + return err; +} + static bool sdhci_check_state(struct sdhci_host *host) { if (!host->clock || !host->pwr) @@ -2345,6 +2385,7 @@ static const struct mmc_host_ops sdhci_ops = { .card_busy = sdhci_card_busy, .enable = sdhci_enable, .disable = sdhci_disable, + .notify_load = sdhci_notify_load, }; /*****************************************************************************\ @@ -3588,9 +3629,11 @@ int sdhci_add_host(struct sdhci_host *host) mmiowb(); - if (host->cpu_dma_latency_us) + if (host->cpu_dma_latency_us) { + host->pm_qos_timeout_us = 10000; /* default value */ pm_qos_add_request(&host->pm_qos_req_dma, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + } mmc_add_host(mmc); pr_info("%s: SDHCI controller on %s [%s] using %s\n", diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 1a39c46439c5..8aa0f4e077de 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -316,6 +316,11 @@ enum sdhci_cookie { COOKIE_GIVEN, }; +enum sdhci_power_policy { + SDHCI_PERFORMANCE_MODE, + SDHCI_POWER_SAVE_MODE, +}; + struct sdhci_host { /* Data set by hardware interface driver */ const char *hw_name; /* Hardware bus name */ @@ -568,6 +573,9 @@ struct sdhci_host { struct pm_qos_request pm_qos_req_dma; ktime_t data_start_time; + unsigned int pm_qos_timeout_us; /* timeout for PM QoS request */ + enum sdhci_power_policy power_policy; + unsigned long private[0] ____cacheline_aligned; }; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 2b6159a85a15..1939cd47b3aa 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -79,6 +79,12 @@ struct mmc_ios { #define MMC_SET_DRIVER_TYPE_D 3 }; +/* states to represent load on the host */ +enum mmc_load { + MMC_LOAD_HIGH, + MMC_LOAD_LOW, +}; + struct mmc_host_ops { /* * 'enable' is called when the host is claimed and 'disable' is called @@ -150,6 +156,7 @@ struct mmc_host_ops { */ int (*multi_io_quirk)(struct mmc_card *card, unsigned int direction, int blk_size); + int (*notify_load)(struct mmc_host *, enum mmc_load); }; struct mmc_card; From 9102d4a526fdb62699e2c68df1b15d48b7950e66 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 19 Jun 2013 20:25:38 +0530 Subject: [PATCH 052/472] mmc: sdhci: Provide sysfs attributes to tune PM QoS unvote timeout Provide sysfs tunables to defer PM QoS vote of default value so that back-to-back requests wouldn't suffer from latencies caused by CPU power collapse transition states. Change-Id: I7180c68c1f13240faa5f432335d72e7f6b198183 Signed-off-by: Sujit Reddy Thumma [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 38 ++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 40 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index f9f22a2fe0dd..436cf467ff9d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -156,6 +156,33 @@ static void sdhci_dumpregs(struct sdhci_host *host) pr_info(DRIVER_NAME ": ===========================================\n"); } +#define MAX_PM_QOS_TIMEOUT_VALUE 100000 /* 100 ms */ +static ssize_t +show_sdhci_pm_qos_tout(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d us\n", host->pm_qos_timeout_us); +} + +static ssize_t +store_sdhci_pm_qos_tout(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + uint32_t value; + unsigned long flags; + + if (!kstrtou32(buf, 0, &value)) { + spin_lock_irqsave(&host->lock, flags); + if (value <= MAX_PM_QOS_TIMEOUT_VALUE) + host->pm_qos_timeout_us = value; + spin_unlock_irqrestore(&host->lock, flags); + } + return count; +} + /*****************************************************************************\ * * * Low level functions * @@ -3633,7 +3660,18 @@ int sdhci_add_host(struct sdhci_host *host) host->pm_qos_timeout_us = 10000; /* default value */ pm_qos_add_request(&host->pm_qos_req_dma, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + + host->pm_qos_tout.show = show_sdhci_pm_qos_tout; + host->pm_qos_tout.store = store_sdhci_pm_qos_tout; + sysfs_attr_init(&host->pm_qos_tout.attr); + host->pm_qos_tout.attr.name = "pm_qos_unvote_delay"; + host->pm_qos_tout.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(mmc_dev(mmc), &host->pm_qos_tout); + if (ret) + pr_err("%s: cannot create pm_qos_unvote_delay %d\n", + mmc_hostname(mmc), ret); } + mmc_add_host(mmc); pr_info("%s: SDHCI controller on %s [%s] using %s\n", diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 8aa0f4e077de..21d49a109c2b 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -574,6 +574,8 @@ struct sdhci_host { ktime_t data_start_time; unsigned int pm_qos_timeout_us; /* timeout for PM QoS request */ + struct device_attribute pm_qos_tout; + enum sdhci_power_policy power_policy; unsigned long private[0] ____cacheline_aligned; From c7626f6a261b6004fcf8d4de7bc5ed4efa4490f1 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 24 Jun 2013 18:20:45 +0530 Subject: [PATCH 053/472] mmc: sdhci-msm: add MMC_PM_KEEP_POWER flag This flag ensures that the power to the sdio card is not cut-off. That way this can wake-up the device when required using asynchronous interrupt mechanism. Change-Id: Ic91bfdc93e117c5e627360a4d0ef80f661aa1b60 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 08558b511af0..90a13ee1b799 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2356,6 +2356,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; + msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; From 69c5bb04104a1c674d13dc229274a85f368f8d85 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 28 Jun 2013 15:03:44 +0530 Subject: [PATCH 054/472] mmc: sdhci-msm: configure regulators only if defined in dts This patch adds support to configure regulators only if it is defined in dts files. However, it doesn't return an error otherwise. Change-Id: Iac2284b2df3b3d8af623da0f17697694994b34b8 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 90a13ee1b799..ac0fd9fe40cc 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -124,6 +124,7 @@ struct sdhci_msm_reg_data { bool is_always_on; /* is low power mode setting required for this regulator? */ bool lpm_sup; + bool set_voltage_sup; }; /* @@ -807,8 +808,7 @@ static int sdhci_msm_dt_parse_vreg_info(struct device *dev, snprintf(prop_name, MAX_PROP_SIZE, "%s-supply", vreg_name); if (!of_parse_phandle(np, prop_name, 0)) { - dev_err(dev, "No vreg data found for %s\n", vreg_name); - ret = -EINVAL; + dev_info(dev, "No vreg data found for %s\n", vreg_name); return ret; } @@ -1446,11 +1446,14 @@ static int sdhci_msm_vreg_init_reg(struct device *dev, goto out; } - /* sanity check */ - if (!vreg->high_vol_level || !vreg->hpm_uA) { - pr_err("%s: %s invalid constraints specified\n", - __func__, vreg->name); - ret = -EINVAL; + if (regulator_count_voltages(vreg->reg) > 0) { + vreg->set_voltage_sup = true; + /* sanity check */ + if (!vreg->high_vol_level || !vreg->hpm_uA) { + pr_err("%s: %s invalid constraints specified\n", + __func__, vreg->name); + ret = -EINVAL; + } } out: @@ -1472,9 +1475,10 @@ static int sdhci_msm_vreg_set_optimum_mode(struct sdhci_msm_reg_data * regulators that do not support regulator_set_voltage also * do not support regulator_set_optimum_mode */ - ret = regulator_set_optimum_mode(vreg->reg, uA_load); - if (ret < 0) - pr_err("%s: regulator_set_optimum_mode(reg=%s,uA_load=%d) failed. ret=%d\n", + if (vreg->set_voltage_sup) { + ret = regulator_set_optimum_mode(vreg->reg, uA_load); + if (ret < 0) + pr_err("%s: regulator_set_optimum_mode(reg=%s,uA_load=%d) failed. ret=%d\n", __func__, vreg->name, uA_load, ret); else /* @@ -1482,6 +1486,7 @@ static int sdhci_msm_vreg_set_optimum_mode(struct sdhci_msm_reg_data * value even for success case. */ ret = 0; + } return ret; } @@ -1489,12 +1494,13 @@ static int sdhci_msm_vreg_set_voltage(struct sdhci_msm_reg_data *vreg, int min_uV, int max_uV) { int ret = 0; - - ret = regulator_set_voltage(vreg->reg, min_uV, max_uV); - if (ret) { - pr_err("%s: regulator_set_voltage(%s)failed. min_uV=%d,max_uV=%d,ret=%d\n", + if (vreg->set_voltage_sup) { + ret = regulator_set_voltage(vreg->reg, min_uV, max_uV); + if (ret) { + pr_err("%s: regulator_set_voltage(%s)failed. min_uV=%d,max_uV=%d,ret=%d\n", __func__, vreg->name, min_uV, max_uV, ret); } + } return ret; } From c1bee7c5f4f0e6787480532d832431080697b1ae Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 23 Jul 2013 16:20:34 +0530 Subject: [PATCH 055/472] mmc: sdhci: add auto command error interrupt support The auto command error interrupt is not enabled now. Hence, if there occurs a timeout when auto-cmd is in progress, no data timeout interrupt would occur and the driver would only timeout after the software timer expires. This patch enables the auto command error interrupt and lets the higher layers know of such an error, if any. CRs-Fixed: 515513 Change-Id: I69880a72ece7730645dcfe699d58271d60cab33d Signed-off-by: Asutosh Das Signed-off-by: Krishna Konda --- drivers/mmc/host/sdhci.c | 23 +++++++++++++++++++++-- drivers/mmc/host/sdhci.h | 14 +++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 436cf467ff9d..520db52d7fab 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -129,7 +129,7 @@ static void sdhci_dumpregs(struct sdhci_host *host) sdhci_readl(host, SDHCI_INT_ENABLE), sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); pr_info(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", - sdhci_readw(host, SDHCI_ACMD12_ERR), + sdhci_readw(host, SDHCI_AUTO_CMD_ERR), sdhci_readw(host, SDHCI_SLOT_INT_STATUS)); pr_info(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", sdhci_readl(host, SDHCI_CAPABILITIES), @@ -137,6 +137,12 @@ static void sdhci_dumpregs(struct sdhci_host *host) pr_info(DRIVER_NAME ": Cmd: 0x%08x | Max curr: 0x%08x\n", sdhci_readw(host, SDHCI_COMMAND), sdhci_readl(host, SDHCI_MAX_CURRENT)); + pr_info(DRIVER_NAME ": Resp 1: 0x%08x | Resp 0: 0x%08x\n", + sdhci_readl(host, SDHCI_RESPONSE + 0x4), + sdhci_readl(host, SDHCI_RESPONSE)); + pr_info(DRIVER_NAME ": Resp 3: 0x%08x | Resp 2: 0x%08x\n", + sdhci_readl(host, SDHCI_RESPONSE + 0xC), + sdhci_readl(host, SDHCI_RESPONSE + 0x8)); pr_info(DRIVER_NAME ": Host ctl2: 0x%08x\n", sdhci_readw(host, SDHCI_HOST_CONTROL2)); @@ -288,7 +294,7 @@ static void sdhci_init(struct sdhci_host *host, int soft) SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END | - SDHCI_INT_RESPONSE; + SDHCI_INT_RESPONSE | SDHCI_INT_AUTO_CMD_ERR; sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); @@ -2528,6 +2534,7 @@ static void sdhci_timeout_timer(unsigned long data) static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) { + u16 auto_cmd_status; BUG_ON(intmask == 0); if (!host->cmd) { @@ -2544,6 +2551,18 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) SDHCI_INT_INDEX)) host->cmd->error = -EILSEQ; + if (intmask & SDHCI_INT_AUTO_CMD_ERR) { + auto_cmd_status = sdhci_readw(host, SDHCI_AUTO_CMD_ERR); + if (auto_cmd_status & (SDHCI_AUTO_CMD12_NOT_EXEC | + SDHCI_AUTO_CMD_INDEX_ERR | + SDHCI_AUTO_CMD_ENDBIT_ERR)) + host->cmd->error = -EIO; + else if (auto_cmd_status & SDHCI_AUTO_CMD_TIMEOUT_ERR) + host->cmd->error = -ETIMEDOUT; + else if (auto_cmd_status & SDHCI_AUTO_CMD_CRC_ERR) + host->cmd->error = -EILSEQ; + } + if (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING) { if ((host->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) || (host->cmd->opcode == MMC_SEND_TUNING_BLOCK)) { diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 21d49a109c2b..51cbe3859ba7 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -138,14 +138,16 @@ #define SDHCI_INT_DATA_CRC 0x00200000 #define SDHCI_INT_DATA_END_BIT 0x00400000 #define SDHCI_INT_BUS_POWER 0x00800000 -#define SDHCI_INT_ACMD12ERR 0x01000000 +#define SDHCI_INT_AUTO_CMD_ERR 0x01000000 #define SDHCI_INT_ADMA_ERROR 0x02000000 #define SDHCI_INT_NORMAL_MASK 0x00007FFF #define SDHCI_INT_ERROR_MASK 0xFFFF8000 #define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \ - SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX) + SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX | \ + SDHCI_INT_AUTO_CMD_ERR) + #define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \ SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \ SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \ @@ -153,7 +155,13 @@ SDHCI_INT_BLK_GAP) #define SDHCI_INT_ALL_MASK ((unsigned int)-1) -#define SDHCI_ACMD12_ERR 0x3C +#define SDHCI_AUTO_CMD_ERR 0x3C +#define SDHCI_AUTO_CMD12_NOT_EXEC 0x0001 +#define SDHCI_AUTO_CMD_TIMEOUT_ERR 0x0002 +#define SDHCI_AUTO_CMD_CRC_ERR 0x0004 +#define SDHCI_AUTO_CMD_ENDBIT_ERR 0x0008 +#define SDHCI_AUTO_CMD_INDEX_ERR 0x0010 +#define SDHCI_AUTO_CMD12_NOT_ISSUED 0x0080 #define SDHCI_HOST_CONTROL2 0x3E #define SDHCI_CTRL_UHS_MASK 0x0007 From b1a0856e691353478e94c19cf3e12626f4e38f63 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 2 Aug 2013 09:17:54 +0530 Subject: [PATCH 056/472] mmc: sdhci: Enhance debug register dump Add new host operation dump_vendor_regs to provide a mechanism through which host drivers can dump vendor specific registers in addition to SDHC registers during error conditions. Change-Id: Ifba3ddcb18c3c0917343d99fe58d5ed04b2da871 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 2 ++ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 520db52d7fab..d6d241705397 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -158,6 +158,8 @@ static void sdhci_dumpregs(struct sdhci_host *host) readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); } + if (host->ops->dump_vendor_regs) + host->ops->dump_vendor_regs(host); sdhci_dump_state(host); pr_info(DRIVER_NAME ": ===========================================\n"); } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 51cbe3859ba7..0820425efffc 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -625,6 +625,7 @@ struct sdhci_ops { void (*card_event)(struct sdhci_host *host); void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); void (*check_power_status)(struct sdhci_host *host, u32 req_type); + void (*dump_vendor_regs)(struct sdhci_host *host); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, struct mmc_card *card, From 0931642c2067ccadea5af291634c1d9acc764de1 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 2 Aug 2013 09:21:37 +0530 Subject: [PATCH 057/472] mmc: sdhci-msm: dump vendor specific registers during error Implement dump_vendor_registers host operation to print the vendor specific registers in addition to standard SDHC register during error conditions. Change-Id: I347e8f0373264574a80e460967afba6859607ac9 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ac0fd9fe40cc..020677d7ab48 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -79,6 +79,16 @@ #define CORE_CLK_PWRSAVE (1 << 1) #define CORE_IO_PAD_PWR_SWITCH (1 << 16) +#define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 +#define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 + +#define CORE_MCI_DATA_CNT 0x30 +#define CORE_MCI_STATUS 0x34 +#define CORE_MCI_FIFO_CNT 0x44 + +#define CORE_TESTBUS_SEL2_BIT 4 +#define CORE_TESTBUS_SEL2 (1 << CORE_TESTBUS_SEL2_BIT) + /* 8KB descriptors */ #define SDHCI_MSM_MAX_SEGMENTS (1 << 13) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ @@ -2116,6 +2126,61 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, } +#define MAX_TEST_BUS 20 + +void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int tbsel, tbsel2; + int i, index = 0; + u32 test_bus_val = 0; + u32 debug_reg[MAX_TEST_BUS] = {0}; + + pr_info("----------- VENDOR REGISTER DUMP -----------\n"); + pr_info("Data cnt: 0x%08x | Fifo cnt: 0x%08x | Int sts: 0x%08x\n", + readl_relaxed(msm_host->core_mem + CORE_MCI_DATA_CNT), + readl_relaxed(msm_host->core_mem + CORE_MCI_FIFO_CNT), + readl_relaxed(msm_host->core_mem + CORE_MCI_STATUS)); + pr_info("DLL cfg: 0x%08x | DLL sts: 0x%08x | SDCC ver: 0x%08x\n", + readl_relaxed(host->ioaddr + CORE_DLL_CONFIG), + readl_relaxed(host->ioaddr + CORE_DLL_STATUS), + readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION)); + pr_info("Vndr func: 0x%08x | Vndr adma err : addr0: 0x%08x addr1: 0x%08x\n", + readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC), + readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_ADMA_ERR_ADDR0), + readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_ADMA_ERR_ADDR1)); + + /* + * tbsel indicates [2:0] bits and tbsel2 indicates [7:4] bits + * of CORE_TESTBUS_CONFIG register. + * + * To select test bus 0 to 7 use tbsel and to select any test bus + * above 7 use (tbsel2 | tbsel) to get the test bus number. For eg, + * to select test bus 14, write 0x1E to CORE_TESTBUS_CONFIG register + * i.e., tbsel2[7:4] = 0001, tbsel[2:0] = 110. + */ + for (tbsel2 = 0; tbsel2 < 3; tbsel2++) { + for (tbsel = 0; tbsel < 8; tbsel++) { + if (index >= MAX_TEST_BUS) + break; + test_bus_val = (tbsel2 << CORE_TESTBUS_SEL2_BIT) | + tbsel | CORE_TESTBUS_ENA; + writel_relaxed(test_bus_val, + msm_host->core_mem + CORE_TESTBUS_CONFIG); + debug_reg[index++] = readl_relaxed(msm_host->core_mem + + CORE_SDCC_DEBUG_REG); + } + } + for (i = 0; i < MAX_TEST_BUS; i = i + 4) + pr_info(" Test bus[%d to %d]: 0x%08x 0x%08x 0x%08x 0x%08x\n", + i, i + 3, debug_reg[i], debug_reg[i+1], + debug_reg[i+2], debug_reg[i+3]); + /* Disable test bus */ + writel_relaxed(~CORE_TESTBUS_ENA, msm_host->core_mem + + CORE_TESTBUS_CONFIG); +} + static struct sdhci_ops sdhci_msm_ops = { .set_uhs_signaling = sdhci_msm_set_uhs_signaling, .check_power_status = sdhci_msm_check_power_status, @@ -2125,6 +2190,7 @@ static struct sdhci_ops sdhci_msm_ops = { .set_clock = sdhci_msm_set_clock, .get_min_clock = sdhci_msm_get_min_clock, .get_max_clock = sdhci_msm_get_max_clock, + .dump_vendor_regs = sdhci_msm_dump_vendor_regs, }; static int sdhci_msm_probe(struct platform_device *pdev) From 6f092797e7859446617ecfe7668d0b40be932717 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 6 Aug 2013 11:21:33 +0530 Subject: [PATCH 058/472] mmc: sdhci-msm: Fix incorrect flags passed during spin_unlock_irqrestore Fix following bug - foo_bar1(int arg1, unsigned long flags) { spin_unlock_irqrestore(lock, flags); // step 1 ; spin_lock_irqsave(lock, flags); // step 2 } foo_bar() { unsinged long flags; spin_lock_irqsave(lock, flags); // step 3 foo_bar1(arg1, flags); spin_unlock_irqrestore(lock, flags); // step 4 } The "flags" might be changed in step 4 due to irqrestore and irqsave in foo_bar1(). Change-Id: I42366f7acdde022705f4b3dd06122d54ad817078 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/host/sdhci-msm.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 020677d7ab48..ef7fd50bb56e 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1262,16 +1262,18 @@ static int sdhci_msm_bus_get_vote_for_bw(struct sdhci_msm_host *host, */ static inline int sdhci_msm_bus_set_vote(struct sdhci_msm_host *msm_host, int vote, - unsigned long flags) + unsigned long *flags) { struct sdhci_host *host = platform_get_drvdata(msm_host->pdev); int rc = 0; + BUG_ON(!flags); + if (vote != msm_host->msm_bus_vote.curr_vote) { - spin_unlock_irqrestore(&host->lock, flags); + spin_unlock_irqrestore(&host->lock, *flags); rc = msm_bus_scale_client_update_request( msm_host->msm_bus_vote.client_handle, vote); - spin_lock_irqsave(&host->lock, flags); + spin_lock_irqsave(&host->lock, *flags); if (rc) { pr_err("%s: msm_bus_scale_client_update_request() failed: bus_client_handle=0x%x, vote=%d, err=%d\n", mmc_hostname(host->mmc), @@ -1304,7 +1306,7 @@ static void sdhci_msm_bus_work(struct work_struct *work) /* don't vote for 0 bandwidth if any request is in progress */ if (!host->mrq) { sdhci_msm_bus_set_vote(msm_host, - msm_host->msm_bus_vote.min_bw_vote, flags); + msm_host->msm_bus_vote.min_bw_vote, &flags); } else pr_warning("%s: %s: Transfer in progress. skipping bus voting to 0 bandwidth\n", mmc_hostname(host->mmc), __func__); @@ -1326,7 +1328,7 @@ static void sdhci_msm_bus_cancel_work_and_set_vote(struct sdhci_host *host, cancel_delayed_work_sync(&msm_host->msm_bus_vote.vote_work); spin_lock_irqsave(&host->lock, flags); vote = sdhci_msm_bus_get_vote_for_bw(msm_host, bw); - sdhci_msm_bus_set_vote(msm_host, vote, flags); + sdhci_msm_bus_set_vote(msm_host, vote, &flags); spin_unlock_irqrestore(&host->lock, flags); } From 0c6d03791ca275d3d8f6c57bbee1a5a39e80d93b Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 13 Jun 2013 14:27:42 +0530 Subject: [PATCH 059/472] mmc: sdhci-msm: ignore data-end-bit error in 1 bit mode Some SDHC controllers are unable to handle data end-bit errors in one bit mode. This patch adds a quirk to ignore data-end-bit error in 1-bit mode in Qualcomm SDHC controllers. Change-Id: Ica0f10573d654021449c32197b126e12bb1a3c10 Signed-off-by: Asutosh Das [venkatg@codeaurora.org: sdhci_clear_set_irqs was removed from 3.14 kernel, write the registers directly] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed minor merge conflict and fixed the bitmap for the quirk macro] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 2 ++ drivers/mmc/host/sdhci.c | 6 ++++++ drivers/mmc/host/sdhci.h | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ef7fd50bb56e..f2fb9f56564a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2396,6 +2396,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_RDWR_TX_ACTIVE_EOT; } + host->quirks2 |= SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR; + /* Setup PWRCTL irq */ pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); if (pwr_irq < 0) { diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d6d241705397..c434771815dc 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3695,6 +3695,12 @@ int sdhci_add_host(struct sdhci_host *host) mmc_add_host(mmc); + if (host->quirks2 & SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR) { + host->ier = (host->ier & ~SDHCI_INT_DATA_END_BIT); + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + } + pr_info("%s: SDHCI controller on %s [%s] using %s\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), (host->flags & SDHCI_USE_ADMA) ? diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 0820425efffc..9334741d2172 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -485,6 +485,11 @@ struct sdhci_host { */ #define SDHCI_QUIRK2_DIVIDE_TOUT_BY_4 (1 << 23) +/* + * Some SDHC controllers are unable to handle data-end bit error in + * 1-bit mode of SDIO. + */ +#define SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR (1<<24) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From 2858ae8aceb39b24d51eec05975d93b73ca271d2 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 6 Aug 2013 15:22:28 +0530 Subject: [PATCH 060/472] mmc: sdhci-msm: Fix power IRQ issue uncovered in 3.10 kernel The request to change the VDD I/O voltage level to high/low will trigger an IRQ only when - 1. SWITCHABLE_SIGNALING_VOLTAGE bit 29 of SDCC_MCI_GENERICS register is set. 2. Above condition is true and when there is a state change in VDD bit 3 of SDHCi Host Control 2 register. Until now, the MMC core layer issues I/O high request only after the controller is powered up. The I/O high request is same as the reset state of host control2 register which will never trigger an IRQ. The driver already handles this case by ensuring that I/O voltage is set to high as part of power up itself and thus returns immediately when I/O high request is issued later. But in 3.10 kernel, this request is issued even before the controller is powered up. Hence, check for host->pwr state to avoid waiting for an IRQ that never comes. Change-Id: I31b6723f53397be1ba151305ead89e739560eb20 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f2fb9f56564a..9ec0ec0baf84 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -43,6 +43,9 @@ #define CORE_HC_MODE 0x78 #define HC_MODE_EN 0x1 +#define CORE_GENERICS 0x70 +#define SWITCHABLE_SIGNALLING_VOL (1 << 29) + #define CORE_POWER 0x0 #define CORE_SW_RST (1 << 7) @@ -1891,11 +1894,36 @@ static void sdhci_msm_check_power_status(struct sdhci_host *host, u32 req_type) struct sdhci_msm_host *msm_host = pltfm_host->priv; unsigned long flags; bool done = false; + u32 io_sig_sts; spin_lock_irqsave(&host->lock, flags); pr_debug("%s: %s: request %d curr_pwr_state %x curr_io_level %x\n", mmc_hostname(host->mmc), __func__, req_type, msm_host->curr_pwr_state, msm_host->curr_io_level); + io_sig_sts = readl_relaxed(msm_host->core_mem + CORE_GENERICS); + /* + * The IRQ for request type IO High/Low will be generated when - + * 1. SWITCHABLE_SIGNALLING_VOL is enabled in HW. + * 2. If 1 is true and when there is a state change in 1.8V enable + * bit (bit 3) of SDHCI_HOST_CONTROL2 register. The reset state of + * that bit is 0 which indicates 3.3V IO voltage. So, when MMC core + * layer tries to set it to 3.3V before card detection happens, the + * IRQ doesn't get triggered as there is no state change in this bit. + * The driver already handles this case by changing the IO voltage + * level to high as part of controller power up sequence. Hence, check + * for host->pwr to handle a case where IO voltage high request is + * issued even before controller power up. + */ + if (req_type & (REQ_IO_HIGH | REQ_IO_LOW)) { + if (!(io_sig_sts & SWITCHABLE_SIGNALLING_VOL) || + ((req_type & REQ_IO_HIGH) && !host->pwr)) { + pr_debug("%s: do not wait for power IRQ that never comes\n", + mmc_hostname(host->mmc)); + spin_unlock_irqrestore(&host->lock, flags); + return; + } + } + if ((req_type & msm_host->curr_pwr_state) || (req_type & msm_host->curr_io_level)) done = true; From 405700959cf6729be2c474052b5060a404ed90b7 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Sun, 23 Jun 2013 17:36:46 -0700 Subject: [PATCH 061/472] mmc: sdhci-msm: Add HS400 platform support The following msm platform specific changes are added to support HS400. - Enable CDC calibration fixed feedback and sleep clock. - Allow tuning for HS400 mode. - Add capability to parse and configure pull settings for RCLK pin. - Configure HS400 timing mode using the VENDOR_SPECIFIC_FUNC register. Change-Id: I1304fe0f01df493ead48bf9ff3c7baee5ab040d4 Signed-off-by: Venkat Gopalakrishnan --- .../devicetree/bindings/mmc/sdhci-msm.txt | 3 +- drivers/mmc/host/sdhci-msm.c | 201 ++++++++++++++++-- 2 files changed, 183 insertions(+), 21 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index f8bc7a294a70..82cf9b0d1c5e 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -49,7 +49,8 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag - qcom,pad-pull-off - Suspend pull configuration for sdc tlmm pins. - qcom,pad-drv-on - Active drive strength configuration for sdc tlmm pins. - qcom,pad-drv-off - Suspend drive strength configuration for sdc tlmm pins. - Tlmm pins are specified as + Tlmm pins are specified as and starting with eMMC5.0 as + Example: diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9ec0ec0baf84..936326fe411b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -42,6 +42,7 @@ #define SDHCI_VER_100 0x2B #define CORE_HC_MODE 0x78 #define HC_MODE_EN 0x1 +#define FF_CLK_SW_RST_DIS (1 << 13) #define CORE_GENERICS 0x70 #define SWITCHABLE_SIGNALLING_VOL (1 << 29) @@ -80,7 +81,13 @@ #define CORE_VENDOR_SPEC 0x10C #define CORE_CLK_PWRSAVE (1 << 1) +#define CORE_HC_MCLK_SEL_DFLT (2 << 8) +#define CORE_HC_MCLK_SEL_HS400 (3 << 8) +#define CORE_HC_MCLK_SEL_MASK (3 << 8) #define CORE_IO_PAD_PWR_SWITCH (1 << 16) +#define CORE_HC_SELECT_IN_EN (1 << 18) +#define CORE_HC_SELECT_IN_HS400 (6 << 19) +#define CORE_HC_SELECT_IN_MASK (7 << 19) #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 @@ -96,6 +103,8 @@ #define SDHCI_MSM_MAX_SEGMENTS (1 << 13) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ +#define CORE_FREQ_100MHZ (100 * 1000 * 1000) + static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, @@ -241,6 +250,8 @@ struct sdhci_msm_host { struct clk *clk; /* main SD/MMC bus clock */ struct clk *pclk; /* SDHC peripheral bus clock */ struct clk *bus_clk; /* SDHC bus voter clock */ + struct clk *ff_clk; /* CDC calibration fixed feedback clock */ + struct clk *sleep_clk; /* CDC calibration sleep clock */ atomic_t clks_on; /* Set if clocks are enabled */ struct sdhci_msm_pltfm_data *pdata; struct mmc_host *mmc; @@ -251,6 +262,8 @@ struct sdhci_msm_host { struct sdhci_msm_bus_vote msm_bus_vote; struct device_attribute polling; u32 clk_rate; /* Keeps track of current clock rate that is set */ + bool tuning_done; + bool calibration_done; }; enum vdd_io_level { @@ -354,8 +367,8 @@ out: * Find out the greatest range of consecuitive selected * DLL clock output phases that can be used as sampling * setting for SD3.0 UHS-I card read operation (in SDR104 - * timing mode) or for eMMC4.5 card read operation (in HS200 - * timing mode). + * timing mode) or for eMMC4.5 card read operation (in + * HS400/HS200 timing mode). * Select the 3/4 of the range and configure the DLL with the * selected DLL clock output phase. */ @@ -588,12 +601,13 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) struct mmc_ios ios = host->mmc->ios; /* - * Tuning is required for SDR104 and HS200 cards and if clock frequency - * is greater than 100MHz in these modes. + * Tuning is required for SDR104, HS200 and HS400 cards and + * if clock frequency is greater than 100MHz in these modes. */ - if (host->clock <= (100 * 1000 * 1000) || - !(ios.timing == MMC_TIMING_MMC_HS200 || - ios.timing == MMC_TIMING_UHS_SDR104)) + if (host->clock <= CORE_FREQ_100MHZ || + !((ios.timing == MMC_TIMING_MMC_HS400) || + (ios.timing == MMC_TIMING_MMC_HS200) || + (ios.timing == MMC_TIMING_UHS_SDR104))) return 0; pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); @@ -784,7 +798,7 @@ static int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name, goto out; } sz = *len = *len / sizeof(*arr); - if (sz <= 0 || (size > 0 && (sz != size))) { + if (sz <= 0 || (size > 0 && (sz > size))) { dev_err(dev, "%s invalid size\n", prop_name); ret = -EINVAL; goto out; @@ -910,9 +924,9 @@ static int sdhci_msm_dt_get_pad_pull_info(struct device *dev, int id, ret = -ENOMEM; goto out; } - pull_data->size = 3; /* array size for clk, cmd, data */ + pull_data->size = 4; /* array size for clk, cmd, data and rclk */ - /* Allocate on, off configs for clk, cmd, data */ + /* Allocate on, off configs for clk, cmd, data and rclk */ pull = devm_kzalloc(dev, 2 * pull_data->size *\ sizeof(struct sdhci_msm_pad_pull), GFP_KERNEL); if (!pull) { @@ -1195,7 +1209,11 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) if (!name) continue; - if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v"))) + if (!strncmp(name, "HS400_1p8v", sizeof("HS400_1p8v"))) + pdata->caps2 |= MMC_CAP2_HS400_1_8V; + else if (!strncmp(name, "HS400_1p2v", sizeof("HS400_1p2v"))) + pdata->caps2 |= MMC_CAP2_HS400_1_2V; + else if (!strncmp(name, "HS200_1p8v", sizeof("HS200_1p8v"))) pdata->caps2 |= MMC_CAP2_HS200_1_8V_SDR; else if (!strncmp(name, "HS200_1p2v", sizeof("HS200_1p2v"))) pdata->caps2 |= MMC_CAP2_HS200_1_2V_SDR; @@ -2039,6 +2057,22 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) mmc_hostname(host->mmc), __func__, rc); goto disable_pclk; } + if (!IS_ERR(msm_host->ff_clk)) { + rc = clk_prepare_enable(msm_host->ff_clk); + if (rc) { + pr_err("%s: %s: failed to enable the ff_clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_clk; + } + } + if (!IS_ERR(msm_host->sleep_clk)) { + rc = clk_prepare_enable(msm_host->sleep_clk); + if (rc) { + pr_err("%s: %s: failed to enable the sleep_clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_ff_clk; + } + } mb(); } else if (!enable && atomic_read(&msm_host->clks_on)) { @@ -2046,6 +2080,10 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) mmc_hostname(host->mmc)); sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); mb(); + if (!IS_ERR_OR_NULL(msm_host->sleep_clk)) + clk_disable_unprepare(msm_host->sleep_clk); + if (!IS_ERR_OR_NULL(msm_host->ff_clk)) + clk_disable_unprepare(msm_host->ff_clk); clk_disable_unprepare(msm_host->clk); if (!IS_ERR(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); @@ -2056,6 +2094,12 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) } atomic_set(&msm_host->clks_on, enable); goto out; +disable_ff_clk: + if (!IS_ERR_OR_NULL(msm_host->ff_clk)) + clk_disable_unprepare(msm_host->ff_clk); +disable_clk: + if (!IS_ERR_OR_NULL(msm_host->clk)) + clk_disable_unprepare(msm_host->clk); disable_pclk: if (!IS_ERR_OR_NULL(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); @@ -2088,17 +2132,79 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) goto out; sup_clock = sdhci_msm_get_sup_clk_rate(host, clock); - if (curr_ios.timing == MMC_TIMING_UHS_DDR50) { + if ((curr_ios.timing == MMC_TIMING_UHS_DDR50) || + (curr_ios.timing == MMC_TIMING_MMC_HS400)) { /* * The SDHC requires internal clock frequency to be double the * actual clock that will be set for DDR mode. The controller - * uses the faster clock(100MHz) for some of its parts and send - * the actual required clock (50MHz) to the card. + * uses the faster clock(100/400MHz) for some of its parts and + * send the actual required clock (50/200MHz) to the card. */ ddr_clock = clock * 2; sup_clock = sdhci_msm_get_sup_clk_rate(host, ddr_clock); } + + /* + * In general all timing modes are controlled via UHS mode select in + * Host Control2 register. eMMC specific HS200/HS400 doesn't have + * their respective modes defined here, hence we use these values. + * + * HS200 - SDR104 (Since they both are equivalent in functionality) + * HS400 - This involves multiple configurations + * Initially SDR104 - when tuning is required as HS200 + * Then when switching to DDR @ 400MHz (HS400) we use + * the vendor specific HC_SELECT_IN to control the mode. + * + * In addition to controlling the modes we also need to select the + * correct input clock for DLL depending on the mode. + * + * HS400 - divided clock (free running MCLK/2) + * All other modes - default (free running MCLK) + */ + if (curr_ios.timing == MMC_TIMING_MMC_HS400) { + /* Select the divided clock (free running MCLK/2) */ + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_MCLK_SEL_MASK) + | CORE_HC_MCLK_SEL_HS400), + host->ioaddr + CORE_VENDOR_SPEC); + /* + * Select HS400 mode using the HC_SELECT_IN from VENDOR SPEC + * register + */ + if (msm_host->tuning_done && !msm_host->calibration_done) { + /* + * Write 0x6 to HC_SELECT_IN and 1 to HC_SELECT_IN_EN + * field in VENDOR_SPEC_FUNC + */ + writel_relaxed((readl_relaxed(host->ioaddr + \ + CORE_VENDOR_SPEC) + | CORE_HC_SELECT_IN_HS400 + | CORE_HC_SELECT_IN_EN), + host->ioaddr + CORE_VENDOR_SPEC); + } + } else { + /* Select the default clock (free running MCLK) */ + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_MCLK_SEL_MASK) + | CORE_HC_MCLK_SEL_DFLT), + host->ioaddr + CORE_VENDOR_SPEC); + + /* + * Disable HC_SELECT_IN to be able to use the UHS mode select + * configuration from Host Control2 register for all other + * modes. + * + * Write 0 to HC_SELECT_IN and HC_SELECT_IN_EN field + * in VENDOR_SPEC_FUNC + */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_HC_SELECT_IN_EN + & ~CORE_HC_SELECT_IN_MASK), + host->ioaddr + CORE_VENDOR_SPEC); + } + mb(); + if (sup_clock != msm_host->clk_rate) { pr_debug("%s: %s: setting clk rate to %u\n", mmc_hostname(host->mmc), __func__, sup_clock); @@ -2124,12 +2230,16 @@ out: static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, unsigned int uhs) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; u16 ctrl_2; ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* Select Bus Speed Mode for host */ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; - if (uhs == MMC_TIMING_MMC_HS200) + if (uhs == MMC_TIMING_MMC_HS400) + ctrl_2 |= SDHCI_CTRL_UHS_SDR104; + else if (uhs == MMC_TIMING_MMC_HS200) ctrl_2 |= SDHCI_CTRL_UHS_SDR104; else if (uhs == MMC_TIMING_UHS_SDR12) ctrl_2 |= SDHCI_CTRL_UHS_SDR12; @@ -2147,11 +2257,36 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, * provide feedback clock, the mode selection can be any value less * than 3'b011 in bits [2:0] of HOST CONTROL2 register. */ - if (host->clock <= (100 * 1000 * 1000) && - (uhs == MMC_TIMING_MMC_HS200 || - uhs == MMC_TIMING_UHS_SDR104)) - ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + if (host->clock <= CORE_FREQ_100MHZ) { + if ((uhs == MMC_TIMING_MMC_HS400) || + (uhs == MMC_TIMING_MMC_HS200) || + (uhs == MMC_TIMING_UHS_SDR104)) + ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; + /* + * Make sure DLL is disabled when not required + * + * Write 1 to DLL_RST bit of DLL_CONFIG register + */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_RST), + host->ioaddr + CORE_DLL_CONFIG); + + /* Write 1 to DLL_PDN bit of DLL_CONFIG register */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_DLL_PDN), + host->ioaddr + CORE_DLL_CONFIG); + mb(); + + /* + * The DLL needs to be restored and CDCLP533 recalibrated + * when the clock frequency is set back to 400MHz. + */ + msm_host->calibration_done = false; + } + + pr_debug("%s: %s-clock:%u uhs mode:%u ctrl_2:0x%x\n", + mmc_hostname(host->mmc), __func__, host->clock, uhs, ctrl_2); sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2); } @@ -2320,9 +2455,25 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->clk_rate = sdhci_msm_get_min_clock(host); atomic_set(&msm_host->clks_on, 1); + /* Setup CDC calibration fixed feedback clock */ + msm_host->ff_clk = devm_clk_get(&pdev->dev, "cal_clk"); + if (!IS_ERR(msm_host->ff_clk)) { + ret = clk_prepare_enable(msm_host->ff_clk); + if (ret) + goto clk_disable; + } + + /* Setup CDC calibration sleep clock */ + msm_host->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk"); + if (!IS_ERR(msm_host->sleep_clk)) { + ret = clk_prepare_enable(msm_host->sleep_clk); + if (ret) + goto ff_clk_disable; + } + ret = sdhci_msm_bus_register(msm_host, pdev); if (ret) - goto clk_disable; + goto sleep_clk_disable; if (msm_host->msm_bus_vote.client_handle) INIT_DELAYED_WORK(&msm_host->msm_bus_vote.vote_work, @@ -2367,6 +2518,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); + /* Set FF_CLK_SW_RST_DIS bit in HC_MODE register */ + writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_HC_MODE) | + FF_CLK_SW_RST_DIS, msm_host->core_mem + CORE_HC_MODE); + /* * CORE_SW_RST above may trigger power irq if previous status of PWRCTL * was either BUS_ON or IO_HIGH_V. So before we enable the power irq @@ -2529,6 +2684,12 @@ bus_unregister: if (msm_host->msm_bus_vote.client_handle) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); sdhci_msm_bus_unregister(msm_host); +sleep_clk_disable: + if (!IS_ERR(msm_host->sleep_clk)) + clk_disable_unprepare(msm_host->sleep_clk); +ff_clk_disable: + if (!IS_ERR(msm_host->ff_clk)) + clk_disable_unprepare(msm_host->ff_clk); clk_disable: if (!IS_ERR(msm_host->clk)) clk_disable_unprepare(msm_host->clk); From 39ba9d6667631c41453bd9a1ef83496ddc247e62 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 12 Jun 2013 11:16:37 -0700 Subject: [PATCH 062/472] mmc: sdhci-msm: Add calibration tuning for CDCLP533 circuit In HS400 mode a new RCLK is introduced on the interface for read data transfers. The eMMC5.0 device transmits the read data to the host with respect to rising and falling edges of RCLK. In order to ensure correct operation of read data transfers in HS400 mode, the incoming RX data needs to be sampled by delayed version of RCLK. The CDCLP533 delay circuit shifts the RCLK by T/4. It needs to be initialized, configured and enabled once during HS400 mode switch and when operational voltage/clock is changed. Change-Id: Ie7acc50cb932a7b531434ebe72f78e2e7ad27408 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 191 ++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 936326fe411b..79468051a231 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -68,16 +68,17 @@ #define INT_MASK 0xF #define MAX_PHASES 16 -#define CORE_DLL_LOCK (1 << 7) +#define CORE_DLL_CONFIG 0x100 +#define CORE_CMD_DAT_TRACK_SEL (1 << 0) #define CORE_DLL_EN (1 << 16) #define CORE_CDR_EN (1 << 17) #define CORE_CK_OUT_EN (1 << 18) #define CORE_CDR_EXT_EN (1 << 19) #define CORE_DLL_PDN (1 << 29) #define CORE_DLL_RST (1 << 30) -#define CORE_DLL_CONFIG 0x100 -#define CORE_DLL_TEST_CTL 0x104 + #define CORE_DLL_STATUS 0x108 +#define CORE_DLL_LOCK (1 << 7) #define CORE_VENDOR_SPEC 0x10C #define CORE_CLK_PWRSAVE (1 << 1) @@ -92,6 +93,33 @@ #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 +#define CORE_CSR_CDC_CTLR_CFG0 0x130 +#define CORE_SW_TRIG_FULL_CALIB (1 << 16) +#define CORE_HW_AUTOCAL_ENA (1 << 17) + +#define CORE_CSR_CDC_CTLR_CFG1 0x134 +#define CORE_CSR_CDC_CAL_TIMER_CFG0 0x138 +#define CORE_TIMER_ENA (1 << 16) + +#define CORE_CSR_CDC_CAL_TIMER_CFG1 0x13C +#define CORE_CSR_CDC_REFCOUNT_CFG 0x140 +#define CORE_CSR_CDC_COARSE_CAL_CFG 0x144 +#define CORE_CDC_OFFSET_CFG 0x14C +#define CORE_CSR_CDC_DELAY_CFG 0x150 +#define CORE_CDC_SLAVE_DDA_CFG 0x160 +#define CORE_CSR_CDC_STATUS0 0x164 +#define CORE_CALIBRATION_DONE (1 << 0) + +#define CORE_CDC_ERROR_CODE_MASK 0x7000000 + +#define CORE_CSR_CDC_GEN_CFG 0x178 +#define CORE_CDC_SWITCH_BYPASS_OFF (1 << 0) +#define CORE_CDC_SWITCH_RC_EN (1 << 1) + +#define CORE_DDR_200_CFG 0x184 +#define CORE_CDC_T4_DLY_SEL (1 << 0) +#define CORE_START_CDC_TRAFFIC (1 << 6) + #define CORE_MCI_DATA_CNT 0x30 #define CORE_MCI_STATUS 0x34 #define CORE_MCI_FIFO_CNT 0x44 @@ -105,6 +133,8 @@ #define CORE_FREQ_100MHZ (100 * 1000 * 1000) +#define INVALID_TUNING_PHASE -1 + static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, @@ -264,6 +294,7 @@ struct sdhci_msm_host { u32 clk_rate; /* Keeps track of current clock rate that is set */ bool tuning_done; bool calibration_done; + u8 saved_tuning_phase; }; enum vdd_io_level { @@ -589,6 +620,137 @@ out: return rc; } +static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host) +{ + u32 wait_cnt; + int ret = 0; + int cdc_err = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); + + /* + * Retuning in HS400 (DDR mode) will fail, just reset the + * tuning block and restore the saved tuning phase. + */ + ret = msm_init_cm_dll(host); + if (ret) + goto out; + + /* Set the selected phase in delay line hw block */ + ret = msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase); + if (ret) + goto out; + + /* Write 1 to CMD_DAT_TRACK_SEL field in DLL_CONFIG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_CMD_DAT_TRACK_SEL), + host->ioaddr + CORE_DLL_CONFIG); + + /* Write 0 to CDC_T4_DLY_SEL field in VENDOR_SPEC_DDR200_CFG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) + & ~CORE_CDC_T4_DLY_SEL), + host->ioaddr + CORE_DDR_200_CFG); + + /* Write 0 to CDC_SWITCH_BYPASS_OFF field in CORE_CSR_CDC_GEN_CFG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_GEN_CFG) + & ~CORE_CDC_SWITCH_BYPASS_OFF), + host->ioaddr + CORE_CSR_CDC_GEN_CFG); + + /* Write 1 to CDC_SWITCH_RC_EN field in CORE_CSR_CDC_GEN_CFG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_GEN_CFG) + | CORE_CDC_SWITCH_RC_EN), + host->ioaddr + CORE_CSR_CDC_GEN_CFG); + + /* Write 0 to START_CDC_TRAFFIC field in CORE_DDR200_CFG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) + & ~CORE_START_CDC_TRAFFIC), + host->ioaddr + CORE_DDR_200_CFG); + + /* + * Perform CDC Register Initialization Sequence + * + * CORE_CSR_CDC_CTLR_CFG0 0x11800EC + * CORE_CSR_CDC_CTLR_CFG1 0x3011111 + * CORE_CSR_CDC_CAL_TIMER_CFG0 0x1201000 + * CORE_CSR_CDC_CAL_TIMER_CFG1 0x4 + * CORE_CSR_CDC_REFCOUNT_CFG 0xCB732020 + * CORE_CSR_CDC_COARSE_CAL_CFG 0xB19 + * CORE_CSR_CDC_DELAY_CFG 0x3AC + * CORE_CDC_OFFSET_CFG 0x0 + * CORE_CDC_SLAVE_DDA_CFG 0x16334 + */ + + writel_relaxed(0x11800EC, host->ioaddr + CORE_CSR_CDC_CTLR_CFG0); + writel_relaxed(0x3011111, host->ioaddr + CORE_CSR_CDC_CTLR_CFG1); + writel_relaxed(0x1201000, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0); + writel_relaxed(0x4, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG1); + writel_relaxed(0xCB732020, host->ioaddr + CORE_CSR_CDC_REFCOUNT_CFG); + writel_relaxed(0xB19, host->ioaddr + CORE_CSR_CDC_COARSE_CAL_CFG); + writel_relaxed(0x3AC, host->ioaddr + CORE_CSR_CDC_DELAY_CFG); + writel_relaxed(0x0, host->ioaddr + CORE_CDC_OFFSET_CFG); + writel_relaxed(0x16334, host->ioaddr + CORE_CDC_SLAVE_DDA_CFG); + + /* CDC HW Calibration */ + + /* Write 1 to SW_TRIG_FULL_CALIB field in CORE_CSR_CDC_CTLR_CFG0 */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0) + | CORE_SW_TRIG_FULL_CALIB), + host->ioaddr + CORE_CSR_CDC_CTLR_CFG0); + + /* Write 0 to SW_TRIG_FULL_CALIB field in CORE_CSR_CDC_CTLR_CFG0 */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0) + & ~CORE_SW_TRIG_FULL_CALIB), + host->ioaddr + CORE_CSR_CDC_CTLR_CFG0); + + /* Write 1 to HW_AUTOCAL_ENA field in CORE_CSR_CDC_CTLR_CFG0 */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_CSR_CDC_CTLR_CFG0) + | CORE_HW_AUTOCAL_ENA), + host->ioaddr + CORE_CSR_CDC_CTLR_CFG0); + + /* Write 1 to TIMER_ENA field in CORE_CSR_CDC_CAL_TIMER_CFG0 */ + writel_relaxed((readl_relaxed(host->ioaddr + + CORE_CSR_CDC_CAL_TIMER_CFG0) | CORE_TIMER_ENA), + host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG0); + + mb(); + + /* Poll on CALIBRATION_DONE field in CORE_CSR_CDC_STATUS0 to be 1 */ + wait_cnt = 50; + while (!(readl_relaxed(host->ioaddr + CORE_CSR_CDC_STATUS0) + & CORE_CALIBRATION_DONE)) { + /* max. wait for 50us sec for CALIBRATION_DONE bit to be set */ + if (--wait_cnt == 0) { + pr_err("%s: %s: CDC Calibration was not completed\n", + mmc_hostname(host->mmc), __func__); + ret = -ETIMEDOUT; + goto out; + } + /* wait for 1us before polling again */ + udelay(1); + } + + /* Verify CDC_ERROR_CODE field in CORE_CSR_CDC_STATUS0 is 0 */ + cdc_err = readl_relaxed(host->ioaddr + CORE_CSR_CDC_STATUS0) + & CORE_CDC_ERROR_CODE_MASK; + if (cdc_err) { + pr_err("%s: %s: CDC Error Code %d\n", + mmc_hostname(host->mmc), __func__, cdc_err); + ret = -EINVAL; + goto out; + } + + /* Write 1 to START_CDC_TRAFFIC field in CORE_DDR200_CFG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) + | CORE_START_CDC_TRAFFIC), + host->ioaddr + CORE_DDR_200_CFG); +out: + pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc), + __func__, ret); + return ret; +} + int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) { unsigned long flags; @@ -599,6 +761,8 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) int rc; struct mmc_host *mmc = host->mmc; struct mmc_ios ios = host->mmc->ios; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; /* * Tuning is required for SDR104, HS200 and HS400 cards and @@ -611,6 +775,18 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) return 0; pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); + + /* CDCLP533 HW calibration is only required for HS400 mode*/ + if (msm_host->tuning_done && !msm_host->calibration_done && + (mmc->ios.timing == MMC_TIMING_MMC_HS400)) { + rc = sdhci_msm_cdclp533_calibration(host); + spin_lock_irqsave(&host->lock, flags); + if (!rc) + msm_host->calibration_done = true; + spin_unlock_irqrestore(&host->lock, flags); + goto out; + } + spin_lock_irqsave(&host->lock, flags); if ((opcode == MMC_SEND_TUNING_BLOCK_HS200) && @@ -685,6 +861,7 @@ retry: rc = msm_config_cm_dll_phase(host, phase); if (rc) goto kfree; + msm_host->saved_tuning_phase = phase; pr_debug("%s: %s: finally setting the tuning phase to %d\n", mmc_hostname(mmc), __func__, phase); } else { @@ -699,7 +876,11 @@ retry: kfree: kfree(data_buf); out: - pr_debug("%s: Exit %s\n", mmc_hostname(mmc), __func__); + spin_lock_irqsave(&host->lock, flags); + if (!rc) + msm_host->tuning_done = true; + spin_unlock_irqrestore(&host->lock, flags); + pr_debug("%s: Exit %s, err(%d)\n", mmc_hostname(mmc), __func__, rc); return rc; } @@ -2471,6 +2652,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto ff_clk_disable; } + msm_host->saved_tuning_phase = INVALID_TUNING_PHASE; + ret = sdhci_msm_bus_register(msm_host, pdev); if (ret) goto sleep_clk_disable; From d453e3e1fff3e16b37ab47665eabfa1acad81b84 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 7 Aug 2013 18:40:29 +0530 Subject: [PATCH 063/472] mmc: sdhci: fix issue with auto cmd err detection As per specification, auto cmd error status register is valid only when auto cmd error bit is set in Error interrupt status register. CRs-fixed: 515513 Change-Id: Id1013e1705d8efdba0171dcad14f783607d38ef3 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 10 ++++++++-- drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index c434771815dc..85dc8cd6d647 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -129,7 +129,7 @@ static void sdhci_dumpregs(struct sdhci_host *host) sdhci_readl(host, SDHCI_INT_ENABLE), sdhci_readl(host, SDHCI_SIGNAL_ENABLE)); pr_info(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", - sdhci_readw(host, SDHCI_AUTO_CMD_ERR), + host->auto_cmd_err_sts, sdhci_readw(host, SDHCI_SLOT_INT_STATUS)); pr_info(DRIVER_NAME ": Caps: 0x%08x | Caps_1: 0x%08x\n", sdhci_readl(host, SDHCI_CAPABILITIES), @@ -2480,6 +2480,7 @@ static void sdhci_tasklet_finish(unsigned long param) host->mrq = NULL; host->cmd = NULL; host->data = NULL; + host->auto_cmd_err_sts = 0; #ifndef SDHCI_USE_LEDS_CLASS sdhci_deactivate_led(host); @@ -2554,7 +2555,9 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) host->cmd->error = -EILSEQ; if (intmask & SDHCI_INT_AUTO_CMD_ERR) { - auto_cmd_status = sdhci_readw(host, SDHCI_AUTO_CMD_ERR); + auto_cmd_status = host->auto_cmd_err_sts; + pr_err("%s: %s: AUTO CMD err sts 0x%08x\n", + mmc_hostname(host->mmc), __func__, auto_cmd_status); if (auto_cmd_status & (SDHCI_AUTO_CMD12_NOT_EXEC | SDHCI_AUTO_CMD_INDEX_ERR | SDHCI_AUTO_CMD_ENDBIT_ERR)) @@ -2811,6 +2814,9 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) } do { + if (intmask & SDHCI_INT_AUTO_CMD_ERR) + host->auto_cmd_err_sts = sdhci_readw(host, + SDHCI_AUTO_CMD_ERR); /* Clear selected interrupts. */ mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | SDHCI_INT_BUS_POWER); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 9334741d2172..065e13abedfc 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -591,6 +591,8 @@ struct sdhci_host { enum sdhci_power_policy power_policy; + u32 auto_cmd_err_sts; + unsigned long private[0] ____cacheline_aligned; }; From d938b50db5652d11924d1eb013cca3e4932ca78d Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 30 Jul 2013 19:07:29 +0530 Subject: [PATCH 064/472] mmc: sdhci: Add support for auto-calibration This patch adds Programmable Delay Line auto-calibration support if supported by respective hosts.If the host supports auto-calibration this change would enable sending CMD19/CMD21 before any read operation. CRs-Fixed: 516314 Change-Id: I8a0f51206dc0e174519dd71f0c75267a9e08e7f7 Signed-off-by: Asutosh Das [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 38 ++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci.h | 3 +++ 2 files changed, 41 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 85dc8cd6d647..724a574ed4e9 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "sdhci.h" @@ -1522,6 +1523,34 @@ static bool sdhci_check_state(struct sdhci_host *host) return false; } +static bool sdhci_check_auto_tuning(struct sdhci_host *host, + struct mmc_command *cmd) +{ + if (((cmd->opcode != MMC_READ_SINGLE_BLOCK) && + (cmd->opcode != MMC_READ_MULTIPLE_BLOCK) && + (cmd->opcode != SD_IO_RW_EXTENDED)) || (host->clock < 100000000)) + return false; + else if (host->mmc->ios.timing == MMC_TIMING_MMC_HS200 || + host->mmc->ios.timing == MMC_TIMING_UHS_SDR104) + return true; + else + return false; +} + +static int sdhci_get_tuning_cmd(struct sdhci_host *host) +{ + if (!host->mmc || !host->mmc->card) + return 0; + /* + * If we are here, all conditions have already been true + * and the card can either be an eMMC or SD/SDIO + */ + if (mmc_card_mmc(host->mmc->card)) + return MMC_SEND_TUNING_BLOCK_HS200; + else + return MMC_SEND_TUNING_BLOCK; +} + static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct sdhci_host *host; @@ -1583,6 +1612,15 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } else { + if (host->ops->config_auto_tuning_cmd) { + if (sdhci_check_auto_tuning(host, mrq->cmd)) + host->ops->config_auto_tuning_cmd(host, true, + sdhci_get_tuning_cmd(host)); + else + host->ops->config_auto_tuning_cmd(host, false, + sdhci_get_tuning_cmd(host)); + } + if (mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23)) sdhci_send_command(host, mrq->sbc); else diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 065e13abedfc..63106bb28782 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -632,6 +632,9 @@ struct sdhci_ops { void (*card_event)(struct sdhci_host *host); void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); void (*check_power_status)(struct sdhci_host *host, u32 req_type); + int (*config_auto_tuning_cmd)(struct sdhci_host *host, + bool enable, + u32 type); void (*dump_vendor_regs)(struct sdhci_host *host); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, From fb6a81715b4e76f4c0711590646aadf6d8db711b Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 30 Jul 2013 19:08:36 +0530 Subject: [PATCH 065/472] mmc: sdhci-msm: Enable auto-calibration using auto-cmd21 This patch enables automatic calibration for eMMC devices in using auto-command21. This feature is disabled by default and can be enabled using the sysfs attribute 'enable_auto_cmd21'. CRs-Fixed: 516314 Change-Id: I020c61cb9dee56c0ebe37864e67e4753ddee1adc Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 79468051a231..4cdf66f5d147 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -85,6 +85,7 @@ #define CORE_HC_MCLK_SEL_DFLT (2 << 8) #define CORE_HC_MCLK_SEL_HS400 (3 << 8) #define CORE_HC_MCLK_SEL_MASK (3 << 8) +#define CORE_HC_AUTO_CMD21_EN (1 << 6) #define CORE_IO_PAD_PWR_SWITCH (1 << 16) #define CORE_HC_SELECT_IN_EN (1 << 18) #define CORE_HC_SELECT_IN_HS400 (6 << 19) @@ -295,6 +296,8 @@ struct sdhci_msm_host { bool tuning_done; bool calibration_done; u8 saved_tuning_phase; + bool en_auto_cmd21; + struct device_attribute auto_cmd21_attr; }; enum vdd_io_level { @@ -338,6 +341,93 @@ out: return rc; } +/* + * Enable CDR to track changes of DAT lines and adjust sampling + * point according to voltage/temperature variations + */ +static int msm_enable_cdr_cm_sdc4_dll(struct sdhci_host *host) +{ + int rc = 0; + u32 config; + + config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + config |= CORE_CDR_EN; + config &= ~(CORE_CDR_EXT_EN | CORE_CK_OUT_EN); + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + + rc = msm_dll_poll_ck_out_en(host, 0); + if (rc) + goto err; + + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) | + CORE_CK_OUT_EN), host->ioaddr + CORE_DLL_CONFIG); + + rc = msm_dll_poll_ck_out_en(host, 1); + if (rc) + goto err; + goto out; +err: + pr_err("%s: %s: failed\n", mmc_hostname(host->mmc), __func__); +out: + return rc; +} + +static ssize_t store_auto_cmd21(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + u32 tmp; + unsigned long flags; + + if (!kstrtou32(buf, 0, &tmp)) { + spin_lock_irqsave(&host->lock, flags); + msm_host->en_auto_cmd21 = !!tmp; + spin_unlock_irqrestore(&host->lock, flags); + } + return count; +} + +static ssize_t show_auto_cmd21(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + return snprintf(buf, PAGE_SIZE, "%d\n", msm_host->en_auto_cmd21); +} + +/* MSM auto-tuning handler */ +static int sdhci_msm_config_auto_tuning_cmd(struct sdhci_host *host, + bool enable, + u32 type) +{ + int rc = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + u32 val = 0; + + if (!msm_host->en_auto_cmd21) + return 0; + + if (type == MMC_SEND_TUNING_BLOCK_HS200) + val = CORE_HC_AUTO_CMD21_EN; + else + return 0; + + if (enable) { + rc = msm_enable_cdr_cm_sdc4_dll(host); + writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | + val, host->ioaddr + CORE_VENDOR_SPEC); + } else { + writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & + ~val, host->ioaddr + CORE_VENDOR_SPEC); + } + return rc; +} + static int msm_config_cm_dll_phase(struct sdhci_host *host, u8 phase) { int rc = 0; @@ -2537,6 +2627,7 @@ static struct sdhci_ops sdhci_msm_ops = { .get_min_clock = sdhci_msm_get_min_clock, .get_max_clock = sdhci_msm_get_max_clock, .dump_vendor_regs = sdhci_msm_dump_vendor_regs, + .config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -2850,6 +2941,19 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) goto remove_max_bus_bw_file; } + + msm_host->auto_cmd21_attr.show = show_auto_cmd21; + msm_host->auto_cmd21_attr.store = store_auto_cmd21; + sysfs_attr_init(&msm_host->auto_cmd21_attr.attr); + msm_host->auto_cmd21_attr.attr.name = "enable_auto_cmd21"; + msm_host->auto_cmd21_attr.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(&pdev->dev, &msm_host->auto_cmd21_attr); + if (ret) { + pr_err("%s: %s: failed creating auto-cmd21 attr: %d\n", + mmc_hostname(host->mmc), __func__, ret); + device_remove_file(&pdev->dev, &msm_host->auto_cmd21_attr); + } + /* Successful initialization */ goto out; From 3118da1c322234006a43f84aef70a399ac3ce643 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 24 Jun 2013 09:55:33 +0530 Subject: [PATCH 066/472] mmc: sdhci-msm: Enable controller power save feature Enable power save feature within controller by setting bit 1 in vendor specific register (0x10C). This allows controller to disable SD clock when bus is idle to save power. Change-Id: I916a5a414adb3f21dc3a75f3f86c3a81d6956dc8 Signed-off-by: Sahitya Tummala Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4cdf66f5d147..a25f3e83037c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2391,6 +2391,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) struct sdhci_msm_host *msm_host = pltfm_host->priv; struct mmc_ios curr_ios = host->mmc->ios; u32 sup_clock, ddr_clock; + bool curr_pwrsave; if (!clock) { sdhci_msm_prepare_clocks(host, false); @@ -2402,6 +2403,22 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) if (rc) goto out; + curr_pwrsave = !!(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & + CORE_CLK_PWRSAVE); + if ((msm_host->clk_rate > 400000) && + !curr_pwrsave && mmc_host_may_gate_card(host->mmc->card)) + writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + | CORE_CLK_PWRSAVE, + host->ioaddr + CORE_VENDOR_SPEC); + /* + * Disable pwrsave for a newly added card if doesn't allow clock + * gating. + */ + else if (curr_pwrsave && !mmc_host_may_gate_card(host->mmc->card)) + writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) + & ~CORE_CLK_PWRSAVE, + host->ioaddr + CORE_VENDOR_SPEC); + sup_clock = sdhci_msm_get_sup_clk_rate(host, clock); if ((curr_ios.timing == MMC_TIMING_UHS_DDR50) || (curr_ios.timing == MMC_TIMING_MMC_HS400)) { From 2ba264341c60b4a5ab95ef75575a92b941cedc9d Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 29 Aug 2013 16:21:08 +0530 Subject: [PATCH 067/472] mmc: sdhci-msm: Fix issue with power save bit enablement The power save bit is currently enabled based on the clock rate (clk_rate > 400KHz) within struct sdhci_msm_host. But this clk_rate is updated with the latest value down in this function sdhci_msm_set_clock(). So during runtime/system resume when the card is still in initialization phase, the power save bit is getting enabled when sdhci_msm_set_clock() is called for the first time based on the previous rate which is wrong. Change-Id: I05dc8a4a760f658935de3831aaf8dd3b2b996466 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index a25f3e83037c..7bdc85437854 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2405,7 +2405,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) curr_pwrsave = !!(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & CORE_CLK_PWRSAVE); - if ((msm_host->clk_rate > 400000) && + if ((clock > 400000) && !curr_pwrsave && mmc_host_may_gate_card(host->mmc->card)) writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | CORE_CLK_PWRSAVE, From c2037aa944ca2f3decbefa620d4392652b51f286 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 20 Aug 2013 15:32:09 +0530 Subject: [PATCH 068/472] mmc: sdhci-msm: Fix issue with 1.8v switch sequence in 3.10 kernel The SD3.0 voltage switch sequence to 1.8v would involve stopping the SDCLK before changing the voltage level and with recent changes in 3.10 kernel, the peripheral clocks are also getting disabled instead of just stopping the clock to the card. This patch makes sure this doesn't happen by marking the flag card_clock_off in struct mmc_host before starting the voltage switch sequence and checking it in host controller driver. Change-Id: If62378ba1dd369e69524365a4421d57317c22ca2 Signed-off-by: Sahitya Tummala --- drivers/mmc/core/core.c | 5 +++++ drivers/mmc/host/sdhci-msm.c | 12 ++++++++++-- include/linux/mmc/host.h | 5 +++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 1a662cf93cb0..536cb3655d49 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1707,6 +1707,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr) * During a signal voltage level switch, the clock must be gated * for 5 ms according to the SD spec */ + host->card_clock_off = true; clock = host->ios.clock; host->ios.clock = 0; mmc_set_ios(host); @@ -1717,6 +1718,9 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr) * sent CMD11, so a power cycle is required anyway */ err = -EAGAIN; + host->ios.clock = clock; + mmc_set_ios(host); + host->card_clock_off = false; goto power_cycle; } @@ -1725,6 +1729,7 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr) host->ios.clock = clock; mmc_set_ios(host); + host->card_clock_off = false; /* Wait for at least 1 ms according to spec */ mmc_delay(1); diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 7bdc85437854..3c5e546016b6 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2347,10 +2347,18 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) mb(); } else if (!enable && atomic_read(&msm_host->clks_on)) { - pr_debug("%s: request to disable clocks\n", - mmc_hostname(host->mmc)); sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL); mb(); + /* + * During 1.8V signal switching the clock source must + * still be ON as it requires accessing SDHC + * registers (SDHCi host control2 register bit 3 must + * be written and polled after stopping the SDCLK). + */ + if (host->mmc->card_clock_off) + return 0; + pr_debug("%s: request to disable clocks\n", + mmc_hostname(host->mmc)); if (!IS_ERR_OR_NULL(msm_host->sleep_clk)) clk_disable_unprepare(msm_host->sleep_clk); if (!IS_ERR_OR_NULL(msm_host->ff_clk)) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 1939cd47b3aa..63a10df66b87 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -404,6 +404,11 @@ struct mmc_host { } embedded_sdio_data; #endif + /* + * Set to 1 to just stop the SDCLK to the card without + * actually disabling the clock from it's source. + */ + bool card_clock_off; unsigned long private[0] ____cacheline_aligned; }; From 6d26067be7decc88beec36e6081507632cad15e1 Mon Sep 17 00:00:00 2001 From: Stepan Moskovchenko Date: Fri, 13 Sep 2013 22:19:33 -0700 Subject: [PATCH 069/472] mmc: sdhci-msm: set core in proper mode before reset During probe disable the HC mode since the reset is done in SDCC mode. HC mode will get set after the reset is complete before the rest of the initialization is done. Change-Id: I1fdc633c218447c15c8caad24e2805e7510088f2 Signed-off-by: Stepan Moskovchenko --- drivers/mmc/host/sdhci-msm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3c5e546016b6..e8c4279c1cbd 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2798,6 +2798,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto vreg_deinit; } + /* Unset HC_MODE_EN bit in HC_MODE register */ + writel_relaxed(0, (msm_host->core_mem + CORE_HC_MODE)); + /* Set SW_RST bit in POWER register (Offset 0x0) */ writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | CORE_SW_RST, msm_host->core_mem + CORE_POWER); From a45d072d68387b27af213e9c49d7bfb806d8ac9a Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 10 Jan 2013 21:05:49 +0530 Subject: [PATCH 070/472] mmc: sdhci: add support for configurable request size Some platform drivers support more than 128 adma descriptors and allows more than 512KB maximum request size per transfer. Add a callback to get maximum supported adma descriptors from platform driver instead of limiting host capabilities to 128 descriptors. Change-Id: I0ac0ffbd0e792a76931e21b321b39c35195ac8d6 Signed-off-by: Asutosh Das [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 2 +- drivers/mmc/host/sdhci.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 724a574ed4e9..24f3cac7879f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -819,7 +819,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) return; /* Sanity checks */ - BUG_ON(data->blksz * data->blocks > 524288); + BUG_ON(data->blksz * data->blocks > host->mmc->max_req_size); BUG_ON(data->blksz > host->mmc->max_blk_size); BUG_ON(data->blocks > 65535); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 63106bb28782..6d8ddfc07fb5 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -550,6 +550,10 @@ struct sdhci_host { size_t adma_table_sz; /* ADMA descriptor table size */ size_t align_buffer_sz; /* Bounce buffer size */ + unsigned int adma_desc_sz; /* ADMA descriptor table size */ + unsigned int align_buf_sz; /* Bounce buffer size */ + unsigned int adma_max_desc; /* Max ADMA descriptos (max sg segments) */ + dma_addr_t adma_addr; /* Mapped ADMA descr. table */ dma_addr_t align_addr; /* Mapped bounce buffer */ @@ -624,6 +628,7 @@ struct sdhci_ops { void (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); void (*hw_reset)(struct sdhci_host *host); void (*adma_workaround)(struct sdhci_host *host, u32 intmask); + unsigned int (*get_max_segments)(void); void (*platform_init)(struct sdhci_host *host); #define REQ_BUS_OFF (1 << 0) #define REQ_BUS_ON (1 << 1) From f9359ba80e55fba4612d2b627c079bcc61e60444 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 24 May 2013 08:47:26 +0530 Subject: [PATCH 071/472] mmc: sdhci: initialize sdhci_host lock in sdhci_alloc_host() Currently, the sdhci host lock is initialized in sdhci_add_host() but there can be a case where it is required even before that. Hence, initialize it in sdhci_alloc_host() where sdhci_host structure is allocated. Change-Id: If99d82679c07bc2d36e0aad9354757288aa400b8 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 24f3cac7879f..4930d9f09d7f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3181,6 +3181,8 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, host = mmc_priv(mmc); host->mmc = mmc; + spin_lock_init(&host->lock); + return host; } @@ -3622,8 +3624,6 @@ int sdhci_add_host(struct sdhci_host *host) return -ENODEV; } - spin_lock_init(&host->lock); - /* * Maximum number of segments. Depends on if the hardware * can do scatter/gather or not. From 29ff0494897e8afdf44d42e095174de1685eeb23 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 25 Feb 2013 15:45:32 +0530 Subject: [PATCH 072/472] mmc: sdhci: Add new callback to enable/disable CDR MSM SDHCI has CDR mechanism (clock-data recovery) to automatically adjust the sampling point based on the voltage/temperature variations. This must be enabled for only read. Hence, add new host op toggle_cdr to control CDR. Change-Id: I75940cfca15fe88de6d46fe58cb33620a3b7ced1 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 7 ++++++- drivers/mmc/host/sdhci.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4930d9f09d7f..0a7a599588df 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1009,8 +1009,13 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host, } } - if (data->flags & MMC_DATA_READ) + if (data->flags & MMC_DATA_READ) { mode |= SDHCI_TRNS_READ; + if (host->ops->toggle_cdr) + host->ops->toggle_cdr(host, true); + } + if (host->ops->toggle_cdr && (data->flags & MMC_DATA_WRITE)) + host->ops->toggle_cdr(host, false); if (host->flags & SDHCI_REQ_USE_DMA) mode |= SDHCI_TRNS_DMA; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 6d8ddfc07fb5..1430ca98dd94 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -641,6 +641,7 @@ struct sdhci_ops { bool enable, u32 type); void (*dump_vendor_regs)(struct sdhci_host *host); + void (*toggle_cdr)(struct sdhci_host *host, bool enable); void (*voltage_switch)(struct sdhci_host *host); int (*select_drive_strength)(struct sdhci_host *host, struct mmc_card *card, From 9df7e24765adf7c69bee96171ee24571024ea8aa Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Tue, 17 Sep 2013 23:55:40 -0700 Subject: [PATCH 073/472] mmc: sdhci-msm: set dma mask for lpae/64-bit machines On machines that support more than 32-bit address via lpae or 64-bit buses set the dma mask as 64-bit. Change-Id: Ida88f3999fd5e7d09ebe73bb3481d3f1f4cf30c2 Signed-off-by: Krishna Konda --- drivers/mmc/host/sdhci-msm.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e8c4279c1cbd..2a5f2cc6b758 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -91,6 +91,9 @@ #define CORE_HC_SELECT_IN_HS400 (6 << 19) #define CORE_HC_SELECT_IN_MASK (7 << 19) +#define CORE_VENDOR_SPEC_CAPABILITIES0 0x11C +#define CORE_SYS_BUS_SUPPORT_64_BIT 28 + #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 @@ -2936,7 +2939,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) } } - if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) { + if ((sdhci_readl(host, SDHCI_CAPABILITIES) & SDHCI_CAN_64BIT) && + (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(64)))) { + host->dma_mask = DMA_BIT_MASK(64); + mmc_dev(host->mmc)->dma_mask = &host->dma_mask; + } else if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) { host->dma_mask = DMA_BIT_MASK(32); mmc_dev(host->mmc)->dma_mask = &host->dma_mask; } else { From 2c530476d3898b8ab1d3d12df02bf96faf2e43bb Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 21 Oct 2013 17:17:03 +0530 Subject: [PATCH 074/472] mmc: sdhci: Use max timeout and skip timeout calculation Several data-timeout issues were seen, most of which were due to the card taking a longer time to respond. This patch increases the timeout of the controller to 0xF i.e. max possible. Change-Id: I6739de3eb5d9cccf8e39d9dc4730056782334162 CRs-Fixed: 536832 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0a7a599588df..0394eaf50d86 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -717,6 +717,12 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) if (host->quirks & SDHCI_QUIRK_BROKEN_TIMEOUT_VAL) return 0xE; + /* During initialization, don't use max timeout as the clock is slow */ + if ((host->quirks2 & SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT) && + (host->clock > 400000)) { + return 0xF; + } + /* Unspecified timeout, assume max */ if (!data && !cmd->busy_timeout) return 0xE; From deba741527aa75d03e2d0d1f6010b5bae538a4ac Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 8 Nov 2013 12:31:48 +0530 Subject: [PATCH 075/472] mmc: sdhci: Turn on controller clocks and card power at MMC_POWER_UP Currently, the clock to the card is enabled prior to enabling the power to card. Specification requires that the power be supplied first and then a delay of 10ms and then clock be provided to the card. In this, during MMC_POWER_UP mode, the controller clocks would be ON and the power would be supplied to the card. In the MMC_POWER_ON mode, the clocks to the card would be enabled and the rate set. A callback has been provided to facilitate the enabling of controller clocks. CRs-Fixed: 567658 Change-Id: I2d66eae1581b9b136faaba4cafc330aeb6a3f364 Signed-off-by: Asutosh Das [venkatg@codeaurora.org: Fix sdhci_set_power fn signature as it changed in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 24 +++++++++++++++++++++++- drivers/mmc/host/sdhci.h | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 0394eaf50d86..abde70324954 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1693,6 +1693,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) unsigned long flags; u8 ctrl; struct mmc_host *mmc = host->mmc; + int ret; if (host->flags & SDHCI_DEVICE_DEAD) { if (!IS_ERR(mmc->supply.vmmc) && @@ -1706,6 +1707,25 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) sdhci_enable_preset_value(host, false); + /* + * The controller clocks may be off during power-up and we may end up + * enabling card clock before giving power to the card. Hence, during + * MMC_POWER_UP enable the controller clock and turn-on the regulators. + * The mmc_power_up would provide the necessary delay before turning on + * the clocks to the card. + */ + if (ios->power_mode & MMC_POWER_UP) { + if (host->ops->enable_controller_clock) { + ret = host->ops->enable_controller_clock(host); + if (ret) { + pr_err("%s: enabling controller clock: failed: %d\n", + mmc_hostname(host->mmc), ret); + } else { + sdhci_set_power(host, ios->power_mode, ios->vdd); + } + } + } + spin_lock_irqsave(&host->lock, flags); if (!ios->clock || ios->clock != host->clock) { spin_unlock_irqrestore(&host->lock, flags); @@ -1727,7 +1747,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } spin_unlock_irqrestore(&host->lock, flags); - if (ios->power_mode & (MMC_POWER_UP | MMC_POWER_ON)) + if (!host->ops->enable_controller_clock && (ios->power_mode & + (MMC_POWER_UP | + MMC_POWER_ON))) sdhci_set_power(host, ios->power_mode, ios->vdd); spin_lock_irqsave(&host->lock, flags); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 1430ca98dd94..f6c1ce0b0475 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -640,6 +640,7 @@ struct sdhci_ops { int (*config_auto_tuning_cmd)(struct sdhci_host *host, bool enable, u32 type); + int (*enable_controller_clock)(struct sdhci_host *host); void (*dump_vendor_regs)(struct sdhci_host *host); void (*toggle_cdr)(struct sdhci_host *host, bool enable); void (*voltage_switch)(struct sdhci_host *host); From 21512ff94960e3d9bfdfae860dcbdca698578f95 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 8 Nov 2013 12:33:47 +0530 Subject: [PATCH 076/472] mmc: sdhci-msm: enable controller clocks at MMC_POWER_UP A callback to turn-on the controller clocks is implemented. This callback would ensure that the pclk and mclk are enabled but the output clock to the card is disabled. CRs-Fixed: 567658 Change-Id: Ic97e600a6388fb64f1267a097b201f31d114a1fb Signed-off-by: Asutosh Das Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/host/sdhci-msm.c | 90 +++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 2a5f2cc6b758..d4517938cb64 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -301,6 +301,7 @@ struct sdhci_msm_host { u8 saved_tuning_phase; bool en_auto_cmd21; struct device_attribute auto_cmd21_attr; + atomic_t controller_clock; }; enum vdd_io_level { @@ -2297,6 +2298,50 @@ static unsigned int sdhci_msm_get_sup_clk_rate(struct sdhci_host *host, return sel_clk; } +static int sdhci_msm_enable_controller_clock(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int rc = 0; + + if (atomic_read(&msm_host->controller_clock)) + return 0; + + sdhci_msm_bus_voting(host, 1); + + if (!IS_ERR(msm_host->pclk)) { + rc = clk_prepare_enable(msm_host->pclk); + if (rc) { + pr_err("%s: %s: failed to enable the pclk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto remove_vote; + } + } + + rc = clk_prepare_enable(msm_host->clk); + if (rc) { + pr_err("%s: %s: failed to enable the host-clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_pclk; + } + + atomic_set(&msm_host->controller_clock, 1); + pr_debug("%s: %s: enabled controller clock\n", + mmc_hostname(host->mmc), __func__); + goto out; + +disable_pclk: + if (!IS_ERR(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); +remove_vote: + if (msm_host->msm_bus_vote.client_handle) + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); +out: + return rc; +} + + + static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -2307,36 +2352,32 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) pr_debug("%s: request to enable clocks\n", mmc_hostname(host->mmc)); - sdhci_msm_bus_voting(host, 1); + /* + * The bus-width or the clock rate might have changed + * after controller clocks are enbaled, update bus vote + * in such case. + */ + if (atomic_read(&msm_host->controller_clock)) + sdhci_msm_bus_voting(host, 1); + + rc = sdhci_msm_enable_controller_clock(host); + if (rc) + goto remove_vote; if (!IS_ERR_OR_NULL(msm_host->bus_clk)) { rc = clk_prepare_enable(msm_host->bus_clk); if (rc) { pr_err("%s: %s: failed to enable the bus-clock with error %d\n", mmc_hostname(host->mmc), __func__, rc); - goto remove_vote; + goto disable_controller_clk; } } - if (!IS_ERR(msm_host->pclk)) { - rc = clk_prepare_enable(msm_host->pclk); - if (rc) { - pr_err("%s: %s: failed to enable the pclk with error %d\n", - mmc_hostname(host->mmc), __func__, rc); - goto disable_bus_clk; - } - } - rc = clk_prepare_enable(msm_host->clk); - if (rc) { - pr_err("%s: %s: failed to enable the host-clk with error %d\n", - mmc_hostname(host->mmc), __func__, rc); - goto disable_pclk; - } if (!IS_ERR(msm_host->ff_clk)) { rc = clk_prepare_enable(msm_host->ff_clk); if (rc) { pr_err("%s: %s: failed to enable the ff_clk with error %d\n", mmc_hostname(host->mmc), __func__, rc); - goto disable_clk; + goto disable_bus_clk; } } if (!IS_ERR(msm_host->sleep_clk)) { @@ -2372,6 +2413,7 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); + atomic_set(&msm_host->controller_clock, 0); sdhci_msm_bus_voting(host, 0); } atomic_set(&msm_host->clks_on, enable); @@ -2379,15 +2421,15 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) disable_ff_clk: if (!IS_ERR_OR_NULL(msm_host->ff_clk)) clk_disable_unprepare(msm_host->ff_clk); -disable_clk: - if (!IS_ERR_OR_NULL(msm_host->clk)) - clk_disable_unprepare(msm_host->clk); -disable_pclk: - if (!IS_ERR_OR_NULL(msm_host->pclk)) - clk_disable_unprepare(msm_host->pclk); disable_bus_clk: if (!IS_ERR_OR_NULL(msm_host->bus_clk)) clk_disable_unprepare(msm_host->bus_clk); +disable_controller_clk: + if (!IS_ERR_OR_NULL(msm_host->clk)) + clk_disable_unprepare(msm_host->clk); + if (!IS_ERR_OR_NULL(msm_host->pclk)) + clk_disable_unprepare(msm_host->pclk); + atomic_set(&msm_host->controller_clock, 0); remove_vote: if (msm_host->msm_bus_vote.client_handle) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); @@ -2656,6 +2698,7 @@ static struct sdhci_ops sdhci_msm_ops = { .get_max_clock = sdhci_msm_get_max_clock, .dump_vendor_regs = sdhci_msm_dump_vendor_regs, .config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd, + .enable_controller_clock = sdhci_msm_enable_controller_clock, }; static int sdhci_msm_probe(struct platform_device *pdev) @@ -2734,6 +2777,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (ret) goto bus_clk_disable; } + atomic_set(&msm_host->controller_clock, 1); /* Setup SDC MMC clock */ msm_host->clk = devm_clk_get(&pdev->dev, "core_clk"); From 7eba9c3c954ce8031b7d215391ee994aa8df1716 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Mon, 11 Nov 2013 01:32:21 +0530 Subject: [PATCH 077/472] mmc: sdhci-msm: Add software capabilities for voltage init With SDCC5 the capabilities register is not advertising the 3.0 voltage features (except for 8974 and 8084). So add the software capabilities for voltage initialization for rest of the targets with SDCC5. CRs-Fixed: 568227 Change-Id: Ida53f5ad7249cd0cd8428b4839dfd932b04e31fa Signed-off-by: Pratibhasagar V --- drivers/mmc/host/sdhci-msm.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d4517938cb64..4a32b0593ff3 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -92,6 +92,10 @@ #define CORE_HC_SELECT_IN_MASK (7 << 19) #define CORE_VENDOR_SPEC_CAPABILITIES0 0x11C +#define CORE_8_BIT_SUPPORT (1 << 18) +#define CORE_3_3V_SUPPORT (1 << 24) +#define CORE_3_0V_SUPPORT (1 << 25) +#define CORE_1_8V_SUPPORT (1 << 26) #define CORE_SYS_BUS_SUPPORT_64_BIT 28 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 @@ -139,6 +143,8 @@ #define INVALID_TUNING_PHASE -1 +#define CORE_VERSION_TARGET_MASK 0x000000FF + static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, @@ -2701,6 +2707,31 @@ static struct sdhci_ops sdhci_msm_ops = { .enable_controller_clock = sdhci_msm_enable_controller_clock, }; +static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, + struct sdhci_host *host) +{ + u32 version, caps; + u16 minor; + u8 major; + + version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION); + major = (version & CORE_VERSION_MAJOR_MASK) >> + CORE_VERSION_MAJOR_SHIFT; + minor = version & CORE_VERSION_TARGET_MASK; + + /* + * Starting with SDCC 5 controller (core major version = 1) + * controller won't advertise 3.0v features except for + * some targets. + */ + if (major >= 1 && minor != 0x11 && minor != 0x12) { + caps = CORE_3_0V_SUPPORT; + writel_relaxed( + (readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | + caps), host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); + } +} + static int sdhci_msm_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -2871,6 +2902,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_HC_MODE) | FF_CLK_SW_RST_DIS, msm_host->core_mem + CORE_HC_MODE); + sdhci_set_default_hw_caps(msm_host, host); /* * CORE_SW_RST above may trigger power irq if previous status of PWRCTL * was either BUS_ON or IO_HIGH_V. So before we enable the power irq From ef8a87082783aa07092e177b88ec1cc675136909 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Mon, 9 Dec 2013 20:42:32 +0530 Subject: [PATCH 078/472] mmc: sdhci-msm: Add software capabilities for 8-bit slot With SDCC5 the capabilities register is not advertising the 8-bit capability feature (except for 8974 and 8084). So add the software capabilities for 8-bit slot for rest of the targets with SDCC5. Change-Id: I288292f37d77507bf5aaa44bf156496b4df87234 Signed-off-by: Pratibhasagar V --- drivers/mmc/host/sdhci-msm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4a32b0593ff3..d29ed9f3baec 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2721,11 +2721,13 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, /* * Starting with SDCC 5 controller (core major version = 1) - * controller won't advertise 3.0v features except for + * controller won't advertise 3.0v and 8-bit features except for * some targets. */ if (major >= 1 && minor != 0x11 && minor != 0x12) { caps = CORE_3_0V_SUPPORT; + if (msm_host->pdata->mmc_bus_width == MMC_CAP_8_BIT_DATA) + caps |= CORE_8_BIT_SUPPORT; writel_relaxed( (readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | caps), host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); From e8279c90212b2dc999e84e23696818e2d249dbad Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 16 Dec 2013 10:59:03 +0530 Subject: [PATCH 079/472] mmc: sdhci: do not use maximum timeout for all cards Remove maximum timeout for all cards, since this causes a long resume time for SD cards. Now only Hynix cards would have this maximum timeout and this would be decided at the core layer. Change-Id: I758f9e5ecf407aba371928a86c313cf69e3cda63 CRs-fixed: 587284 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index abde70324954..34678c4e9c31 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -717,12 +717,6 @@ static u8 sdhci_calc_timeout(struct sdhci_host *host, struct mmc_command *cmd) if (host->quirks & SDHCI_QUIRK_BROKEN_TIMEOUT_VAL) return 0xE; - /* During initialization, don't use max timeout as the clock is slow */ - if ((host->quirks2 & SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT) && - (host->clock > 400000)) { - return 0xF; - } - /* Unspecified timeout, assume max */ if (!data && !cmd->busy_timeout) return 0xE; From d90d5901faba22e9bc67ade166de12b63412c44c Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Fri, 10 Jan 2014 10:58:54 +0530 Subject: [PATCH 080/472] mmc: sdhci-msm: Fix clock gating while voltage switch is in progress CLK_PWRSAVE bit in vendor specific register gates the output clock to card automatically if there are no data/cmd operations. According the SD3.0 voltage switch sequence the host should provide clock to the card for atleast one millisecond before DAT[3:0] lines are pulled high by the card. In this case if power save bit is enabled it might auto-gate clocks even before the card completes voltage switch sequence. Fix this by disabling power save operation when the clocks are turned off and enable only when clock rate is >400KHz i.e., end of initialization. CRs-Fixed: 589992 Change-Id: If82d6d2e303b8d1189b76712e514f41fe6e2cf8b Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/host/sdhci-msm.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d29ed9f3baec..4ebbcfaa2fab 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2,7 +2,7 @@ * drivers/mmc/host/sdhci-msm.c - Qualcomm Technologies, Inc. MSM SDHCI Platform * driver source file * - * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -2453,6 +2453,12 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) bool curr_pwrsave; if (!clock) { + /* + * disable pwrsave to ensure clock is not auto-gated until + * the rate is >400KHz (initialization complete). + */ + writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & + ~CORE_CLK_PWRSAVE, host->ioaddr + CORE_VENDOR_SPEC); sdhci_msm_prepare_clocks(host, false); host->clock = clock; goto out; From d621731e5f48f9fc29c5058163e7668b63060f9e Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 21 Jan 2014 17:22:05 +0530 Subject: [PATCH 081/472] mmc: sdhci: Fix possible spec. violation during voltage switch sequence With commit 0797e5f145 (mmc: core: Fixup signal voltage switch), voltage switch sequence for UHS-I cards is broken if used with sdhci driver. The commit expects the SD clock to be disabled when mmc_set_ios() is issued but sdhci_do_set_ios() re-enables the SD clock for few cycles after disabling which is a specification violation during voltage switch sequence. This failure is observed only for a small group of cards where they ultimately fall-back into high-speed mode even if UHS-I modes are supported. Change-Id: Ie275326627a84bfcd4352637a043296c01c175a6 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/host/sdhci.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 34678c4e9c31..022c6319a0c8 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1814,7 +1814,8 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); /* Re-enable SD Clock */ - host->ops->set_clock(host, host->clock); + if (ios->clock) + host->ops->set_clock(host, host->clock); } /* Reset SD Clock Enable */ @@ -1841,7 +1842,8 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } /* Re-enable SD Clock */ - host->ops->set_clock(host, host->clock); + if (ios->clock) + host->ops->set_clock(host, host->clock); } else sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); From 5e8e7c2098abd2d64c8bb843a4794817de8709f3 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Thu, 21 Nov 2013 21:07:21 +0530 Subject: [PATCH 082/472] mmc: sdhci: Add support for pinctrl interface Add support for Linux pin control framework while also supporting the older TLMM configuration for backward compatibility CRs-Fixed: 568232 Change-Id: Ib6b8f41fd6ced9aa62c980d7e4a73469603cbc5b Signed-off-by: Pratibhasagar V --- .../devicetree/bindings/mmc/sdhci-msm.txt | 15 +++ drivers/mmc/host/sdhci-msm.c | 96 +++++++++++++++++-- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 82cf9b0d1c5e..643944fa8565 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -52,6 +52,11 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag Tlmm pins are specified as and starting with eMMC5.0 as + - Refer to "Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt" + for following optional properties: + - pinctrl-names + - pinctrl-0, pinctrl-1,.. pinctrl-n + Example: aliases { @@ -76,6 +81,11 @@ Example: qcom,vdd-io-voltage-level = <1800000 2950000>; qcom,vdd-io-current-level = <6 22000>; + pinctrl-names = "active", "sleep"; + pinctrl-0 = <&sdc1_clk_on &sdc1_cmd_on &sdc1_data_on>; + pinctrl-1 = <&sdc1_clk_off &sdc1_cmd_on &sdc1_data_on>; + + qcom,bus-width = <4>; qcom,nonremovable; qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v"; @@ -99,6 +109,11 @@ Example: vdd-supply = <&pm8941_l21>; vdd-io-supply = <&pm8941_l13>; + pinctrl-names = "active", "sleep"; + pinctrl-0 = <&sdc2_clk_on &sdc2_cmd_on &sdc2_data_on>; + pinctrl-1 = <&sdc2_clk_off &sdc2_cmd_on &sdc2_data_on>; + + qcom,bus-width = <4>; qcom,pad-pull-on = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */ diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4ebbcfaa2fab..3b5f9032fcd5 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -34,6 +34,8 @@ #include #include #include +#include +#include #include #include @@ -245,11 +247,16 @@ struct sdhci_msm_pin_data { * = 0 if controller has dedicated MSM pads */ u8 is_gpio; - bool cfg_sts; struct sdhci_msm_gpio_data *gpio_data; struct sdhci_msm_pad_data *pad_data; }; +struct sdhci_pinctrl_data { + struct pinctrl *pctrl; + struct pinctrl_state *pins_active; + struct pinctrl_state *pins_sleep; +}; + struct sdhci_msm_bus_voting_data { struct msm_bus_scale_pdata *bus_pdata; unsigned int *bw_vecs; @@ -266,7 +273,9 @@ struct sdhci_msm_pltfm_data { unsigned long mmc_bus_width; struct sdhci_msm_slot_reg_data *vreg_data; bool nonremovable; + bool pin_cfg_sts; struct sdhci_msm_pin_data *pin_data; + struct sdhci_pinctrl_data *pctrl_data; u32 cpu_dma_latency_us; int status_gpio; /* card detection GPIO that is configured as IRQ */ struct sdhci_msm_bus_voting_data *voting_data; @@ -1049,19 +1058,44 @@ static int sdhci_msm_setup_pad(struct sdhci_msm_pltfm_data *pdata, bool enable) return 0; } +static int sdhci_msm_setup_pinctrl(struct sdhci_msm_pltfm_data *pdata, + bool enable) +{ + int ret = 0; + + if (enable) + ret = pinctrl_select_state(pdata->pctrl_data->pctrl, + pdata->pctrl_data->pins_active); + else + ret = pinctrl_select_state(pdata->pctrl_data->pctrl, + pdata->pctrl_data->pins_sleep); + + if (ret < 0) + pr_err("%s state for pinctrl failed with %d\n", + enable ? "Enabling" : "Disabling", ret); + + return ret; +} + static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable) { int ret = 0; - if (!pdata->pin_data || (pdata->pin_data->cfg_sts == enable)) + if (pdata->pin_cfg_sts == enable) { return 0; + } else if (pdata->pctrl_data) { + ret = sdhci_msm_setup_pinctrl(pdata, enable); + goto out; + } else if (!pdata->pin_data) { + return 0; + } if (pdata->pin_data->is_gpio) ret = sdhci_msm_setup_gpio(pdata, enable); else ret = sdhci_msm_setup_pad(pdata, enable); - +out: if (!ret) - pdata->pin_data->cfg_sts = enable; + pdata->pin_cfg_sts = enable; return ret; } @@ -1323,6 +1357,46 @@ out: return ret; } +static int sdhci_msm_parse_pinctrl_info(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + struct sdhci_pinctrl_data *pctrl_data; + struct pinctrl *pctrl; + int ret = 0; + + /* Try to obtain pinctrl handle */ + pctrl = devm_pinctrl_get(dev); + if (IS_ERR(pctrl)) { + ret = PTR_ERR(pctrl); + goto out; + } + pctrl_data = devm_kzalloc(dev, sizeof(*pctrl_data), GFP_KERNEL); + if (!pctrl_data) { + dev_err(dev, "No memory for sdhci_pinctrl_data\n"); + ret = -ENOMEM; + goto out; + } + pctrl_data->pctrl = pctrl; + /* Look-up and keep the states handy to be used later */ + pctrl_data->pins_active = pinctrl_lookup_state( + pctrl_data->pctrl, "active"); + if (IS_ERR(pctrl_data->pins_active)) { + ret = PTR_ERR(pctrl_data->pins_active); + dev_err(dev, "Could not get active pinstates, err:%d\n", ret); + goto out; + } + pctrl_data->pins_sleep = pinctrl_lookup_state( + pctrl_data->pctrl, "sleep"); + if (IS_ERR(pctrl_data->pins_sleep)) { + ret = PTR_ERR(pctrl_data->pins_sleep); + dev_err(dev, "Could not get sleep pinstates, err:%d\n", ret); + goto out; + } + pdata->pctrl_data = pctrl_data; +out: + return ret; +} + #define GPIO_NAME_MAX_LEN 32 static int sdhci_msm_dt_parse_gpio_info(struct device *dev, struct sdhci_msm_pltfm_data *pdata) @@ -1331,6 +1405,16 @@ static int sdhci_msm_dt_parse_gpio_info(struct device *dev, struct sdhci_msm_pin_data *pin_data; struct device_node *np = dev->of_node; + ret = sdhci_msm_parse_pinctrl_info(dev, pdata); + if (!ret) { + goto out; + } else if (ret == -EPROBE_DEFER) { + dev_err(dev, "Pinctrl framework not registered, err:%d\n", ret); + goto out; + } else { + dev_err(dev, "Parsing Pinctrl failed with %d, falling back on GPIO lib\n", + ret); + } pin_data = devm_kzalloc(dev, sizeof(*pin_data), GFP_KERNEL); if (!pin_data) { dev_err(dev, "No memory for pin_data\n"); @@ -3133,8 +3217,8 @@ static int sdhci_msm_remove(struct platform_device *pdev) sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); - if (pdata->pin_data) - sdhci_msm_setup_pins(pdata, false); + sdhci_msm_setup_pins(pdata, true); + sdhci_msm_setup_pins(pdata, false); if (msm_host->msm_bus_vote.client_handle) { sdhci_msm_bus_cancel_work_and_set_vote(host, 0); From da2e2cbd576e3320c02f7e88b4110e02c5506722 Mon Sep 17 00:00:00 2001 From: Raviv Shvili Date: Tue, 28 Jan 2014 10:03:38 +0200 Subject: [PATCH 083/472] mmc: sdhci: change msm bus header file There is a new header file for 64bit platforms. Change-Id: Iead9ccab1fd0b7249baf670e67872fcffeccf21d Signed-off-by: Raviv Shvili --- drivers/mmc/host/sdhci-msm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3b5f9032fcd5..a32f82f5ee5e 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -36,8 +36,8 @@ #include #include #include +#include #include -#include #include "sdhci-pltfm.h" From c0a43df5f6fd68fa027e85449c9803ad11675dba Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 5 Feb 2014 16:38:23 +0530 Subject: [PATCH 084/472] mmc: sdhci-msm: add default qos If cpu-dma-latency is not defined in dts files, set it to 200usec Change-Id: I27b0357b4d88842a258332250bae66efac3ee5e2 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index a32f82f5ee5e..c6049b7df941 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -191,6 +191,7 @@ struct sdhci_msm_reg_data { bool set_voltage_sup; }; +#define MSM_MMC_DEFAULT_CPU_DMA_LATENCY 200 /* usecs */ /* * This structure keeps information for all the * regulators required for a SDCC slot. @@ -1526,7 +1527,8 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) if (!of_property_read_u32(np, "qcom,cpu-dma-latency-us", &cpu_dma_latency)) pdata->cpu_dma_latency_us = cpu_dma_latency; - + else + pdata->cpu_dma_latency_us = MSM_MMC_DEFAULT_CPU_DMA_LATENCY; if (sdhci_msm_dt_get_array(dev, "qcom,clk-rates", &clk_table, &clk_table_len, 0)) { dev_err(dev, "failed parsing supported clock rates\n"); From 1fc03424501669b8d6df474c42328123a4297055 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Fri, 14 Feb 2014 08:37:47 +0530 Subject: [PATCH 085/472] mmc: sdhci: clear interrupt status during controller reset In some cases, it is possible that the hardware might trigger an interrupt just about the same time the software tries to reset the controller. In such case, the hardware interrupt will be handled after the current thread release spin lock. This leads to spurious interrupt handling after the completion of reset. Change-Id: I75211adee1179b0636a918f5ceb68a072ad02a6c Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/host/sdhci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 022c6319a0c8..51eae3ff259e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -250,6 +250,10 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) (mask & SDHCI_RESET_ALL)) host->ops->check_power_status(host, REQ_BUS_OFF); + /* clear pending normal/error interrupt status */ + sdhci_writel(host, sdhci_readl(host, SDHCI_INT_STATUS), + SDHCI_INT_STATUS); + /* hw clears the bit when it's done */ while (sdhci_readb(host, SDHCI_SOFTWARE_RESET) & mask) { if (timeout == 0) { From 03cfbbc1fc06db50991da48593bf6c2ee7cf092a Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 21 Feb 2014 11:28:36 +0530 Subject: [PATCH 086/472] mmc: sdhci: finish the request if sdhc is in bad state In the current code, if sdhci_check_state returns true a tasklet is scheduled which doesn't complete the request if host->mrq is NULL, which is the case, if sdhci is in bad state. This would make the mmcqd thread wait for completion infinitely. Hence, complete the request if sdhci_check_state returns true instead of scheduling the tasklet. CRs-Fixed: 615537 Change-Id: I8e2950c3999ac3806f9d631c52d86f0dc13b992f Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 51eae3ff259e..5e483e84aee3 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1575,7 +1575,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) mrq->cmd->error = -EIO; if (mrq->data) mrq->data->error = -EIO; - tasklet_schedule(&host->finish_tasklet); + mmc_request_done(host->mmc, mrq); + sdhci_runtime_pm_put(host); return; } From 21a9ae94b51d0c81bedcda704815e7ce153eed2a Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 14 Feb 2014 13:19:01 +0530 Subject: [PATCH 087/472] mmc: sdhci-msm: remove mach/gpio.h This is needed for supporting 64-bit kernel. Change-Id: Id4f60dc15688a6f02f31d77705ad1ef0181a1ce9 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 242 +---------------------------------- 1 file changed, 2 insertions(+), 240 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c6049b7df941..87d9c793f468 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -37,7 +37,6 @@ #include #include #include -#include #include "sdhci-pltfm.h" @@ -214,34 +213,6 @@ struct sdhci_msm_gpio_data { u8 size; }; -struct sdhci_msm_pad_pull { - enum msm_tlmm_pull_tgt no; - u32 val; -}; - -struct sdhci_msm_pad_pull_data { - struct sdhci_msm_pad_pull *on; - struct sdhci_msm_pad_pull *off; - u8 size; -}; - -struct sdhci_msm_pad_drv { - enum msm_tlmm_hdrive_tgt no; - u32 val; -}; - -struct sdhci_msm_pad_drv_data { - struct sdhci_msm_pad_drv *on; - struct sdhci_msm_pad_drv *off; - u8 size; -}; - -struct sdhci_msm_pad_data { - struct sdhci_msm_pad_pull_data *pull; - struct sdhci_msm_pad_drv_data *drv; -}; - - struct sdhci_msm_pin_data { /* * = 1 if controller pins are using gpios @@ -249,7 +220,6 @@ struct sdhci_msm_pin_data { */ u8 is_gpio; struct sdhci_msm_gpio_data *gpio_data; - struct sdhci_msm_pad_data *pad_data; }; struct sdhci_pinctrl_data { @@ -1032,33 +1002,6 @@ free_gpios: return ret; } -static int sdhci_msm_setup_pad(struct sdhci_msm_pltfm_data *pdata, bool enable) -{ - struct sdhci_msm_pad_data *curr; - int i; - - curr = pdata->pin_data->pad_data; - for (i = 0; i < curr->drv->size; i++) { - if (enable) - msm_tlmm_set_hdrive(curr->drv->on[i].no, - curr->drv->on[i].val); - else - msm_tlmm_set_hdrive(curr->drv->off[i].no, - curr->drv->off[i].val); - } - - for (i = 0; i < curr->pull->size; i++) { - if (enable) - msm_tlmm_set_pull(curr->pull->on[i].no, - curr->pull->on[i].val); - else - msm_tlmm_set_pull(curr->pull->off[i].no, - curr->pull->off[i].val); - } - - return 0; -} - static int sdhci_msm_setup_pinctrl(struct sdhci_msm_pltfm_data *pdata, bool enable) { @@ -1092,8 +1035,6 @@ static int sdhci_msm_setup_pins(struct sdhci_msm_pltfm_data *pdata, bool enable) } if (pdata->pin_data->is_gpio) ret = sdhci_msm_setup_gpio(pdata, enable); - else - ret = sdhci_msm_setup_pad(pdata, enable); out: if (!ret) pdata->pin_cfg_sts = enable; @@ -1206,158 +1147,6 @@ static int sdhci_msm_dt_parse_vreg_info(struct device *dev, } /* GPIO/Pad data extraction */ -static int sdhci_msm_dt_get_pad_pull_info(struct device *dev, int id, - struct sdhci_msm_pad_pull_data **pad_pull_data) -{ - int ret = 0, base = 0, len, i; - u32 *tmp; - struct sdhci_msm_pad_pull_data *pull_data; - struct sdhci_msm_pad_pull *pull; - - switch (id) { - case 1: - base = TLMM_PULL_SDC1_CLK; - break; - case 2: - base = TLMM_PULL_SDC2_CLK; - break; - case 3: - base = TLMM_PULL_SDC3_CLK; - break; - case 4: - base = TLMM_PULL_SDC4_CLK; - break; - default: - dev_err(dev, "%s: Invalid slot id\n", __func__); - ret = -EINVAL; - goto out; - } - - pull_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_pad_pull_data), - GFP_KERNEL); - if (!pull_data) { - dev_err(dev, "No memory for msm_mmc_pad_pull_data\n"); - ret = -ENOMEM; - goto out; - } - pull_data->size = 4; /* array size for clk, cmd, data and rclk */ - - /* Allocate on, off configs for clk, cmd, data and rclk */ - pull = devm_kzalloc(dev, 2 * pull_data->size *\ - sizeof(struct sdhci_msm_pad_pull), GFP_KERNEL); - if (!pull) { - dev_err(dev, "No memory for msm_mmc_pad_pull\n"); - ret = -ENOMEM; - goto out; - } - pull_data->on = pull; - pull_data->off = pull + pull_data->size; - - ret = sdhci_msm_dt_get_array(dev, "qcom,pad-pull-on", - &tmp, &len, pull_data->size); - if (ret) - goto out; - - for (i = 0; i < len; i++) { - pull_data->on[i].no = base + i; - pull_data->on[i].val = tmp[i]; - dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, - i, pull_data->on[i].val); - } - - ret = sdhci_msm_dt_get_array(dev, "qcom,pad-pull-off", - &tmp, &len, pull_data->size); - if (ret) - goto out; - - for (i = 0; i < len; i++) { - pull_data->off[i].no = base + i; - pull_data->off[i].val = tmp[i]; - dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, - i, pull_data->off[i].val); - } - - *pad_pull_data = pull_data; -out: - return ret; -} - -static int sdhci_msm_dt_get_pad_drv_info(struct device *dev, int id, - struct sdhci_msm_pad_drv_data **pad_drv_data) -{ - int ret = 0, base = 0, len, i; - u32 *tmp; - struct sdhci_msm_pad_drv_data *drv_data; - struct sdhci_msm_pad_drv *drv; - - switch (id) { - case 1: - base = TLMM_HDRV_SDC1_CLK; - break; - case 2: - base = TLMM_HDRV_SDC2_CLK; - break; - case 3: - base = TLMM_HDRV_SDC3_CLK; - break; - case 4: - base = TLMM_HDRV_SDC4_CLK; - break; - default: - dev_err(dev, "%s: Invalid slot id\n", __func__); - ret = -EINVAL; - goto out; - } - - drv_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_pad_drv_data), - GFP_KERNEL); - if (!drv_data) { - dev_err(dev, "No memory for msm_mmc_pad_drv_data\n"); - ret = -ENOMEM; - goto out; - } - drv_data->size = 3; /* array size for clk, cmd, data */ - - /* Allocate on, off configs for clk, cmd, data */ - drv = devm_kzalloc(dev, 2 * drv_data->size *\ - sizeof(struct sdhci_msm_pad_drv), GFP_KERNEL); - if (!drv) { - dev_err(dev, "No memory msm_mmc_pad_drv\n"); - ret = -ENOMEM; - goto out; - } - drv_data->on = drv; - drv_data->off = drv + drv_data->size; - - ret = sdhci_msm_dt_get_array(dev, "qcom,pad-drv-on", - &tmp, &len, drv_data->size); - if (ret) - goto out; - - for (i = 0; i < len; i++) { - drv_data->on[i].no = base + i; - drv_data->on[i].val = tmp[i]; - dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, - i, drv_data->on[i].val); - } - - ret = sdhci_msm_dt_get_array(dev, "qcom,pad-drv-off", - &tmp, &len, drv_data->size); - if (ret) - goto out; - - for (i = 0; i < len; i++) { - drv_data->off[i].no = base + i; - drv_data->off[i].val = tmp[i]; - dev_dbg(dev, "%s: val[%d]=0x%x\n", __func__, - i, drv_data->off[i].val); - } - - *pad_drv_data = drv_data; -out: - return ret; -} - static int sdhci_msm_parse_pinctrl_info(struct device *dev, struct sdhci_msm_pltfm_data *pdata) { @@ -1402,7 +1191,7 @@ out: static int sdhci_msm_dt_parse_gpio_info(struct device *dev, struct sdhci_msm_pltfm_data *pdata) { - int ret = 0, id = 0, cnt, i; + int ret = 0, cnt, i; struct sdhci_msm_pin_data *pin_data; struct device_node *np = dev->of_node; @@ -1415,6 +1204,7 @@ static int sdhci_msm_dt_parse_gpio_info(struct device *dev, } else { dev_err(dev, "Parsing Pinctrl failed with %d, falling back on GPIO lib\n", ret); + ret = 0; } pin_data = devm_kzalloc(dev, sizeof(*pin_data), GFP_KERNEL); if (!pin_data) { @@ -1456,34 +1246,6 @@ static int sdhci_msm_dt_parse_gpio_info(struct device *dev, pin_data->gpio_data->gpio[i].name, pin_data->gpio_data->gpio[i].no); } - } else { - pin_data->pad_data = - devm_kzalloc(dev, - sizeof(struct sdhci_msm_pad_data), - GFP_KERNEL); - if (!pin_data->pad_data) { - dev_err(dev, - "No memory for pin_data->pad_data\n"); - ret = -ENOMEM; - goto out; - } - - ret = of_alias_get_id(np, "sdhc"); - if (ret < 0) { - dev_err(dev, "Failed to get slot index %d\n", ret); - goto out; - } - id = ret; - - ret = sdhci_msm_dt_get_pad_pull_info( - dev, id, &pin_data->pad_data->pull); - if (ret) - goto out; - ret = sdhci_msm_dt_get_pad_drv_info( - dev, id, &pin_data->pad_data->drv); - if (ret) - goto out; - } pdata->pin_data = pin_data; out: From b0d11d554caef0ad130fd5d92187d0d8ea14aaff Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 28 Oct 2013 15:25:03 -0700 Subject: [PATCH 088/472] mmc: sdhci-msm: improve tuning process In newer hardware, the tuning process is not able to always find a reliable phase to use for sampling data. This is mostly due to hardware. This problem manifests itself as all successful tuning phases, which means that the phase choosen could be a bad one but is not identified as such at the time of tuning. So in order to work around this, rely on optional drive types implemented by the eMMC card, in addition to the mandatory drive type (50 ohm). By using drive types supported by the card, when all phases are sucessful in tuning, change drive type to a different value in the list of supported drive types and retune. This will continue for all tuning phases until a valid one is found. After that the drive type is reset to the default one, if changed. Change-Id: I348fb30daa43d97c58f83f7e4a22019f94ef4954 Signed-off-by: Krishna Konda Signed-off-by: Asutosh Das [venkatg@codeaurora.org: rename cmd_timeout_ms to busy_timeout as part of 3.14 kernel upgrade] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed minor merge conflict & compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 5 +-- drivers/mmc/host/sdhci-msm.c | 76 +++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index dc70b2d7a895..619bd9b1ff13 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1428,6 +1428,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, card->type = MMC_TYPE_MMC; card->rca = 1; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); + host->card = card; } /* @@ -1655,12 +1656,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } - if (!oldcard) - host->card = card; - return 0; free_card: + host->card = NULL; if (!oldcard) mmc_remove_card(card); err: diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 87d9c793f468..e2419293a58d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -146,6 +146,9 @@ #define CORE_VERSION_TARGET_MASK 0x000000FF +#define NUM_TUNING_PHASES 16 +#define MAX_DRV_TYPES_SUPPORTED_HS200 3 + static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, 0xDDFFDFFF, 0xFBFFFBFF, 0xFF7FFFBF, 0xEFBDF777, @@ -831,11 +834,40 @@ out: return ret; } +static void sdhci_msm_set_mmc_drv_type(struct sdhci_host *host, u32 opcode, + u8 drv_type) +{ + struct mmc_command cmd = {0}; + struct mmc_request mrq = {NULL}; + struct mmc_host *mmc = host->mmc; + u8 val = ((drv_type << 4) | 2); + + cmd.opcode = MMC_SWITCH; + cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (EXT_CSD_HS_TIMING << 16) | + (val << 8) | + EXT_CSD_CMD_SET_NORMAL; + cmd.flags = MMC_CMD_AC | MMC_RSP_R1B; + /* 1 sec */ + cmd.busy_timeout = 1000 * 1000; + + memset(cmd.resp, 0, sizeof(cmd.resp)); + cmd.retries = 3; + + mrq.cmd = &cmd; + cmd.data = NULL; + + mmc_wait_for_req(mmc, &mrq); + pr_debug("%s: %s: set card drive type to %d\n", + mmc_hostname(mmc), __func__, + drv_type); +} + int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) { unsigned long flags; int tuning_seq_cnt = 3; - u8 phase, *data_buf, tuned_phases[16], tuned_phase_cnt = 0; + u8 phase, *data_buf, tuned_phases[NUM_TUNING_PHASES], tuned_phase_cnt; const u32 *tuning_block_pattern = tuning_block_64; int size = sizeof(tuning_block_64); /* Tuning pattern size in bytes */ int rc; @@ -843,6 +875,9 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) struct mmc_ios ios = host->mmc->ios; struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; + u8 drv_type = 0; + bool drv_type_changed = false; + struct mmc_card *card = host->mmc->card; /* * Tuning is required for SDR104, HS200 and HS400 cards and @@ -883,6 +918,8 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) } retry: + tuned_phase_cnt = 0; + /* first of all reset the tuning block */ rc = msm_init_cm_dll(host); if (rc) @@ -921,11 +958,46 @@ retry: !memcmp(data_buf, tuning_block_pattern, size)) { /* tuning is successful at this tuning point */ tuned_phases[tuned_phase_cnt++] = phase; - pr_debug("%s: %s: found good phase = %d\n", + pr_debug("%s: %s: found *** good *** phase = %d\n", + mmc_hostname(mmc), __func__, phase); + } else { + pr_debug("%s: %s: found ## bad ## phase = %d\n", mmc_hostname(mmc), __func__, phase); } } while (++phase < 16); + if ((tuned_phase_cnt == NUM_TUNING_PHASES) && mmc_card_mmc(card)) { + /* + * If all phases pass then its a problem. So change the card's + * drive type to a different value, if supported and repeat + * tuning until at least one phase fails. Then set the original + * drive type back. + * + * If all the phases still pass after trying all possible + * drive types, then one of those 16 phases will be picked. + * This is no different from what was going on before the + * modification to change drive type and retune. + */ + pr_debug("%s: tuned phases count: %d\n", mmc_hostname(mmc), + tuned_phase_cnt); + + /* set drive type to other value . default setting is 0x0 */ + while (++drv_type <= MAX_DRV_TYPES_SUPPORTED_HS200) { + if (card->ext_csd.raw_driver_strength & + (1 << drv_type)) { + sdhci_msm_set_mmc_drv_type(host, opcode, + drv_type); + if (!drv_type_changed) + drv_type_changed = true; + goto retry; + } + } + } + + /* reset drive type to default (50 ohm) if changed */ + if (drv_type_changed) + sdhci_msm_set_mmc_drv_type(host, opcode, 0); + if (tuned_phase_cnt) { rc = msm_find_most_appropriate_phase(host, tuned_phases, tuned_phase_cnt); From 1eb54465509ed12c1684dd107971fffddfb7ca3b Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 12 Mar 2014 16:27:32 +0530 Subject: [PATCH 089/472] mmc: sdhci-msm: fix issue with tuning command As of now we ignore CRC/INDEX command failures to tuning command and still wait for data from card but in case the card did not receive the command, it won't send the data. This is causing software request timeout for tuning commands. Hence, software must not ignore such cmd errors for tuning commands but end the request immediately after resetting the controller for both CMD and DATA. Also, wait for 146 MCLK cycles for card to send out the data and thus to move to transfer state. Its corresponding phase must also be considered as bad phase. CRs-fixed: 625855 Change-Id: Ic8462dd9c67e4f18a3ce73d972591772be8c6d10 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 9 ++++++++- drivers/mmc/host/sdhci.c | 23 +---------------------- drivers/mmc/host/sdhci.h | 7 ++++--- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e2419293a58d..4d1d9c326a19 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -954,6 +954,14 @@ retry: memset(data_buf, 0, size); mmc_wait_for_req(mmc, &mrq); + /* + * wait for 146 MCLK cycles for the card to send out the data + * and thus move to TRANS state. As the MCLK would be minimum + * 200MHz when tuning is performed, we need maximum 0.73us + * delay. To be on safer side 1ms delay is given. + */ + if (cmd.error) + usleep_range(1000, 1200); if (!cmd.error && !data.error && !memcmp(data_buf, tuning_block_pattern, size)) { /* tuning is successful at this tuning point */ @@ -2861,7 +2869,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; - host->quirks2 |= SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING; host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 5e483e84aee3..bd9e297419cb 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2640,16 +2640,6 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) host->cmd->error = -EILSEQ; } - if (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING) { - if ((host->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) || - (host->cmd->opcode == MMC_SEND_TUNING_BLOCK)) { - if (intmask & SDHCI_INT_CRC) { - sdhci_reset(host, SDHCI_RESET_CMD); - host->cmd->error = 0; - } - } - } - if (host->cmd->error) { tasklet_schedule(&host->finish_tasklet); return; @@ -2684,16 +2674,6 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) *mask &= ~SDHCI_INT_DATA_END; } - if (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING) { - if ((host->cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) || - (host->cmd->opcode == MMC_SEND_TUNING_BLOCK)) { - if (intmask & SDHCI_INT_CRC) { - sdhci_finish_command(host); - return; - } - } - } - if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host); } @@ -2801,8 +2781,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) host->ops->adma_workaround(host, intmask); } if (host->data->error) { - if ((intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT)) && - (host->quirks2 & SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING)) { + if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT)) { command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); if ((command != MMC_SEND_TUNING_BLOCK_HS200) && diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index f6c1ce0b0475..95487cd95147 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -444,9 +444,6 @@ struct sdhci_host { */ #define SDHCI_QUIRK2_SLOW_INT_CLR (1<<18) -/* Ignore CMD CRC errors for tuning commands */ -#define SDHCI_QUIRK2_IGNORE_CMDCRC_FOR_TUNING (1<<6) - #define SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK (1<<19) #define SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE (1<<20) @@ -490,6 +487,10 @@ struct sdhci_host { * 1-bit mode of SDIO. */ #define SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR (1<<24) + +/* Controller has nonstandard clock management */ +#define SDHCI_QUIRK_NONSTANDARD_CLOCK (1<<25) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From 80a4d5b5a503b6ce32bb0fe0764807d75c5a68ee Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 1 Apr 2014 14:29:13 +0530 Subject: [PATCH 090/472] mmc: sdhci-msm: fix issue with SD card tuning functionality The commit 'e2598c - mmc: sdhci-msm: improve tuning process' introduces NULL pointer dereference bug for SD 3.0 cards when all the tuning phases pass. It uses mmc_card_mmc(card) for checking the card type but card is initialized after tuning is done for SD card and thus resulting in NULL pointer dereference issue. CRs-fixed: 640424 Change-Id: I59a8d5f017243d8391269bc98998bb37fc44f685 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4d1d9c326a19..bcb637263a1f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -974,7 +974,8 @@ retry: } } while (++phase < 16); - if ((tuned_phase_cnt == NUM_TUNING_PHASES) && mmc_card_mmc(card)) { + if ((tuned_phase_cnt == NUM_TUNING_PHASES) && + card && mmc_card_mmc(card)) { /* * If all phases pass then its a problem. So change the card's * drive type to a different value, if supported and repeat From 93a9fa1e474c2eae42a181590f12e9e20a3a7e90 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 7 Apr 2014 10:33:11 +0530 Subject: [PATCH 091/472] mmc: sdhci: Fix issue with host op card_event() For controllers that doesn't support card insertion/removal i.e., when the quirk SDHCI_QUIRK_BROKEN_CARD_DETECTION is defined, card_event() host op must not rely on present state register to check the card's status. CRs-fixed: 644221 Change-Id: Icff6db0d8fe17f01cf751896ae09aee215edc548 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index bd9e297419cb..560264335f1b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -344,6 +344,9 @@ static void sdhci_led_control(struct led_classdev *led, struct sdhci_host *host = container_of(led, struct sdhci_host, led); unsigned long flags; + if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) + return; + spin_lock_irqsave(&host->lock, flags); if (host->runtime_suspended || sdhci_check_state(host)) From 81dd76ac077bdbc120ddb05ea9a27bc2ae8d7796 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 9 Apr 2014 10:54:29 -0700 Subject: [PATCH 092/472] mmc: sdhci: Poll for register status much tightly On fast path, waiting for 1ms interval to poll registers cause performance degradation. Also having 1ms delays for polling with interrupts disabled cause considerable system latencies, hence poll at 1us interval. Change-Id: I40113ccf56050b3c46604112846e9b37b254d2be Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 560264335f1b..379135a14f38 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -244,7 +244,7 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) } /* Wait max 100 ms */ - timeout = 100; + timeout = 100000; if (host->ops->check_power_status && host->pwr && (mask & SDHCI_RESET_ALL)) @@ -1096,7 +1096,7 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) WARN_ON(host->cmd); /* Wait max 10 ms */ - timeout = 10; + timeout = 10000; mask = SDHCI_CMD_INHIBIT; if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY)) @@ -1117,7 +1117,7 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) return; } timeout--; - mdelay(1); + udelay(1); } timeout = jiffies; @@ -1349,7 +1349,7 @@ clock_set: sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); /* Wait max 20 ms */ - timeout = 20; + timeout = 20000; while (!((clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL)) & SDHCI_CLOCK_INT_STABLE)) { if (timeout == 0) { @@ -1359,7 +1359,7 @@ clock_set: return; } timeout--; - mdelay(1); + udelay(1); } clk |= SDHCI_CLOCK_CARD_EN; From 28f9d7aa7bca7c18aa41149d82c7b7398e483b9c Mon Sep 17 00:00:00 2001 From: Matt Wagantall Date: Tue, 29 Apr 2014 15:48:15 -0700 Subject: [PATCH 093/472] mmc: sdhci-msm: Correct usage of readl_poll_timeout() API The intention of sdhci_msm_probe() was to wait up to 1ms for CORE_SW_RST to be set, but only a 10us timeout was passed to readl_poll_timeout(). Correct the timeout value to be 1ms, and decrease the sleep time between read attempts to only 10us so that it's less than the expected wait time of 40us. Change-Id: I7007a68232bfba76409e2dbae3060622fa5c1913 Signed-off-by: Matt Wagantall --- drivers/mmc/host/sdhci-msm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index bcb637263a1f..55001192434c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2826,7 +2826,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) * max. 1ms for reset completion. */ ret = readl_poll_timeout(msm_host->core_mem + CORE_POWER, - pwr, !(pwr & CORE_SW_RST), 100, 10); + pwr, !(pwr & CORE_SW_RST), 10, 1000); if (ret) { dev_err(&pdev->dev, "reset failed (%d)\n", ret); From 87baaf746779a0fd588511f9724715577c68e6a7 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 5 Jun 2014 13:26:55 +0530 Subject: [PATCH 094/472] mmc: sdhci-msm: Fix issue with SD card detect GPIO Set up the card detect GPIO in active configuration before configuring it as an IRQ. Otherwise, it can be in some weird/inconsistent state resulting in flood of interrupts. Change-Id: I8f2344a72acbd48df745cc9b2602fe4ac6f034d4 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 55001192434c..7c139c2ff4f6 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2942,6 +2942,13 @@ static int sdhci_msm_probe(struct platform_device *pdev) init_completion(&msm_host->pwr_irq_completion); if (gpio_is_valid(msm_host->pdata->status_gpio)) { + /* + * Set up the card detect GPIO in active configuration before + * configuring it as an IRQ. Otherwise, it can be in some + * weird/inconsistent state resulting in flood of interrupts. + */ + sdhci_msm_setup_pins(msm_host->pdata, true); + ret = mmc_gpio_request_cd(msm_host->mmc, msm_host->pdata->status_gpio, 0); if (ret) { From 7302266f4cc943dd37f0cfd7db16e83ecf28f7cb Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Wed, 4 Jun 2014 01:25:16 -0700 Subject: [PATCH 095/472] sdhci: sdhci-msm: update dll configuration The newer msm sdhci's cores use a different DLL hardware for HS400. Update the configuration and calibration of the newer DLL block. The HS400 DLL block used previously is CDC LP 533 and requires programming multiple registers and waiting for configuration to complete and then enable it. It has about 18 register writes and two register reads. The newer HS400 DLL block is SDC4 DLL and requires two register writes for configuration and one register read to confirm that it is initialized. There is an additional register write to enable the power save mode for SDC4 DLL block. Change-Id: I20ddeaee9309c43cd51bebdfc02d70553c9d2a87 Signed-off-by: Krishna Konda --- drivers/mmc/host/sdhci-msm.c | 158 +++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 35 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 7c139c2ff4f6..469b79ebe77b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -80,6 +80,7 @@ #define CORE_DLL_STATUS 0x108 #define CORE_DLL_LOCK (1 << 7) +#define CORE_DDR_DLL_LOCK (1 << 11) #define CORE_VENDOR_SPEC 0x10C #define CORE_CLK_PWRSAVE (1 << 1) @@ -128,6 +129,15 @@ #define CORE_DDR_200_CFG 0x184 #define CORE_CDC_T4_DLY_SEL (1 << 0) #define CORE_START_CDC_TRAFFIC (1 << 6) +#define CORE_VENDOR_SPEC3 0x1B0 +#define CORE_PWRSAVE_DLL (1 << 3) + +#define CORE_DLL_CONFIG_2 0x1B4 +#define CORE_DDR_CAL_EN (1 << 0) + +#define CORE_DDR_CONFIG 0x1B8 +#define DDR_CONFIG_POR_VAL 0x80040853 + #define CORE_MCI_DATA_CNT 0x30 #define CORE_MCI_STATUS 0x34 @@ -291,6 +301,7 @@ struct sdhci_msm_host { bool en_auto_cmd21; struct device_attribute auto_cmd21_attr; atomic_t controller_clock; + bool use_cdclp533; }; enum vdd_io_level { @@ -705,32 +716,12 @@ out: static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host) { - u32 wait_cnt; + u32 calib_done; int ret = 0; int cdc_err = 0; - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct sdhci_msm_host *msm_host = pltfm_host->priv; pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); - /* - * Retuning in HS400 (DDR mode) will fail, just reset the - * tuning block and restore the saved tuning phase. - */ - ret = msm_init_cm_dll(host); - if (ret) - goto out; - - /* Set the selected phase in delay line hw block */ - ret = msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase); - if (ret) - goto out; - - /* Write 1 to CMD_DAT_TRACK_SEL field in DLL_CONFIG */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) - | CORE_CMD_DAT_TRACK_SEL), - host->ioaddr + CORE_DLL_CONFIG); - /* Write 0 to CDC_T4_DLY_SEL field in VENDOR_SPEC_DDR200_CFG */ writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) & ~CORE_CDC_T4_DLY_SEL), @@ -800,18 +791,13 @@ static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host) mb(); /* Poll on CALIBRATION_DONE field in CORE_CSR_CDC_STATUS0 to be 1 */ - wait_cnt = 50; - while (!(readl_relaxed(host->ioaddr + CORE_CSR_CDC_STATUS0) - & CORE_CALIBRATION_DONE)) { - /* max. wait for 50us sec for CALIBRATION_DONE bit to be set */ - if (--wait_cnt == 0) { - pr_err("%s: %s: CDC Calibration was not completed\n", + ret = readl_poll_timeout(host->ioaddr + CORE_CSR_CDC_STATUS0, + calib_done, (calib_done & CORE_CALIBRATION_DONE), 1, 50); + + if (ret == -ETIMEDOUT) { + pr_err("%s: %s: CDC Calibration was not completed\n", mmc_hostname(host->mmc), __func__); - ret = -ETIMEDOUT; - goto out; - } - /* wait for 1us before polling again */ - udelay(1); + goto out; } /* Verify CDC_ERROR_CODE field in CORE_CSR_CDC_STATUS0 is 0 */ @@ -834,6 +820,81 @@ out: return ret; } +static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) +{ + u32 dll_status; + int ret = 0; + + pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); + + /* + * Currently the CORE_DDR_CONFIG register defaults to desired + * configuration on reset. Currently reprogramming the power on + * reset (POR) value in case it might have been modified by + * bootloaders. In the future, if this changes, then the desired + * values will need to be programmed appropriately. + */ + writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr + CORE_DDR_CONFIG); + + /* Write 1 to DDR_CAL_EN field in CORE_DLL_CONFIG_2 */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG_2) + | CORE_DDR_CAL_EN), + host->ioaddr + CORE_DLL_CONFIG_2); + + /* Poll on DDR_DLL_LOCK bit in CORE_DLL_STATUS to be set */ + ret = readl_poll_timeout(host->ioaddr + CORE_DLL_STATUS, + dll_status, (dll_status & CORE_DDR_DLL_LOCK), 10, 1000); + + if (ret == -ETIMEDOUT) { + pr_err("%s: %s: CM_DLL_SDC4 Calibration was not completed\n", + mmc_hostname(host->mmc), __func__); + goto out; + } + + /* set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3 */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC3) + | CORE_PWRSAVE_DLL), + host->ioaddr + CORE_VENDOR_SPEC3); + mb(); +out: + pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc), + __func__, ret); + return ret; +} + +static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host) +{ + int ret = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); + + /* + * Retuning in HS400 (DDR mode) will fail, just reset the + * tuning block and restore the saved tuning phase. + */ + ret = msm_init_cm_dll(host); + if (ret) + goto out; + + /* Set the selected phase in delay line hw block */ + ret = msm_config_cm_dll_phase(host, msm_host->saved_tuning_phase); + if (ret) + goto out; + + if (msm_host->use_cdclp533) + /* Calibrate CDCLP533 DLL HW */ + ret = sdhci_msm_cdclp533_calibration(host); + else + /* Calibrate CM_DLL_SDC4 HW */ + ret = sdhci_msm_cm_dll_sdc4_calibration(host); +out: + pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc), + __func__, ret); + return ret; +} + static void sdhci_msm_set_mmc_drv_type(struct sdhci_host *host, u32 opcode, u8 drv_type) { @@ -891,10 +952,10 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); - /* CDCLP533 HW calibration is only required for HS400 mode*/ + /* CDC/SDC4 DLL HW calibration is only required for HS400 mode*/ if (msm_host->tuning_done && !msm_host->calibration_done && (mmc->ios.timing == MMC_TIMING_MMC_HS400)) { - rc = sdhci_msm_cdclp533_calibration(host); + rc = sdhci_msm_hs400_dll_calibration(host); spin_lock_irqsave(&host->lock, flags); if (!rc) msm_host->calibration_done = true; @@ -2378,7 +2439,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; struct mmc_ios curr_ios = host->mmc->ios; - u32 sup_clock, ddr_clock; + u32 sup_clock, ddr_clock, dll_lock; bool curr_pwrsave; if (!clock) { @@ -2465,7 +2526,27 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) | CORE_HC_SELECT_IN_EN), host->ioaddr + CORE_VENDOR_SPEC); } + if (!host->mmc->ios.old_rate && !msm_host->use_cdclp533) { + /* + * Poll on DLL_LOCK and DDR_DLL_LOCK bits in + * CORE_DLL_STATUS to be set. This should get set + * with in 15 us at 200 MHz. + */ + rc = readl_poll_timeout(host->ioaddr + CORE_DLL_STATUS, + dll_lock, (dll_lock & (CORE_DLL_LOCK | + CORE_DDR_DLL_LOCK)), 10, 1000); + if (rc == -ETIMEDOUT) + pr_err("%s: Unable to get DLL_LOCK/DDR_DLL_LOCK, dll_status: 0x%08x\n", + mmc_hostname(host->mmc), + dll_lock); + } } else { + if (!msm_host->use_cdclp533) + /* set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3 */ + writel_relaxed((readl_relaxed(host->ioaddr + + CORE_VENDOR_SPEC3) & ~CORE_PWRSAVE_DLL), + host->ioaddr + CORE_VENDOR_SPEC3); + /* Select the default clock (free running MCLK) */ writel_relaxed(((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & ~CORE_HC_MCLK_SEL_MASK) @@ -2667,6 +2748,13 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, (readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | caps), host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); } + + /* + * SDCC 5 controller with major version 1, minor version 0x34 and later + * with HS 400 mode support will use CM DLL instead of CDC LP 533 DLL. + */ + if ((major == 1) && (minor < 0x34)) + msm_host->use_cdclp533 = true; } static int sdhci_msm_probe(struct platform_device *pdev) From a2fbfacff042b6a282ae06ae4a1ca5b5a42714bb Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 23 Jun 2014 14:50:38 -0700 Subject: [PATCH 096/472] sdhci: sdhci-msm: fix issue with hs400 mode In HS400 mode, command line is still operated in SDR mode and not DDR mode like data lines. So in order to correctly process command responses, the hardware needs to be set appropriately or else there will command crc or similar errors due to incorrect sampling of the response. Change-Id: I426ff4fc4798afae254d11a608c800dc0b7bf765 Signed-off-by: Krishna Konda --- drivers/mmc/host/sdhci-msm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 469b79ebe77b..6d00eff97a29 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -883,6 +883,11 @@ static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host) if (ret) goto out; + /* Write 1 to CMD_DAT_TRACK_SEL field in DLL_CONFIG */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + | CORE_CMD_DAT_TRACK_SEL), + host->ioaddr + CORE_DLL_CONFIG); + if (msm_host->use_cdclp533) /* Calibrate CDCLP533 DLL HW */ ret = sdhci_msm_cdclp533_calibration(host); From 2e08a5f0a41677174aa3e871c3bb06663e99ebc4 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 17 Jun 2014 16:47:48 -0700 Subject: [PATCH 097/472] mmc: sdhci-msm: configure CORE_CSR_CDC_DELAY_CFG to recommended value Program CORE_CSR_CDC_DELAY_CFG for hardware recommended 1.25ns delay. We may see data CRC errors if it's programmed for any other delay value. CRs-Fixed: 683894 Change-Id: Id7de28b7b9222c35e6b419e416f72bd8f98cbaf8 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 6d00eff97a29..82eb8b9c5f99 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -762,7 +762,7 @@ static int sdhci_msm_cdclp533_calibration(struct sdhci_host *host) writel_relaxed(0x4, host->ioaddr + CORE_CSR_CDC_CAL_TIMER_CFG1); writel_relaxed(0xCB732020, host->ioaddr + CORE_CSR_CDC_REFCOUNT_CFG); writel_relaxed(0xB19, host->ioaddr + CORE_CSR_CDC_COARSE_CAL_CFG); - writel_relaxed(0x3AC, host->ioaddr + CORE_CSR_CDC_DELAY_CFG); + writel_relaxed(0x4E2, host->ioaddr + CORE_CSR_CDC_DELAY_CFG); writel_relaxed(0x0, host->ioaddr + CORE_CDC_OFFSET_CFG); writel_relaxed(0x16334, host->ioaddr + CORE_CDC_SLAVE_DDA_CFG); From 9ecd59212542e74205ed782a84ea6b7bf535c5c2 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 26 Jun 2014 15:41:36 +0530 Subject: [PATCH 098/472] mmc: sdhci: Vote for new IRQ specific QoS request The current vote applies to all CPUs and hence may have power impact, especially when there are more number of CPUs. The new IRQ specific QoS request shall update the vote only for one CPU to which the IRQ's smp_affinity attribute is set to. Change-Id: I55298f729949c39ebfa3eecd4746d77e40cb2e5c Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 379135a14f38..d0c9c21533be 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3204,6 +3204,27 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, EXPORT_SYMBOL_GPL(sdhci_alloc_host); +#ifdef CONFIG_SMP +static void sdhci_set_pmqos_req_type(struct sdhci_host *host) +{ + + /* + * The default request type PM_QOS_REQ_ALL_CORES is + * applicable to all CPU cores that are online and + * this would have a power impact when there are more + * number of CPUs. This new PM_QOS_REQ_AFFINE_IRQ request + * type shall update/apply the vote only to that CPU to + * which this IRQ's affinity is set to. + */ + host->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ; + host->pm_qos_req_dma.irq = host->irq; +} +#else +static void sdhci_set_pmqos_req_type(struct sdhci_host *host) +{ +} +#endif + int sdhci_add_host(struct sdhci_host *host) { struct mmc_host *mmc; @@ -3739,6 +3760,7 @@ int sdhci_add_host(struct sdhci_host *host) if (host->cpu_dma_latency_us) { host->pm_qos_timeout_us = 10000; /* default value */ + sdhci_set_pmqos_req_type(host); pm_qos_add_request(&host->pm_qos_req_dma, PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); From 10cf95ae7737dbd2c5a955a0822928c40e116f62 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 9 Jun 2014 14:00:31 -0700 Subject: [PATCH 099/472] mmc: sdhci: Add tracepoints to enhance debugging Instrument the sdhci driver with tracepoints to aid in debugging issues and identifying latencies in the following path: * CMD completion * DATA completion * DMA preparation * Post DMA cleanup Change-Id: Ie8cd0c2fb6c1bd6ab13883123be021081f8b8f78 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 19 +++++++--- include/trace/events/mmc.h | 73 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d0c9c21533be..a2b6dda8ebb8 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -32,6 +32,8 @@ #include #include +#include + #include "sdhci.h" #define DRIVER_NAME "sdhci" @@ -663,7 +665,10 @@ static void sdhci_adma_table_post(struct sdhci_host *host, void *align; char *buffer; unsigned long flags; - bool has_unaligned; + bool has_unaligned = false; + u32 command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); + + trace_mmc_adma_table_post(command, data->sg_len); if (data->flags & MMC_DATA_READ) direction = DMA_FROM_DEVICE; @@ -903,6 +908,7 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_command *cmd) if (host->flags & SDHCI_REQ_USE_DMA) { if (host->flags & SDHCI_USE_ADMA) { + trace_mmc_adma_table_pre(cmd->opcode, data->sg_len); ret = sdhci_adma_table_pre(host, data); if (ret) { /* @@ -1165,6 +1171,7 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) if (cmd->data) host->data_start_time = ktime_get(); + trace_mmc_cmd_rw_start(cmd->opcode, cmd->arg, cmd->flags); sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND); } EXPORT_SYMBOL_GPL(sdhci_send_command); @@ -2623,6 +2630,9 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) return; } + trace_mmc_cmd_rw_end(host->cmd->opcode, intmask, + sdhci_readl(host, SDHCI_RESPONSE)); + if (intmask & SDHCI_INT_TIMEOUT) host->cmd->error = -ETIMEDOUT; else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | @@ -2720,9 +2730,11 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) bool pr_msg = false; BUG_ON(intmask == 0); + command = SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)); + trace_mmc_data_rw_end(command, intmask); + /* CMD19 generates _only_ Buffer Read Ready interrupt */ if (intmask & SDHCI_INT_DATA_AVAIL) { - 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; @@ -2773,8 +2785,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) else if (intmask & SDHCI_INT_DATA_END_BIT) host->data->error = -EILSEQ; else if ((intmask & SDHCI_INT_DATA_CRC) && - SDHCI_GET_CMD(sdhci_readw(host, SDHCI_COMMAND)) - != MMC_BUS_TEST_R) + (command != MMC_BUS_TEST_R)) host->data->error = -EILSEQ; else if (intmask & SDHCI_INT_ADMA_ERROR) { pr_err("%s: ADMA error\n", mmc_hostname(host->mmc)); diff --git a/include/trace/events/mmc.h b/include/trace/events/mmc.h index 82b368dbcefc..22cf81b2d9ff 100644 --- a/include/trace/events/mmc.h +++ b/include/trace/events/mmc.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2013 Google, Inc. + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -85,6 +86,78 @@ DEFINE_EVENT_CONDITION(mmc_blk_rw_class, mmc_blk_rw_end, TP_CONDITION(((cmd == MMC_READ_MULTIPLE_BLOCK) || (cmd == MMC_WRITE_MULTIPLE_BLOCK)) && data)); + +TRACE_EVENT(mmc_cmd_rw_start, + TP_PROTO(unsigned int cmd, unsigned int arg, unsigned int flags), + TP_ARGS(cmd, arg, flags), + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(unsigned int, arg) + __field(unsigned int, flags) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->arg = arg; + __entry->flags = flags; + ), + TP_printk("cmd=%u,arg=0x%08x,flags=0x%08x", + __entry->cmd, __entry->arg, __entry->flags) +); + +TRACE_EVENT(mmc_cmd_rw_end, + TP_PROTO(unsigned int cmd, unsigned int status, unsigned int resp), + TP_ARGS(cmd, status, resp), + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(unsigned int, status) + __field(unsigned int, resp) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->status = status; + __entry->resp = resp; + ), + TP_printk("cmd=%u,int_status=0x%08x,response=0x%08x", + __entry->cmd, __entry->status, __entry->resp) +); + +TRACE_EVENT(mmc_data_rw_end, + TP_PROTO(unsigned int cmd, unsigned int status), + TP_ARGS(cmd, status), + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(unsigned int, status) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->status = status; + ), + TP_printk("cmd=%u,int_status=0x%08x", + __entry->cmd, __entry->status) +); + +DECLARE_EVENT_CLASS(mmc_adma_class, + TP_PROTO(unsigned int cmd, unsigned int len), + TP_ARGS(cmd, len), + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(unsigned int, len) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->len = len; + ), + TP_printk("cmd=%u,sg_len=0x%08x", __entry->cmd, __entry->len) +); + +DEFINE_EVENT(mmc_adma_class, mmc_adma_table_pre, + TP_PROTO(unsigned int cmd, unsigned int len), + TP_ARGS(cmd, len)); + +DEFINE_EVENT(mmc_adma_class, mmc_adma_table_post, + TP_PROTO(unsigned int cmd, unsigned int len), + TP_ARGS(cmd, len)); + #endif /* _TRACE_MMC_H */ /* This part must be outside protection */ From 902d3304766715562c83b12f34a4df98aa5e7b4e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 24 Jul 2014 20:39:34 -0700 Subject: [PATCH 100/472] mmc: sdhci-msm: Reduce the max descriptors 8K is the max descriptors supported by the hardware per transfer. On normal use cases we never use that many descriptors even if the memory is extremely fragmented. The memory allocated for these descriptors are never freed hence wasting memory otherwise available for the system. Reduce the max descriptors to 512 to save memory (128K with 8K descriptors vs 8K with 512 descriptors) and still support maximum performance. CRs-fixed: 684185 Change-Id: Ib57ce03834d741c0fda4195a58d6b287ee9fb0a0 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 82eb8b9c5f99..9a3813401de0 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -146,8 +146,8 @@ #define CORE_TESTBUS_SEL2_BIT 4 #define CORE_TESTBUS_SEL2 (1 << CORE_TESTBUS_SEL2_BIT) -/* 8KB descriptors */ -#define SDHCI_MSM_MAX_SEGMENTS (1 << 13) +/* 512 descriptors */ +#define SDHCI_MSM_MAX_SEGMENTS (1 << 9) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ #define CORE_FREQ_100MHZ (100 * 1000 * 1000) From 195613ce195be99f6e9fd987b8ffcebfaac950b9 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 8 Aug 2014 23:01:42 +0530 Subject: [PATCH 101/472] mmc: sdhci-msm: fix possible NULL pointer dereference The platform_get_resource API may return NULL, hence check the return value before using the mapped memory. Change-Id: I28741554f8e1b5843671ee0d6b08bdcf8e4469c9 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9a3813401de0..e98a2c96c6ab 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2897,6 +2897,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Reset the core and Enable SDHC mode */ core_memres = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core_mem"); + if (!core_memres) { + dev_err(&pdev->dev, "Failed to get iomem resource\n"); + goto vreg_deinit; + } msm_host->core_mem = devm_ioremap(&pdev->dev, core_memres->start, resource_size(core_memres)); From eef1739c4479c2ad5a69daf8a4092da630a96136 Mon Sep 17 00:00:00 2001 From: Guoping Yu Date: Wed, 20 Aug 2014 16:42:55 +0800 Subject: [PATCH 102/472] mmc: sdhci: add support nonhotplug With some devices, SD card could not support hotplug as there is no cd-gpio, and also could not use polling way due to power comsumption. So add nonhotplug to meet such requirement and when SD card lost or removed manually, device will not crash until next reboot process to detect SD card. Change-Id: Ie8ea8ec57015f36689a119249003eeaa48391393 Signed-off-by: Guoping Yu [subhashj@codeaurora.org: fixed minor merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 3 ++- include/linux/mmc/host.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a2b6dda8ebb8..a65e9575632f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3507,7 +3507,8 @@ int sdhci_add_host(struct sdhci_host *host) if ((host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) && !(mmc->caps & MMC_CAP_NONREMOVABLE) && - IS_ERR_VALUE(mmc_gpio_get_cd(host->mmc))) + (IS_ERR_VALUE(mmc_gpio_get_cd(host->mmc)) && + !(mmc->caps2 & MMC_CAP2_NONHOTPLUG))) mmc->caps |= MMC_CAP_NEEDS_POLL; /* If there are external regulators, get them */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 63a10df66b87..43796e98b324 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -302,6 +302,7 @@ struct mmc_host { #define MMC_CAP2_HSX00_1_2V (MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V) #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17) #define MMC_CAP2_NO_WRITE_PROTECT (1 << 18) /* No physical write protect pin, assume that card is always read-write */ +#define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ mmc_pm_flag_t pm_caps; /* supported pm features */ From 9ce1e0b186f1efc3afa1db62d66538f2dce57192 Mon Sep 17 00:00:00 2001 From: Guoping Yu Date: Wed, 20 Aug 2014 16:56:18 +0800 Subject: [PATCH 103/472] mmc: sdhci-msm: add nonhotplug support With some devices like QRD SKUK, SD card could not support hotplug as there is no cd-gpio, and also could not use polling way due to power comsumption. So add nonhotplug to meet such requirement and when SD card lost or removed manually, device will not crash until next reboot process to detect SD card. Change-Id: I318d3db72fc09248c5dada2fb6d69ade1bbf85cb Signed-off-by: Guoping Yu --- Documentation/devicetree/bindings/mmc/sdhci-msm.txt | 3 +++ drivers/mmc/host/sdhci-msm.c | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 643944fa8565..e46474cc477b 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -25,6 +25,9 @@ Optional Properties: 1, 4 and 8. - qcom,nonremovable - specifies whether the card in slot is hot pluggable or hard wired. + - qcom,nonhotplug - specifies the card in slot is not hot pluggable. + if card lost or removed manually at runtime, don't retry + to redetect it until next reboot probe. - qcom,bus-speed-mode - specifies supported bus speed modes by host. The supported bus speed modes are : "HS200_1p8v" - indicates that host can support HS200 at 1.8v. diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e98a2c96c6ab..ddce818e105c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -257,6 +257,7 @@ struct sdhci_msm_pltfm_data { unsigned long mmc_bus_width; struct sdhci_msm_slot_reg_data *vreg_data; bool nonremovable; + bool nonhotplug; bool pin_cfg_sts; struct sdhci_msm_pin_data *pin_data; struct sdhci_pinctrl_data *pctrl_data; @@ -1504,6 +1505,9 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) if (of_get_property(np, "qcom,nonremovable", NULL)) pdata->nonremovable = true; + if (of_get_property(np, "qcom,nonhotplug", NULL)) + pdata->nonhotplug = true; + return pdata; out: return NULL; @@ -3034,6 +3038,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; + if (msm_host->pdata->nonhotplug) + msm_host->mmc->caps2 |= MMC_CAP2_NONHOTPLUG; + host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; init_completion(&msm_host->pwr_irq_completion); From 9e36f69ea168e9bc276c0e1e0f52b07b7debc3cc Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 28 Aug 2014 18:15:45 -0700 Subject: [PATCH 104/472] mmc: sdhci-msm: Add dual voltage capability SDCC5 controller doesn't advertise 1.8v capability by default. Dual voltage capability is required for SD3.0 support. Add this capability for controllers that support this based on device tree configuration. Change-Id: Ie4cd6db2e7230bc22cd393c8e37d99f49c777cd0 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ddce818e105c..b2edcaa1c09a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2746,11 +2746,20 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, /* * Starting with SDCC 5 controller (core major version = 1) - * controller won't advertise 3.0v and 8-bit features except for - * some targets. + * controller won't advertise 3.0v, 1.8v and 8-bit features + * except for some targets. */ if (major >= 1 && minor != 0x11 && minor != 0x12) { + struct sdhci_msm_reg_data *vdd_io_reg; caps = CORE_3_0V_SUPPORT; + /* + * Enable 1.8V support capability on controllers that + * support dual voltage + */ + vdd_io_reg = msm_host->pdata->vreg_data->vdd_io_data; + if (vdd_io_reg && + (vdd_io_reg->low_vol_level != vdd_io_reg->high_vol_level)) + caps |= CORE_1_8V_SUPPORT; if (msm_host->pdata->mmc_bus_width == MMC_CAP_8_BIT_DATA) caps |= CORE_8_BIT_SUPPORT; writel_relaxed( From 7cda2fabdbdbd4f8613fdc9493a9e92c691b8f35 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Thu, 2 Oct 2014 17:20:35 +0300 Subject: [PATCH 105/472] mmc: sdhci-msm: force 32bit ADMA descriptors SDCC AXI master uses 32-bit addressing so there's no need to use 64-bit descriptors. Using 32-bit descriptors instead will reduce the memory footprint. This change masks the 64-bit capability to force 32-bit ADMA descriptors. CRs-Fixed: 719303 Change-Id: Ifb8095763136bbc795227bdfcb346d1e1fae42c7 Signed-off-by: Gilad Broner --- drivers/mmc/host/sdhci-msm.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b2edcaa1c09a..e23b169beeef 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -98,7 +98,7 @@ #define CORE_3_3V_SUPPORT (1 << 24) #define CORE_3_0V_SUPPORT (1 << 25) #define CORE_1_8V_SUPPORT (1 << 26) -#define CORE_SYS_BUS_SUPPORT_64_BIT 28 +#define CORE_SYS_BUS_SUPPORT_64_BIT BIT(28) #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 @@ -2773,6 +2773,16 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, */ if ((major == 1) && (minor < 0x34)) msm_host->use_cdclp533 = true; + + /* + * Mask 64-bit support for controller with 32-bit address bus so that + * smaller descriptor size will be used and improve memory consumption. + * In case bus addressing ever changes, controller version should be + * used in order to decide whether or not to mask 64-bit support. + */ + caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES); + caps &= ~CORE_SYS_BUS_SUPPORT_64_BIT; + writel_relaxed(caps, host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); } static int sdhci_msm_probe(struct platform_device *pdev) From e5bd1357a19a91773e4bc0bd83216c61ad3ad418 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Tue, 21 Oct 2014 20:22:04 +0300 Subject: [PATCH 106/472] mmc: host: sdhci: allow definition of pm QoS via dts file Add a new dts entry to define the CPU affinity in order to maintain the IRQ pm_qos (Quality of Service) for targets that don't have little cluster and allow setting the pm_qos to the little cluster, to improve its performance. Change-Id: Icf6125066d96331392d98a387974e54c96553306 Signed-off-by: Vince Leung Signed-off-by: Maya Erez --- .../devicetree/bindings/mmc/sdhci-msm.txt | 13 +++++++++ drivers/mmc/host/sdhci-msm.c | 28 +++++++++++++++++++ drivers/mmc/host/sdhci.c | 10 +++++-- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index e46474cc477b..9315a06343f7 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -60,6 +60,19 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag - pinctrl-names - pinctrl-0, pinctrl-1,.. pinctrl-n + - qcom,cpu-affinity: this is a string that specifies the pm QoS request + type. The supported cpu affinity modes are : + "all_cores" - PM_QOS_REQ_ALL_CORES is applicable to all CPU cores that + are online and this would have a power impact when there are more + number of CPUs. + "affine_irq" - PM_QOS_REQ_AFFINE_IRQ request type shall update/apply + the vote only to that CPU to which this IRQ's affinity is set to. + "affine_cores" - PM_QOS_REQ_AFFINE_CORES request type is used for + targets that have little cluster and will update/apply the vote to + all the cores in the little cluster. + The default CPU affinity mode is PM_QOS_REQ_AFFINE_IRQ to maintain + backward compatibility. + Example: aliases { diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e23b169beeef..8ef9478a549e 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -266,6 +266,7 @@ struct sdhci_msm_pltfm_data { struct sdhci_msm_bus_voting_data *voting_data; u32 *sup_clk_table; unsigned char sup_clk_cnt; + enum pm_qos_req_type cpu_affinity_type; }; struct sdhci_msm_bus_vote { @@ -1402,6 +1403,30 @@ out: return ret; } +#ifdef CONFIG_SMP +static void sdhci_msm_populate_affinity_type(struct sdhci_msm_pltfm_data *pdata, + struct device_node *np) +{ + const char *cpu_affinity = NULL; + + pdata->cpu_affinity_type = PM_QOS_REQ_AFFINE_IRQ; + if (!of_property_read_string(np, "qcom,cpu-affinity", + &cpu_affinity)) { + if (!strcmp(cpu_affinity, "all_cores")) + pdata->cpu_affinity_type = PM_QOS_REQ_ALL_CORES; + else if (!strcmp(cpu_affinity, "affine_cores")) + pdata->cpu_affinity_type = PM_QOS_REQ_AFFINE_CORES; + else if (!strcmp(cpu_affinity, "affine_irq")) + pdata->cpu_affinity_type = PM_QOS_REQ_AFFINE_IRQ; + } +} +#else +static void sdhci_msm_populate_affinity_type(struct sdhci_msm_pltfm_data *pdata, + struct device_node *np) +{ +} +#endif + /* Parse platform data */ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) { @@ -1508,6 +1533,8 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) if (of_get_property(np, "qcom,nonhotplug", NULL)) pdata->nonhotplug = true; + sdhci_msm_populate_affinity_type(pdata, np); + return pdata; out: return NULL; @@ -3061,6 +3088,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_NONHOTPLUG; host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; + host->pm_qos_req_dma.type = msm_host->pdata->cpu_affinity_type; init_completion(&msm_host->pwr_irq_completion); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a65e9575632f..116e6083032f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3218,7 +3218,6 @@ EXPORT_SYMBOL_GPL(sdhci_alloc_host); #ifdef CONFIG_SMP static void sdhci_set_pmqos_req_type(struct sdhci_host *host) { - /* * The default request type PM_QOS_REQ_ALL_CORES is * applicable to all CPU cores that are online and @@ -3226,9 +3225,14 @@ static void sdhci_set_pmqos_req_type(struct sdhci_host *host) * number of CPUs. This new PM_QOS_REQ_AFFINE_IRQ request * type shall update/apply the vote only to that CPU to * which this IRQ's affinity is set to. + * PM_QOS_REQ_AFFINE_CORES request type is used for targets that have + * little cluster and will update/apply the vote to all the cores in + * the little cluster. */ - host->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ; - host->pm_qos_req_dma.irq = host->irq; + if (host->pm_qos_req_dma.type == PM_QOS_REQ_AFFINE_CORES) + host->pm_qos_req_dma.cpus_affine.bits[0] = 0x0F; + else if (host->pm_qos_req_dma.type == PM_QOS_REQ_AFFINE_IRQ) + host->pm_qos_req_dma.irq = host->irq; } #else static void sdhci_set_pmqos_req_type(struct sdhci_host *host) From 7c957dec83c3c39a76193bf51e67fef3c9b7edf3 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 31 Oct 2014 14:00:12 +0530 Subject: [PATCH 107/472] mmc: sdhci: rate limit sdhci_dumpregs() prints Rate limit sdhci_dumpregs() prints to avoid unnecessary and exessive logging which can sometimes lead to watchdog timeouts (especially due to bad cards). Change-Id: Ib6be6d563e47c2d2e9e1b6b0410c2c45712a9b17 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci.c | 7 ++++++- drivers/mmc/host/sdhci.h | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 116e6083032f..a47105ae1554 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -48,6 +48,9 @@ #define MAX_TUNING_LOOP 40 +#define SDHCI_DBG_DUMP_RS_INTERVAL (10 * HZ) +#define SDHCI_DBG_DUMP_RS_BURST 2 + static unsigned int debug_quirks = 0; static unsigned int debug_quirks2; @@ -2804,7 +2807,7 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } else { pr_msg = true; } - if (pr_msg) { + if (pr_msg && __ratelimit(&host->dbg_dump_rs)) { pr_err("%s: data txfr (0x%08x) error: %d after %lld ms\n", mmc_hostname(host->mmc), intmask, host->data->error, ktime_to_ms(ktime_sub( @@ -3209,6 +3212,8 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, host->mmc = mmc; spin_lock_init(&host->lock); + ratelimit_state_init(&host->dbg_dump_rs, SDHCI_DBG_DUMP_RS_INTERVAL, + SDHCI_DBG_DUMP_RS_BURST); return host; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 95487cd95147..c98cd2f29182 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -18,7 +18,7 @@ #include #include #include - +#include #include /* @@ -597,7 +597,7 @@ struct sdhci_host { enum sdhci_power_policy power_policy; u32 auto_cmd_err_sts; - + struct ratelimit_state dbg_dump_rs; unsigned long private[0] ____cacheline_aligned; }; From 3d6d2ddeaf9b3804fd9ed24d9b410f1732e3c121 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Fri, 13 Jul 2012 10:51:37 +0530 Subject: [PATCH 108/472] mmc: core: Attribute the IO wait time properly in mmc_wait_for_req_done() In mmc_wait_for_req_done() function, change the call wait_for_completion() to wait_for_compltion_io(). This change makes the kernel account for wait time as I/O wait and through another configuration, this io wait is treated as busy which makes the acpu clock to scale up. Change-Id: Iebdc7b1b22871bf845f10a55e2272816c72d9964 Signed-off-by: Murali Palnati Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 536cb3655d49..0be861b5649c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -494,7 +494,7 @@ static void mmc_wait_for_req_done(struct mmc_host *host, struct mmc_command *cmd; while (1) { - wait_for_completion(&mrq->completion); + wait_for_completion_io(&mrq->completion); cmd = mrq->cmd; From f3bca6c7763f02d8f1318aa840e4a3780ab219f2 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 3 Dec 2014 09:22:39 +0200 Subject: [PATCH 109/472] mmc: core: Allow changing bus frequency for SD/eMMC cards in runtime Currently, bus frequency is set during the card initialization and never changed until a new card is inserted. In some low power use cases, scaling the clock frequencies while the card is in transfer state would allow power savings. This change allows bus frequency to be changed after the card initialization. Change-Id: Iac064221199f69d162d91f5311becd735c15700a Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: remove mmc and sd ops_unsafe settings as they are not in 3.14] Signed-off-by: Maya Erez [venkatg@codeaurora.org: Fix refactor of mmc_card_* functions as used in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts & fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.h | 1 + drivers/mmc/core/mmc.c | 64 +++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sd.c | 67 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 1a22a82209b2..c5cb975dbcbb 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -28,6 +28,7 @@ struct mmc_bus_ops { int (*alive)(struct mmc_host *); int (*shutdown)(struct mmc_host *); int (*reset)(struct mmc_host *); + int (*change_bus_speed)(struct mmc_host *, unsigned long *); }; void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 619bd9b1ff13..a116182875a9 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1298,6 +1298,69 @@ err: return err; } +/** + * mmc_change_bus_speed() - Change MMC card bus frequency at runtime + * @host: pointer to mmc host structure + * @freq: pointer to desired frequency to be set + * + * Change the MMC card bus frequency at runtime after the card is + * initialized. Callers are expected to make sure of the card's + * state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime. + * + * If the frequency to change is greater than max. supported by card, + * *freq is changed to max. supported by card and if it is less than min. + * supported by host, *freq is changed to min. supported by host. + */ +static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) +{ + int err = 0; + struct mmc_card *card; + + mmc_claim_host(host); + /* + * Assign card pointer after claiming host to avoid race + * conditions that may arise during removal of the card. + */ + card = host->card; + + if (!card || !freq) { + err = -EINVAL; + goto out; + } + + if (mmc_card_hs(card) || mmc_card_hs200(card) + || mmc_card_ddr52(card)) { + if (*freq > card->ext_csd.hs_max_dtr) + *freq = card->ext_csd.hs_max_dtr; + } else if (*freq > card->csd.max_dtr) { + *freq = card->csd.max_dtr; + } + + if (*freq < host->f_min) + *freq = host->f_min; + + mmc_set_clock(host, (unsigned int) (*freq)); + + if (mmc_card_hs200(card) && card->host->ops->execute_tuning) { + /* + * We try to probe host driver for tuning for any + * frequency, it is host driver responsibility to + * perform actual tuning only when required. + */ + mmc_host_clk_hold(card->host); + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK_HS200); + mmc_host_clk_release(card->host); + + if (err) + pr_warn("%s: %s: tuning execution failed %d\n", + mmc_hostname(card->host), __func__, err); + } +out: + mmc_release_host(host); + return err; +} + /* * Activate High Speed or HS200 mode if supported. */ @@ -1996,6 +2059,7 @@ static const struct mmc_bus_ops mmc_ops = { .runtime_resume = mmc_runtime_resume, .alive = mmc_alive, .shutdown = mmc_shutdown, + .change_bus_speed = mmc_change_bus_speed, .reset = mmc_reset, }; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 5aa0daf621d3..f9b5b4ad4985 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -569,6 +569,72 @@ static int sd_set_current_limit(struct mmc_card *card, u8 *status) return 0; } +/** + * mmc_sd_change_bus_speed() - Change SD card bus frequency at runtime + * @host: pointer to mmc host structure + * @freq: pointer to desired frequency to be set + * + * Change the SD card bus frequency at runtime after the card is + * initialized. Callers are expected to make sure of the card's + * state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime. + * + * If the frequency to change is greater than max. supported by card, + * *freq is changed to max. supported by card and if it is less than min. + * supported by host, *freq is changed to min. supported by host. + */ +static int mmc_sd_change_bus_speed(struct mmc_host *host, unsigned long *freq) +{ + int err = 0; + struct mmc_card *card; + + mmc_claim_host(host); + /* + * Assign card pointer after claiming host to avoid race + * conditions that may arise during removal of the card. + */ + card = host->card; + + /* sanity checks */ + if (!card || !freq) { + err = -EINVAL; + goto out; + } + + if (mmc_card_uhs(card)) { + if (*freq > card->sw_caps.uhs_max_dtr) + *freq = card->sw_caps.uhs_max_dtr; + } else { + if (*freq > mmc_sd_get_max_clock(card)) + *freq = mmc_sd_get_max_clock(card); + } + + if (*freq < host->f_min) + *freq = host->f_min; + + mmc_set_clock(host, (unsigned int) (*freq)); + + if (!mmc_host_is_spi(card->host) && mmc_card_uhs(card) + && card->host->ops->execute_tuning) { + /* + * We try to probe host driver for tuning for any + * frequency, it is host driver responsibility to + * perform actual tuning only when required. + */ + mmc_host_clk_hold(card->host); + err = card->host->ops->execute_tuning(card->host, + MMC_SEND_TUNING_BLOCK); + mmc_host_clk_release(card->host); + + if (err) + pr_warn("%s: %s: tuning execution failed %d\n", + mmc_hostname(card->host), __func__, err); + } + +out: + mmc_release_host(host); + return err; +} + /* * UHS-I specific initialization procedure */ @@ -1253,6 +1319,7 @@ static const struct mmc_bus_ops mmc_sd_ops = { .resume = mmc_sd_resume, .alive = mmc_sd_alive, .shutdown = mmc_sd_suspend, + .change_bus_speed = mmc_sd_change_bus_speed, .reset = mmc_sd_reset, }; From 7d7218224b477ffb208655be43996fdfdb9d8370 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Fri, 9 Nov 2012 18:32:32 +0530 Subject: [PATCH 110/472] mmc: core: Add a debugfs entry to set max clock rate Limiting the max frequency supported by host controller to a certain value can be useful for testing power consumption at various frequencies that are supported by the host. Note: If the card supports less than desired value then the frequency of operation would be limited to that frequency. Usage: mount -t debugfs none /sys/kernel/debug echo > /sys/kernel/debug/mmcX/max_clock cat /sys/kernel/debug/mmcX/max_clock Change-Id: I9e9a7e368f56d8e16548780288211bd8775fd048 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/debugfs.c | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index f4db93e03e88..cbc83344e0c7 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -233,6 +233,46 @@ static int mmc_clock_opt_set(void *data, u64 val) DEFINE_SIMPLE_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set, "%llu\n"); +static int mmc_max_clock_get(void *data, u64 *val) +{ + struct mmc_host *host = data; + + if (!host) + return -EINVAL; + + *val = host->f_max; + + return 0; +} + +static int mmc_max_clock_set(void *data, u64 val) +{ + struct mmc_host *host = data; + int err = -EINVAL; + unsigned long freq = val; + unsigned int old_freq; + + if (!host || (val < host->f_min)) + goto out; + + mmc_claim_host(host); + if (host->bus_ops && host->bus_ops->change_bus_speed) { + old_freq = host->f_max; + host->f_max = freq; + + err = host->bus_ops->change_bus_speed(host, &freq); + + if (err) + host->f_max = old_freq; + } + mmc_release_host(host); +out: + return err; +} + +DEFINE_SIMPLE_ATTRIBUTE(mmc_max_clock_fops, mmc_max_clock_get, + mmc_max_clock_set, "%llu\n"); + void mmc_add_host_debugfs(struct mmc_host *host) { struct dentry *root; @@ -255,6 +295,10 @@ void mmc_add_host_debugfs(struct mmc_host *host) &mmc_clock_fops)) goto err_node; + if (!debugfs_create_file("max_clock", S_IRUSR | S_IWUSR, root, host, + &mmc_max_clock_fops)) + goto err_node; + #ifdef CONFIG_MMC_CLKGATE if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR), root, &host->clk_delay)) From 20537f4aae5abd63918edc990a5ea9d355b6ad58 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 26 Feb 2013 17:32:58 +0530 Subject: [PATCH 111/472] mmc: card: abort the suspend if the card is busy mmc_blk_suspend() is triggered when system is going into suspend which then waits for the mmcqd thread (which processes active MMC transfer requests) to complete all queued transfer requests but if mmcqd has multiple requests to be processed at this point, mmc_blk_suspend() may very well sleep for duration longer than DPM (Device Power Management) timeout (currently 12 sec) causing the DPM timeout bug. To fix this issue, mmc_blk_suspend() checks if there are any more pending MMC requests in progress or not and if there are any, it returns error which should abort the ongoing system suspend. CRs-Fixed: 439192 Change-Id: Ic7ba2f013d10a1952c30ea2ed93f74b831fcc27d [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 19 ++++++++++++++++--- drivers/mmc/card/queue.c | 17 +++++++++++++++-- drivers/mmc/card/queue.h | 2 +- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index d64d7bfd08c2..996463769129 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2629,14 +2629,27 @@ static int _mmc_blk_suspend(struct mmc_card *card) { struct mmc_blk_data *part_md; struct mmc_blk_data *md = dev_get_drvdata(&card->dev); + int rc = 0; if (md) { - mmc_queue_suspend(&md->queue); + rc = mmc_queue_suspend(&md->queue); + if (rc) + goto out; list_for_each_entry(part_md, &md->part, part) { - mmc_queue_suspend(&part_md->queue); + rc = mmc_queue_suspend(&part_md->queue); + if (rc) + goto out_resume; } } - return 0; + goto out; + + out_resume: + mmc_queue_resume(&md->queue); + list_for_each_entry(part_md, &md->part, part) { + mmc_queue_resume(&part_md->queue); + } + out: + return rc; } static void mmc_blk_shutdown(struct mmc_card *card) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 6a4cd2bb4629..219a39de05e5 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -416,10 +416,11 @@ void mmc_packed_clean(struct mmc_queue *mq) * complete any outstanding requests. This ensures that we * won't suspend while a request is being processed. */ -void mmc_queue_suspend(struct mmc_queue *mq) +int mmc_queue_suspend(struct mmc_queue *mq) { struct request_queue *q = mq->queue; unsigned long flags; + int rc = 0; if (!(mq->flags & MMC_QUEUE_SUSPENDED)) { mq->flags |= MMC_QUEUE_SUSPENDED; @@ -428,8 +429,20 @@ void mmc_queue_suspend(struct mmc_queue *mq) blk_stop_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); - down(&mq->thread_sem); + rc = down_trylock(&mq->thread_sem); + if (rc) { + /* + * Failed to take the lock so better to abort the + * suspend because mmcqd thread is processing requests. + */ + mq->flags &= ~MMC_QUEUE_SUSPENDED; + spin_lock_irqsave(q->queue_lock, flags); + blk_start_queue(q); + spin_unlock_irqrestore(q->queue_lock, flags); + rc = -EBUSY; + } } + return rc; } /** diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 36cddab57d77..98acd0323046 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -63,7 +63,7 @@ struct mmc_queue { extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, const char *); extern void mmc_cleanup_queue(struct mmc_queue *); -extern void mmc_queue_suspend(struct mmc_queue *); +extern int mmc_queue_suspend(struct mmc_queue *); extern void mmc_queue_resume(struct mmc_queue *); extern unsigned int mmc_queue_map_sg(struct mmc_queue *, From a4ec42db59ec72285b0b0823c6f96ca2400f9fdd Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 26 Mar 2013 17:21:32 +0530 Subject: [PATCH 112/472] mmc: sd: Set the card removed if card detect fails Currently, the card state is not being set as removed, even when the card is actually removed. We still wait for the next request to fail and then check the error and set the card to be removed. With the new runtime-pm in place, the card device would be removed on physical removal of card and the corresponding parent would be suspended as well. Hence, when the queue is flushed during clean-up, mmc_rpm_hold would be invoked, which would fail, since the runtime-pm was disabled when the card device was removed. Thus, it won't resume the card/host/ platform device. The request would continue and since the controller is in suspended state nasty things would happen. This patch sets the card as removed during mmc_sd_detect itself, so that when a clean-up of queue happens during card removal, it is known that the card has been removed and the request is errored out there itself. Change-Id: Id5022f8d7db43750edf137d4293796615b56ae36 Signed-off-by: Asutosh Das --- drivers/mmc/core/sd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index f9b5b4ad4985..2ec08b6154d6 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1149,6 +1149,7 @@ static void mmc_sd_detect(struct mmc_host *host) if (!retries) { printk(KERN_ERR "%s(%s): Unable to re-detect card (%d)\n", __func__, mmc_hostname(host), err); + err = _mmc_detect_card_removed(host); } #else err = _mmc_detect_card_removed(host); From 03dbfc78cfc416b57f246520f14a4566b7cc888e Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 3 Dec 2014 11:12:14 +0200 Subject: [PATCH 113/472] mmc: core: export mmc_host_may_gate_card function MMC Host driver might want to use the mmc_host_may_gate_card() API to know whether the clock can be gated to the card or not hence export this function for usage outside its current scope. Change-Id: I9ec4173063e2505eca179161cdf8cda033a3fd4b Signed-off-by: Subhash Jadavani [merez@codeaurora.org: fix trivial conflicts] Signed-off-by: Maya Erez --- drivers/mmc/core/host.c | 2 +- include/linux/mmc/host.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 649b8ab923ce..42afd5318cc1 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -179,7 +179,7 @@ void mmc_host_clk_hold(struct mmc_host *host) * mmc_host_may_gate_card - check if this card may be gated * @card: card to check. */ -static bool mmc_host_may_gate_card(struct mmc_card *card) +bool mmc_host_may_gate_card(struct mmc_card *card) { /* If there is no card we may gate it */ if (!card) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 43796e98b324..4378be90366a 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -414,6 +414,7 @@ struct mmc_host { }; struct mmc_host *mmc_alloc_host(int extra, struct device *); +extern bool mmc_host_may_gate_card(struct mmc_card *); int mmc_add_host(struct mmc_host *); void mmc_remove_host(struct mmc_host *); void mmc_free_host(struct mmc_host *); From aaa56f8a9319fde381a94e08d329f936ab1dfe42 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Mon, 13 May 2013 15:00:21 +0300 Subject: [PATCH 114/472] mmc: core: fix issue with HPI polling timeout Out of CPU time for the MMC context included into measured timeout. System under heavy load will easily exceed out_of_int_time (typically 20 ms), because the MMC context is not running and when it is back to running, timeout already elapsed. In this case real card status checked again and error reported for wrong card state only. Change-Id: I0da3bd2e7d41e4933ad3b1333d7135705a1eafec Signed-off-by: Konstantin Dorfman Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0be861b5649c..863df7a85220 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -727,8 +727,13 @@ int mmc_interrupt_hpi(struct mmc_card *card) if (!err && R1_CURRENT_STATE(status) == R1_STATE_TRAN) break; - if (time_after(jiffies, prg_wait)) - err = -ETIMEDOUT; + if (time_after(jiffies, prg_wait)) { + err = mmc_send_status(card, &status); + if (!err && R1_CURRENT_STATE(status) != R1_STATE_TRAN) + err = -ETIMEDOUT; + else + break; + } } while (!err); out: From 392f2d1389b878d5b43e7ae5965f2b96be753357 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 4 Jul 2013 14:06:18 +0300 Subject: [PATCH 115/472] mmc: core: hpi in TRAN state error handling There could be a race condition in the stop transmission request. 1. Urgent read request comes in. 2. Stop the ongoing write request by calling stop_request MMC host op. 3. Send the stop command (CMD12) to move the card from the receive state to programming state. 4. Send the status command (CMD13) to know if the card is in programming state or not. If yes, then go to step.5 or else go to step.6. 5. Send the HPI command to bring the card out of programming state. 6. Now send the urgent read request. In above sequence in step.4, we check if the card is in programming or not and if it is then we go ahead and send the HPI in step.5 but what if after step.4 is completed and before the step.5, card moves out of the programming state so in that case HPI command would timeout as we are sending it in the wrong state. In that case no command response (by eMMC protocol) causes mmc_send_hpi_cmd to fail (with -ETIMEOUT error). In case the card indeed in TRAN state, no error is reported. Change-Id: Ia6f81f6cc4ec933f2e8423029f7236401aea04df Signed-off-by: Konstantin Dorfman --- drivers/mmc/core/core.c | 2 -- drivers/mmc/core/mmc_ops.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 863df7a85220..dd69ebd2780b 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -718,8 +718,6 @@ int mmc_interrupt_hpi(struct mmc_card *card) } err = mmc_send_hpi_cmd(card, &status); - if (err) - goto out; prg_wait = jiffies + msecs_to_jiffies(card->ext_csd.out_of_int_time); do { diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 1f444269ebbe..a01fcf27f171 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -779,7 +779,7 @@ int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status) err = mmc_wait_for_cmd(card->host, &cmd, 0); if (err) { - pr_warn("%s: error %d interrupting operation. " + pr_debug("%s: error %d interrupting operation. " "HPI command response %#x\n", mmc_hostname(card->host), err, cmd.resp[0]); return err; From 40c3ea6a0ce412f38d19a998c55054a879ec1071 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 13 Jun 2013 10:34:48 +0530 Subject: [PATCH 116/472] mmc: core: Fix MMC clock scaling in case of tuning failure When the clock scaling state is changed from MMC_LOAD_LOW to MMC_LOAD_HIGH, the clocks are first scaled up and then tuning is performed. But in case of tuning failure, the current code does nothing but still retain the previous clock scale stats (state and curr_freq within struct clk_scaling). Hence, correct it to scale down the clocks in case of tuning failure so that clock scaling stats reflect the correct status. This also helps proceed with data transfers at lower clock rate in such cases. Change-Id: I7e9379d1e3ddc863132af31019604c22a42f8d59 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 9 ++++++--- drivers/mmc/core/sd.c | 9 ++++++--- include/linux/mmc/host.h | 17 +++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index a116182875a9..e37c254a101e 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1352,9 +1352,12 @@ static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) MMC_SEND_TUNING_BLOCK_HS200); mmc_host_clk_release(card->host); - if (err) - pr_warn("%s: %s: tuning execution failed %d\n", - mmc_hostname(card->host), __func__, err); + if (err) { + pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n", + mmc_hostname(card->host), __func__, err, + host->clk_scaling.curr_freq); + mmc_set_clock(host, host->clk_scaling.curr_freq); + } } out: mmc_release_host(host); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 2ec08b6154d6..c1e68f40e0b2 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -625,9 +625,12 @@ static int mmc_sd_change_bus_speed(struct mmc_host *host, unsigned long *freq) MMC_SEND_TUNING_BLOCK); mmc_host_clk_release(card->host); - if (err) - pr_warn("%s: %s: tuning execution failed %d\n", - mmc_hostname(card->host), __func__, err); + if (err) { + pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n", + mmc_hostname(card->host), __func__, err, + host->clk_scaling.curr_freq); + mmc_set_clock(host, host->clk_scaling.curr_freq); + } } out: diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 4378be90366a..e173a6e3462e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -405,6 +405,23 @@ struct mmc_host { } embedded_sdio_data; #endif + struct { + unsigned long busy_time_us; + unsigned long window_time; + unsigned long curr_freq; + unsigned long polling_delay_ms; + unsigned int up_threshold; + unsigned int down_threshold; + ktime_t start_busy; + bool enable; + bool initialized; + bool in_progress; + /* freq. transitions are not allowed in invalid state */ + bool invalid_state; + struct delayed_work work; + enum mmc_load state; + } clk_scaling; + /* * Set to 1 to just stop the SDCLK to the card without * actually disabling the clock from it's source. From 636cddbaa907ee1f37bf03e642ae4086e819c564 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 3 Dec 2014 12:31:10 +0200 Subject: [PATCH 117/472] mmc: core: Fix bustest timeout value The current code doesn't set any timeout value for bus test commands CMD19 and CMD14. This fails bus test because the controller returns DATATIMEOUT error for these commands. Fix the bus test timeout value to 1ms for all cards. Change-Id: I12446e094302f51853cc0c56a5c13b4a6a46d8cb Signed-off-by: Sahitya Tummala [merez@codeaurora.org: fix conflict due to change of code] Signed-off-by: Maya Erez --- drivers/mmc/core/mmc_ops.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index a01fcf27f171..71686caa0f6a 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -713,7 +713,10 @@ mmc_send_bus_test(struct mmc_card *card, struct mmc_host *host, u8 opcode, data.sg = &sg; data.sg_len = 1; + data.timeout_ns = 1000000; + data.timeout_clks = 0; mmc_set_data_timeout(&data, card); + sg_init_one(&sg, data_buf, len); mmc_wait_for_req(host, &mrq); err = 0; From 6b2e9c4ce0645fee95ba0aa24d7875706d9cfd04 Mon Sep 17 00:00:00 2001 From: Sujith Reddy Thumma Date: Thu, 17 Feb 2011 21:37:48 +0530 Subject: [PATCH 118/472] mmc: core: Fix race between runtime PM suspend and block requests There is a possible race with mmc_claim_host() in mmc_sd_suspend()/ mmc_suspend() and mmc_claim_host() in mmc_blk_issue_rq() when runtime pm is enabled. Fix this by blocking processing of requests until the previous runtime suspend is processed and then resume as part of msmsdcc_enable(). Previous fix has card detection failure as a side effect during resume. Change-Id: I9cb31269638d9db4e630eb22b973a5335af1bda4 Signed-off-by: Sujith Reddy Thumma [sboyd: Dropped msm driver change] Signed-off-by: Stephen Boyd [subhashj@codeaurora.org: fixed trivial merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 9 +++++++++ include/linux/mmc/host.h | 1 + 2 files changed, 10 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index dd69ebd2780b..9b27006a4452 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -966,6 +967,14 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) might_sleep(); add_wait_queue(&host->wq, &wait); +#ifdef CONFIG_PM_RUNTIME + while (mmc_dev(host)->power.runtime_status == RPM_SUSPENDING) { + if (host->suspend_task == current) + break; + msleep(15); + } +#endif + spin_lock_irqsave(&host->lock, flags); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index e173a6e3462e..01ae900ef4bf 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -357,6 +357,7 @@ struct mmc_host { wait_queue_head_t wq; struct task_struct *claimer; /* task that has host claimed */ + struct task_struct *suspend_task; int claim_cnt; /* "claim" nesting count */ struct delayed_work detect; From 81e08fb2ca97b65841ee03c14a60dea2b742c99d Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Wed, 24 Nov 2010 08:48:18 +0200 Subject: [PATCH 119/472] mmc: core: Workaround to fix a hardware bug in Qualcomm-SDIO client on MDM Qualcomm-SDIO client on MDM contains NULL tuple for all SDIO functions 1 to 7 and doesn't contain any valid data. This patch is a temporary software workaround (not to read CIS if it is NULL tuple) until the hardware bug is resolved. The device ID 0x23F0 is for the SDIO client core on MDM8220. The device ID 0x23F1 is used for msm8660_charm. Change-Id: I82f44d313b3d499462977a0a9bbc0759f489ae95 Signed-off-by: Maya Erez Signed-off-by: Murali Palnati Signed-off-by: Raj Kushwaha [sboyd: Squash in two other device id patches] Signed-off-by: Stephen Boyd --- drivers/mmc/core/sdio_cis.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 8e94e555b788..1ab4560003ef 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -270,8 +270,16 @@ static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) break; /* null entries have no link field or data */ - if (tpl_code == 0x00) - continue; + if (tpl_code == 0x00) { + if (card->cis.vendor == 0x70 && + (card->cis.device == 0x2460 || + card->cis.device == 0x0460 || + card->cis.device == 0x23F1 || + card->cis.device == 0x23F0)) + break; + else + continue; + } ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link); if (ret) From abc814b36465209cbc31cc7094e61455cac5c78f Mon Sep 17 00:00:00 2001 From: Murali Palnati Date: Mon, 11 Apr 2011 18:05:58 +0530 Subject: [PATCH 120/472] mmc: sdio: Fix sdio_disable_wide to properly handle 8 bit bus width. Change-Id: I2e712f5d8f6ff8da9fdabe8cf30e378c560e067f Signed-off-by: Murali Palnati --- drivers/mmc/core/sdio.c | 4 ++-- include/linux/mmc/sdio.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 61bb6c739b45..c3e6e5649f37 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -277,10 +277,10 @@ static int sdio_disable_wide(struct mmc_card *card) if (ret) return ret; - if (!(ctrl & SDIO_BUS_WIDTH_4BIT)) + if (!(ctrl & (SDIO_BUS_WIDTH_4BIT | SDIO_BUS_WIDTH_8BIT))) return 0; - ctrl &= ~SDIO_BUS_WIDTH_4BIT; + ctrl &= ~(SDIO_BUS_WIDTH_4BIT | SDIO_BUS_WIDTH_8BIT); ctrl |= SDIO_BUS_ASYNC_INT; ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index 17446d3c3602..2c274d0530d2 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -102,6 +102,7 @@ #define SDIO_BUS_WIDTH_1BIT 0x00 #define SDIO_BUS_WIDTH_RESERVED 0x01 #define SDIO_BUS_WIDTH_4BIT 0x02 +#define SDIO_BUS_WIDTH_8BIT 0x03 #define SDIO_BUS_ECSI 0x20 /* Enable continuous SPI interrupt */ #define SDIO_BUS_SCSI 0x40 /* Support continuous SPI interrupt */ From 710b297f4c0ad689c544cf5f20c50d97349899c5 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 13 Jun 2011 11:39:58 +0530 Subject: [PATCH 121/472] mmc: core: Add 8-bit support for SDIO Depending on the host and card's capability SDIO cards can operate at 8-bit buswidth. Change-Id: I2ed1c5ecaff521db693db47928b0958ed0289645 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/sdio.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index c3e6e5649f37..80966a6bf3ae 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -210,7 +210,7 @@ static int sdio_enable_wide(struct mmc_card *card) int ret; u8 ctrl; - if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + if (!(card->host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) return 0; if (card->cccr.low_speed && !card->cccr.wide_bus) @@ -226,7 +226,10 @@ static int sdio_enable_wide(struct mmc_card *card) /* set as 4-bit bus width */ ctrl &= ~SDIO_BUS_WIDTH_MASK; - ctrl |= SDIO_BUS_WIDTH_4BIT; + if (card->host->caps & MMC_CAP_8_BIT_DATA) + ctrl |= SDIO_BUS_WIDTH_8BIT; + else if (card->host->caps & MMC_CAP_4_BIT_DATA) + ctrl |= SDIO_BUS_WIDTH_4BIT; ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); if (ret) @@ -267,7 +270,7 @@ static int sdio_disable_wide(struct mmc_card *card) int ret; u8 ctrl; - if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + if (!(card->host->caps & (MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA))) return 0; if (card->cccr.low_speed && !card->cccr.wide_bus) @@ -791,7 +794,12 @@ try_again: * Switch to wider bus (if supported). */ err = sdio_enable_4bit_bus(card); - if (err) + if (err > 0) { + if (card->host->caps & MMC_CAP_8_BIT_DATA) + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_8); + else if (card->host->caps & MMC_CAP_4_BIT_DATA) + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + } else if (err) goto remove; } finish: @@ -974,6 +982,13 @@ static int mmc_sdio_resume(struct mmc_host *host) } else if (mmc_card_keep_power(host) && mmc_card_wake_sdio_irq(host)) { /* We may have switched to 1-bit mode during suspend */ err = sdio_enable_4bit_bus(host->card); + if (err > 0) { + if (host->caps & MMC_CAP_8_BIT_DATA) + mmc_set_bus_width(host, MMC_BUS_WIDTH_8); + else if (host->caps & MMC_CAP_4_BIT_DATA) + mmc_set_bus_width(host, MMC_BUS_WIDTH_4); + err = 0; + } } if (!err && host->sdio_irqs) { From 267e3b25f4327741a6790bc957b99afd1e4c9e4b Mon Sep 17 00:00:00 2001 From: Sujith Reddy Thumma Date: Mon, 20 Dec 2010 16:27:20 +0530 Subject: [PATCH 122/472] mmc: core: Determine correct access mode for eMMC cards by reading OCR MMC core reads SEC_COUNT information from EXT_CSD register and assumes that the card supports sector access mode. Some eMMC cards (<=2GB) do not support this mode even though they have SEC_COUNT value defined, causing failure while populating extended partitions. Sector/Byte access mode information is stored in OCR register. Hence, read OCR bit 30 and then confirm it with SEC_COUNT value to know whether the card supports sector access mode or byte access mode. Change-Id: Ifdfff35309e8667cd2c2ac2761b9a708d5b785d3 Signed-off-by: Sujith Reddy Thumma --- drivers/mmc/core/mmc.c | 3 +++ include/linux/mmc/mmc.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index e37c254a101e..fd910a73d8be 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1562,6 +1562,9 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* Erase size depends on CSD and Extended CSD */ mmc_set_erase_size(card); + + if (card->ext_csd.sectors && (rocr & MMC_CARD_SECTOR_ADDR)) + mmc_card_set_blockaddr(card); } /* diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 5b085532fc2d..6f5b437311c2 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -165,6 +165,7 @@ struct _mmc_csd { * OCR bits are mostly in host.h */ #define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */ +#define MMC_CARD_SECTOR_ADDR 0x40000000 /* Card supports sectors */ /* * Card Command Classes (CCC) From 54e1258f91fde23794e86aa36b3ff92c2bdb7de9 Mon Sep 17 00:00:00 2001 From: Sujith Reddy Thumma Date: Thu, 28 Apr 2011 18:29:34 +0530 Subject: [PATCH 123/472] mmc: core: Claim host while freeing card When SD/MMC card is removed without unmounting and then reinserted again while gendisk is freed. Partition invalidation and deletion may take more than 10secs during which SDCC controller can suspend due to runtime pm functionality. Once the block layer activity is done it claims host to detach card from MMC bus which triggers SDCC resume. As the host->card is NULL already there is a BUG_ON hit in mmc_sd_resume(). Fix this by claiming host even before we free host->card. CRs-Fixed: 284262 Signed-off-by: Sujith Reddy Thumma (cherry picked from commit 5eed6a9778440ef512f597b43368337a7ef9438b) Change-Id: I6b58aab1865a93a025fd9912200ab0beea21be92 Signed-off-by: Shruthi Krishna Signed-off-by: David Brown --- drivers/mmc/core/mmc.c | 3 +++ drivers/mmc/core/sd.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index fd910a73d8be..d9b64b50ce4b 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1827,7 +1827,10 @@ static void mmc_remove(struct mmc_host *host) BUG_ON(!host->card); mmc_remove_card(host->card); + + mmc_claim_host(host); host->card = NULL; + mmc_release_host(host); } /* diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index c1e68f40e0b2..bd5e43a67c8b 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1110,7 +1110,10 @@ static void mmc_sd_remove(struct mmc_host *host) BUG_ON(!host->card); mmc_remove_card(host->card); + + mmc_claim_host(host); host->card = NULL; + mmc_release_host(host); } /* From e44c2b3456e4c525d3fc96b33f2ab83660fb51e0 Mon Sep 17 00:00:00 2001 From: Sujith Reddy Thumma Date: Mon, 8 Aug 2011 12:59:48 +0530 Subject: [PATCH 124/472] mmc: core: Skip frequency retries for SDCC slots Qualcomm SDCC controller supports minimum SD clock frequency which is required for card initialization. This information is exported through platform data for each SDCC controller. There is no need of retrying higher frequencies than the minimum supported by controller for Qualcomm chipsets which inturn add delay in detection process if there is no card during suspend/resume cycles. Hence, skip multiple frequency retries. Change-Id: If1532213aaf265bdb4f5ddf68d9f70005ed97037 Signed-off-by: Sujith Reddy Thumma [subhashj@codeaurora.org: fixed trivial merge conflicts and compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9b27006a4452..1bc2e5cab5fe 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2710,7 +2710,6 @@ void mmc_rescan(struct work_struct *work) { struct mmc_host *host = container_of(work, struct mmc_host, detect.work); - int i; if (host->trigger_card_event && host->ops->card_event) { host->ops->card_event(host); @@ -2765,12 +2764,7 @@ void mmc_rescan(struct work_struct *work) } mmc_claim_host(host); - for (i = 0; i < ARRAY_SIZE(freqs); i++) { - if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) - break; - if (freqs[i] <= host->f_min) - break; - } + mmc_rescan_try_freq(host, host->f_min); mmc_release_host(host); out: From d0cd16e2aea4a574f527c4351230d299fcd42b32 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Tue, 18 Oct 2011 14:57:27 +0530 Subject: [PATCH 125/472] mmc: Fixing few warning messages. CRs-Fixed: 313388 Change-Id: Ibf8ec6bc9eb7adff9bb6ac6af2a2f1af33a76bb8 Signed-off-by: Pratibhasagar V --- drivers/mmc/core/sdio_cis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 1ab4560003ef..76a4508126dd 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -55,7 +55,7 @@ static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, for (i = 0; i < nr_strings; i++) { buffer[i] = string; - strcpy(string, buf); + strlcpy(string, buf, sizeof(string)); string += strlen(string) + 1; buf += strlen(buf) + 1; } From eb645aebffa8e1c12c56ff1d3e659391c38b6b16 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 1 Nov 2011 20:05:07 +0530 Subject: [PATCH 126/472] mmc: core: Use usleep_range for delays less than 20ms msleep of any delay less than 20ms (or 2 jiffies) would give an unpredictable delay (most of the times as ~20ms). Use usleep_range in mmc_delay() for delays less than two jiffies. Change-Id: I7cf22532af2b4048afccf07a5aeba89d808799f0 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/core.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index c5cb975dbcbb..d57daacbfcca 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -63,6 +63,8 @@ static inline void mmc_delay(unsigned int ms) if (ms < 1000 / HZ) { cond_resched(); mdelay(ms); + } else if (ms < jiffies_to_msecs(2)) { + usleep_range(ms * 1000, (ms + 1) * 1000); } else { msleep(ms); } From 69ba619f8c95011100fb827027f97e167d5a788d Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 14 Dec 2011 21:17:08 +0530 Subject: [PATCH 127/472] mmc: msm_sdcc: Fix deadlock in mmc_suspend_host and mmc_rescan SDCC runtime suspend can race with mmc_rescan causing deadlock. The call flow would be this: 1) When runtime suspend is triggered: msmsdcc_runtime_suspend() -> mmc_suspend_host() -> mmc_flush_scheduled_work() 2) mmc_flush_schedule_work() waits for mmc_rescan() work to be completed if it is already in runqueue. 3) When mmc_rescan() is scheduled to run: mmc_claim_host() -> msmsdcc_enable() ->pm_runtime_get_sync() 4) pm_runtime_get_sync() waits for runtime_suspend to complete, and hence cause two threads to wait upon each other. Fix this deadlock by aborting runtime suspend when mmc_rescan is scheduled. CRs-Fixed: 326610 Change-Id: I4ca88651f80f5a1bfccb6e0c07e3ea83fadcdc57 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/core.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 1bc2e5cab5fe..c53a44de86f2 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -967,13 +967,6 @@ int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) might_sleep(); add_wait_queue(&host->wq, &wait); -#ifdef CONFIG_PM_RUNTIME - while (mmc_dev(host)->power.runtime_status == RPM_SUSPENDING) { - if (host->suspend_task == current) - break; - msleep(15); - } -#endif spin_lock_irqsave(&host->lock, flags); while (1) { From 71743a7317a54481627df515c8b3344583457a39 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 3 Dec 2014 18:05:18 +0200 Subject: [PATCH 128/472] mmc: sd: fix the issue with paranoid SD card init retry mmc_resume_host() is called during resume (runtime or system) which calls 1. mmc_power_up 2. mmc_select_voltage 3. host->bus_ops->resume() (mmc_sd_resume()) mmc_sd_resume() ultimately calls mmc_sd_init_card() to initialize the SD card. But let's say if mmc_sd_init_card() fails during the execute_tuning(). At this point, host controller timing is changed to SDR104/SDR50 mode and host clock will also be > 100 MHz. If there is an error returned by mmc_sd_init_card() and if CONFIG_MMC_PARANOID_SD_INIT defined, it retries by calling mmc_sd_init_card() without making sure that host controller timing and clock be initialized again. This may cause the further mmc_sd_init_card() also to fail. To fix this, mmc_sd_resume() should call mmc_power_up() and mmc_select_voltage() before retrying. Change-Id: I8de39ea547fa0d5eca478719a4cf9255b6652503 [merez@codeaurora.org: changes already present in core.h and core.c] Signed-off-by: Maya Erez [venkatg@codeaurora.org: Fix args for mmc_power_up and mmc_select_voltage as used in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/sd.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index bd5e43a67c8b..29b3447bff9e 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1242,8 +1242,11 @@ static int _mmc_sd_resume(struct mmc_host *host) if (err) { printk(KERN_ERR "%s: Re-init card rc = %d (retries = %d)\n", mmc_hostname(host), err, retries); - mdelay(5); retries--; + mmc_power_off(host); + usleep_range(5000, 5500); + mmc_power_up(host, host->card->ocr); + mmc_select_voltage(host, host->card->ocr); continue; } break; @@ -1382,6 +1385,10 @@ int mmc_attach_sd(struct mmc_host *host) err = mmc_sd_init_card(host, rocr, NULL); if (err) { retries--; + mmc_power_off(host); + usleep_range(5000, 5500); + mmc_power_up(host, host->card->ocr); + mmc_select_voltage(host, host->card->ocr); continue; } break; From 6e40d1440803a1ad8aff848f151a848b91c88188 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 3 Dec 2014 18:10:07 +0200 Subject: [PATCH 129/472] mmc: core: Export mmc_set_ios so that host drivers can use it mmc_set_ios() is used by host drivers during suspend/resume routines in indirect way i.e., by calling host->ops->set_ios(). But now with MMC_CLKGATE enabled, mmc_set_ios() also updates host->clk_gated flag. So export this API so that host controller drivers can use it. Change-Id: Ib0c177635bb8d87ba68c98e08b8d940c73f2b80c Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fix trivial conflicts] Signed-off-by: Maya Erez --- drivers/mmc/core/core.c | 3 ++- include/linux/mmc/core.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index c53a44de86f2..07d2c2a24c66 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1058,7 +1058,7 @@ EXPORT_SYMBOL(mmc_put_card); * Internal function that does the actual ios call to the host driver, * optionally printing some debug output. */ -static inline void mmc_set_ios(struct mmc_host *host) +void mmc_set_ios(struct mmc_host *host) { struct mmc_ios *ios = &host->ios; @@ -1072,6 +1072,7 @@ static inline void mmc_set_ios(struct mmc_host *host) mmc_set_ungated(host); host->ops->set_ios(host, ios); } +EXPORT_SYMBOL(mmc_set_ios); /* * Control chip select pin on a host. diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index f7b930b735fc..c5f305249919 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -161,6 +161,7 @@ extern void mmc_release_host(struct mmc_host *host); extern void mmc_get_card(struct mmc_card *card); extern void mmc_put_card(struct mmc_card *card); +extern void mmc_set_ios(struct mmc_host *host); extern int mmc_flush_cache(struct mmc_card *); extern int mmc_detect_card_removed(struct mmc_host *host); From e3b5cec78913a9f23776364eb7c6a28923bd279c Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 3 Dec 2014 18:13:58 +0200 Subject: [PATCH 130/472] mmc: core: Remove BROKEN_CLK_GATING quirk for WCN1314 cards MMC_CLKGATE feature can be enabled for Volans cards as the host supports asynchronous acitivity notification when the clocks are off. Change-Id: Ic6cfa7fb2e713dcf1d2c6b2ae9df9cf0394f8c4a Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fix trivial conflicts] Signed-off-by: Maya Erez --- drivers/mmc/core/core.c | 4 +++- drivers/mmc/core/quirks.c | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 07d2c2a24c66..909af2984433 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1115,6 +1115,8 @@ void mmc_gate_clock(struct mmc_host *host) { unsigned long flags; + WARN_ON(!host->ios.clock); + spin_lock_irqsave(&host->clk_lock, flags); host->clk_old = host->ios.clock; host->ios.clock = 0; @@ -1137,7 +1139,7 @@ void mmc_ungate_clock(struct mmc_host *host) * we just ignore the call. */ if (host->clk_old) { - BUG_ON(host->ios.clock); + WARN_ON(host->ios.clock); /* This call will also set host->clk_gated to false */ __mmc_set_clock(host, host->clk_old); } diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index dd1d1e0fe322..1adfb2d84248 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -35,6 +35,14 @@ #define SDIO_DEVICE_ID_MARVELL_8797_F0 0x9128 #endif +#ifndef SDIO_VENDOR_ID_MSM +#define SDIO_VENDOR_ID_MSM 0x0070 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_WCN1314 +#define SDIO_DEVICE_ID_MSM_WCN1314 0x2881 +#endif + /* * This hook just adds a quirk for all sdio devices */ @@ -54,6 +62,9 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_MSM, SDIO_DEVICE_ID_MSM_WCN1314, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_NONSTD_FUNC_IF), From 0f618d04ba4734411cebfdf2f3f2dbcb47326e0c Mon Sep 17 00:00:00 2001 From: Aparna Mallavarapu Date: Fri, 11 Jun 2010 18:13:05 +0530 Subject: [PATCH 131/472] mmc: Add profiling code to measure performance at MMC layers. Profiling code is added to measure read, write times for the MMC requests at various MMC layers. Profiling is done at the MMC queue and at the driver level. This information can be viewed through a sysfs entry called perf. Change-Id: I7c65bfe25a1f7774e3a9abf1f9539e690b3718ec Signed-off-by: Aparna Mallavarapu Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflict] Signed-off-by: Subhash Jadavani --- drivers/mmc/Kconfig | 8 +++++ drivers/mmc/core/core.c | 20 +++++++++++ drivers/mmc/core/host.c | 71 ++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/host.h | 16 +++++++++ 4 files changed, 115 insertions(+) diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig index f2eeb38efa65..91165514156e 100644 --- a/drivers/mmc/Kconfig +++ b/drivers/mmc/Kconfig @@ -19,6 +19,14 @@ config MMC_DEBUG This is an option for use by developers; most people should say N here. This enables MMC core and driver debugging. +config MMC_PERF_PROFILING + bool "MMC performance profiling" + depends on MMC != n + default n + help + If you say Y here, support will be added for collecting + performance numbers at the MMC Queue and Host layers. + if MMC source "drivers/mmc/core/Kconfig" diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 909af2984433..48ad5f168b49 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -135,6 +135,9 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) { struct mmc_command *cmd = mrq->cmd; int err = cmd->error; +#ifdef CONFIG_MMC_PERF_PROFILING + ktime_t diff; +#endif /* Flag re-tuning needed on CRC errors */ if ((cmd->opcode != MMC_SEND_TUNING_BLOCK && @@ -175,6 +178,20 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->resp[2], cmd->resp[3]); if (mrq->data) { +#ifdef CONFIG_MMC_PERF_PROFILING + diff = ktime_sub(ktime_get(), host->perf.start); + if (mrq->data->flags == MMC_DATA_READ) { + host->perf.rbytes_drv += + mrq->data->bytes_xfered; + host->perf.rtime_drv = + ktime_add(host->perf.rtime_drv, diff); + } else { + host->perf.wbytes_drv += + mrq->data->bytes_xfered; + host->perf.wtime_drv = + ktime_add(host->perf.wtime_drv, diff); + } +#endif pr_debug("%s: %d bytes transferred: %d\n", mmc_hostname(host), mrq->data->bytes_xfered, mrq->data->error); @@ -295,6 +312,9 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->error = 0; mrq->stop->mrq = mrq; } +#ifdef CONFIG_MMC_PERF_PROFILING + host->perf.start = ktime_get(); +#endif } mmc_host_clk_hold(host); led_trigger_event(host->led, LED_FULL); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 42afd5318cc1..c940ad0bd3fe 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -610,6 +610,70 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) } EXPORT_SYMBOL(mmc_alloc_host); +#ifdef CONFIG_MMC_PERF_PROFILING +static ssize_t +show_perf(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = dev_get_drvdata(dev); + int64_t rtime_mmcq, wtime_mmcq, rtime_drv, wtime_drv; + unsigned long rbytes_mmcq, wbytes_mmcq, rbytes_drv, wbytes_drv; + + spin_lock(&host->lock); + + rbytes_mmcq = host->perf.rbytes_mmcq; + wbytes_mmcq = host->perf.wbytes_mmcq; + rbytes_drv = host->perf.rbytes_drv; + wbytes_drv = host->perf.wbytes_drv; + + rtime_mmcq = ktime_to_us(host->perf.rtime_mmcq); + wtime_mmcq = ktime_to_us(host->perf.wtime_mmcq); + rtime_drv = ktime_to_us(host->perf.rtime_drv); + wtime_drv = ktime_to_us(host->perf.wtime_drv); + + spin_unlock(&host->lock); + + return snprintf(buf, PAGE_SIZE, "Write performance at MMCQ Level:" + "%lu bytes in %lld microseconds\n" + "Read performance at MMCQ Level:" + "%lu bytes in %lld microseconds\n" + "Write performance at driver Level:" + "%lu bytes in %lld microseconds\n" + "Read performance at driver Level:" + "%lu bytes in %lld microseconds\n", + wbytes_mmcq, wtime_mmcq, rbytes_mmcq, + rtime_mmcq, wbytes_drv, wtime_drv, + rbytes_drv, rtime_drv); +} + +static ssize_t +set_perf(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int64_t value; + struct mmc_host *host = dev_get_drvdata(dev); + sscanf(buf, "%lld", &value); + if (!value) { + spin_lock(&host->lock); + memset(&host->perf, 0, sizeof(host->perf)); + spin_unlock(&host->lock); + } + + return count; +} + +static DEVICE_ATTR(perf, S_IRUGO | S_IWUSR, + show_perf, set_perf); +#endif + +static struct attribute *dev_attrs[] = { +#ifdef CONFIG_MMC_PERF_PROFILING + &dev_attr_perf.attr, +#endif + NULL, +}; +static struct attribute_group dev_attr_grp = { + .attrs = dev_attrs, +}; /** * mmc_add_host - initialise host hardware @@ -637,6 +701,11 @@ int mmc_add_host(struct mmc_host *host) #endif mmc_host_clk_sysfs_init(host); + err = sysfs_create_group(&host->parent->kobj, &dev_attr_grp); + if (err) + pr_err("%s: failed to create sysfs group with err %d\n", + __func__, err); + mmc_start_host(host); if (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)) register_pm_notifier(&host->pm_notify); @@ -664,6 +733,8 @@ void mmc_remove_host(struct mmc_host *host) #ifdef CONFIG_DEBUG_FS mmc_remove_host_debugfs(host); #endif + sysfs_remove_group(&host->parent->kobj, &dev_attr_grp); + device_del(&host->class_dev); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 01ae900ef4bf..30d33363079b 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -428,6 +428,22 @@ struct mmc_host { * actually disabling the clock from it's source. */ bool card_clock_off; + +#ifdef CONFIG_MMC_PERF_PROFILING + struct { + + unsigned long rbytes_mmcq; /* Rd bytes MMC queue */ + unsigned long wbytes_mmcq; /* Wr bytes MMC queue */ + unsigned long rbytes_drv; /* Rd bytes MMC Host */ + unsigned long wbytes_drv; /* Wr bytes MMC Host */ + ktime_t rtime_mmcq; /* Rd time MMC queue */ + ktime_t wtime_mmcq; /* Wr time MMC queue */ + ktime_t rtime_drv; /* Rd time MMC Host */ + ktime_t wtime_drv; /* Wr time MMC Host */ + ktime_t start; + } perf; +#endif + unsigned long private[0] ____cacheline_aligned; }; From 9a092183816a05f802ce3d4d7815b214413d6ad8 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Sun, 25 Mar 2012 11:15:41 +0530 Subject: [PATCH 132/472] mmc: core: capture performance numbers only when asked Currently performance numbers are captured for each SDCC transfers unconditionally which may add the overhead and could reduce the SDCC read/write throughput numbers. This change adds additional control for enabling/disabling the capturing of performance numbers at runtime. We already have sysfs entry named "perf" for msm sdcc devices. Currently setting this entry to 0 clears the performance statistics. But now we are changing the definition of this entry as mentioned below: Disable performance capturing and clear the performance statistics: "echo 0 > /sys/devices/platform/msm_sdcc./perf" Enable performance capturing: "echo 1 > /sys/devices/platform/msm_sdcc./perf" CRs-fixed: 345170 Change-Id: I3ab9288fd87cc8a8ada6c0c3d066cac4f68d79b7 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 27 ++++++++++++++++----------- drivers/mmc/core/host.c | 9 +++++++-- include/linux/mmc/host.h | 1 + 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 48ad5f168b49..f08d160b7954 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -179,17 +179,21 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) if (mrq->data) { #ifdef CONFIG_MMC_PERF_PROFILING - diff = ktime_sub(ktime_get(), host->perf.start); - if (mrq->data->flags == MMC_DATA_READ) { - host->perf.rbytes_drv += + if (host->perf_enable) { + diff = ktime_sub(ktime_get(), host->perf.start); + if (mrq->data->flags == MMC_DATA_READ) { + host->perf.rbytes_drv += + mrq->data->bytes_xfered; + host->perf.rtime_drv = + ktime_add(host->perf.rtime_drv, + diff); + } else { + host->perf.wbytes_drv += mrq->data->bytes_xfered; - host->perf.rtime_drv = - ktime_add(host->perf.rtime_drv, diff); - } else { - host->perf.wbytes_drv += - mrq->data->bytes_xfered; - host->perf.wtime_drv = - ktime_add(host->perf.wtime_drv, diff); + host->perf.wtime_drv = + ktime_add(host->perf.wtime_drv, + diff); + } } #endif pr_debug("%s: %d bytes transferred: %d\n", @@ -313,7 +317,8 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mrq->stop->mrq = mrq; } #ifdef CONFIG_MMC_PERF_PROFILING - host->perf.start = ktime_get(); + if (host->perf_enable) + host->perf.start = ktime_get(); #endif } mmc_host_clk_hold(host); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index c940ad0bd3fe..38fe00671649 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -651,18 +651,23 @@ set_perf(struct device *dev, struct device_attribute *attr, { int64_t value; struct mmc_host *host = dev_get_drvdata(dev); + sscanf(buf, "%lld", &value); + spin_lock(&host->lock); if (!value) { - spin_lock(&host->lock); memset(&host->perf, 0, sizeof(host->perf)); - spin_unlock(&host->lock); + host->perf_enable = false; + } else { + host->perf_enable = true; } + spin_unlock(&host->lock); return count; } static DEVICE_ATTR(perf, S_IRUGO | S_IWUSR, show_perf, set_perf); + #endif static struct attribute *dev_attrs[] = { diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 30d33363079b..0600ae0d3cb5 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -442,6 +442,7 @@ struct mmc_host { ktime_t wtime_drv; /* Wr time MMC Host */ ktime_t start; } perf; + bool perf_enable; #endif unsigned long private[0] ____cacheline_aligned; From 2c7404ac2598a7086d07c521ec4a9fa0f9c59858 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 20 Mar 2012 14:25:07 +0530 Subject: [PATCH 133/472] mmc: mmc_test: Fix possible NULL pointer dereference test->highmem can be NULL if no page can be allocated. In this case don't try to free it which can cause NULL pointer dereference crash. Change-Id: I6c31a265fd801dfc4169530e3f39ffcf6590f5a8 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/card/mmc_test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c index 7fc9174d4619..9766dcc95602 100644 --- a/drivers/mmc/card/mmc_test.c +++ b/drivers/mmc/card/mmc_test.c @@ -2807,7 +2807,8 @@ static ssize_t mtf_test_write(struct file *file, const char __user *buf, } #ifdef CONFIG_HIGHMEM - __free_pages(test->highmem, BUFFER_ORDER); + if (test->highmem) + __free_pages(test->highmem, BUFFER_ORDER); #endif kfree(test->buffer); kfree(test); From 0513b6cb1ebfc6b3eb505842f4b5ffffce231351 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Wed, 3 Dec 2014 18:26:42 +0200 Subject: [PATCH 134/472] mmc: quirks: Fix data timeout values for certain SanDisk eMMC cards Some INAND MCP devices advertise incorrect data timeout values. This leads to data timeout errors on the platform. So, add a quirk for such devices to facilitate proper functionality. CRs-Fixed: 355347 Change-Id: If4fdd2724dc407450da8529222efca7ee94f50df Signed-off-by: Pratibhasagar V [merez@codeaurora.org: fix num of quirk to an additional quirk on 3.14] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 4 ++++ drivers/mmc/core/core.c | 5 +++++ include/linux/mmc/card.h | 2 ++ 3 files changed, 11 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 996463769129..4ae50932c031 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2550,6 +2550,10 @@ static const struct mmc_fixup blk_fixups[] = MMC_FIXUP("V10016", CID_MANFID_KINGSTON, CID_OEMID_ANY, add_quirk_mmc, MMC_QUIRK_TRIM_BROKEN), + /* Some INAND MCP devices advertise incorrect timeout values */ + MMC_FIXUP("SEM04G", 0x45, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_INAND_DATA_TIMEOUT), + END_FIXUP }; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index f08d160b7954..26108aacf7a0 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -942,6 +942,11 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) data->timeout_ns = 100000000; /* 100ms */ } } + /* Increase the timeout values for some bad INAND MCP devices */ + if (card->quirks & MMC_QUIRK_INAND_DATA_TIMEOUT) { + data->timeout_ns = 4000000000u; /* 4s */ + data->timeout_clks = 0; + } } EXPORT_SYMBOL(mmc_set_data_timeout); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index fdd0779ccdfa..d89132c1b067 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -280,6 +280,8 @@ struct mmc_card { #define MMC_QUIRK_SEC_ERASE_TRIM_BROKEN (1<<10) /* Skip secure for erase/trim */ #define MMC_QUIRK_BROKEN_IRQ_POLLING (1<<11) /* Polling SDIO_CCCR_INTx could create a fake interrupt */ #define MMC_QUIRK_TRIM_BROKEN (1<<12) /* Skip trim */ + /* byte mode */ +#define MMC_QUIRK_INAND_DATA_TIMEOUT (1<<13) /* For incorrect data timeout */ unsigned int erase_size; /* erase size in sectors */ From 16f0e48102eb69e5d4858157faa275de551a0b9d Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 14 May 2012 16:19:31 -0700 Subject: [PATCH 135/472] mmc: core: Remove BROKEN_CLK_GATING quirk for AR6003 cards MMC_CLKGATE feature can be enabled for Atheros AR6003 cards as the host supports asynchronous acitivity notification when the clocks are off. Change-Id: Ieff00382894a0841496776dc6a6b1db53e204d6d Signed-off-by: Krishna Konda --- drivers/mmc/core/quirks.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index 1adfb2d84248..2a2704d9731b 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -43,6 +43,18 @@ #define SDIO_DEVICE_ID_MSM_WCN1314 0x2881 #endif +#ifndef SDIO_VENDOR_ID_MSM_QCA +#define SDIO_VENDOR_ID_MSM_QCA 0x271 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_QCA_AR6003_1 +#define SDIO_DEVICE_ID_MSM_QCA_AR6003_1 0x300 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_QCA_AR6003_2 +#define SDIO_DEVICE_ID_MSM_QCA_AR6003_2 0x301 +#endif + /* * This hook just adds a quirk for all sdio devices */ @@ -65,6 +77,12 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_MSM, SDIO_DEVICE_ID_MSM_WCN1314, remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6003_1, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + + SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6003_2, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_NONSTD_FUNC_IF), From 357f7f515e66f86627ad7f3dfd04263ebfb775f9 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 22 May 2012 22:59:54 +0530 Subject: [PATCH 136/472] mmc: host: remove mmcq performance numbers statistics mmcq performance numbers are not captured since asynchronous MMC request support got added in MMC block driver. So printing out these numbers (which are all zeros) just adds confusion. This patch removes the printing of mmcq performance numbers statistics. CRs-Fixed: 364206 Change-Id: I7213b11c8e9e055894c9902af7e975de3be1c519 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/host.c | 17 ++++------------- include/linux/mmc/host.h | 4 ---- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 38fe00671649..44560b15ce57 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -615,33 +615,24 @@ static ssize_t show_perf(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = dev_get_drvdata(dev); - int64_t rtime_mmcq, wtime_mmcq, rtime_drv, wtime_drv; - unsigned long rbytes_mmcq, wbytes_mmcq, rbytes_drv, wbytes_drv; + int64_t rtime_drv, wtime_drv; + unsigned long rbytes_drv, wbytes_drv; spin_lock(&host->lock); - rbytes_mmcq = host->perf.rbytes_mmcq; - wbytes_mmcq = host->perf.wbytes_mmcq; rbytes_drv = host->perf.rbytes_drv; wbytes_drv = host->perf.wbytes_drv; - rtime_mmcq = ktime_to_us(host->perf.rtime_mmcq); - wtime_mmcq = ktime_to_us(host->perf.wtime_mmcq); rtime_drv = ktime_to_us(host->perf.rtime_drv); wtime_drv = ktime_to_us(host->perf.wtime_drv); spin_unlock(&host->lock); - return snprintf(buf, PAGE_SIZE, "Write performance at MMCQ Level:" - "%lu bytes in %lld microseconds\n" - "Read performance at MMCQ Level:" - "%lu bytes in %lld microseconds\n" - "Write performance at driver Level:" + return snprintf(buf, PAGE_SIZE, "Write performance at driver Level:" "%lu bytes in %lld microseconds\n" "Read performance at driver Level:" "%lu bytes in %lld microseconds\n", - wbytes_mmcq, wtime_mmcq, rbytes_mmcq, - rtime_mmcq, wbytes_drv, wtime_drv, + wbytes_drv, wtime_drv, rbytes_drv, rtime_drv); } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 0600ae0d3cb5..996969baca0a 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -432,12 +432,8 @@ struct mmc_host { #ifdef CONFIG_MMC_PERF_PROFILING struct { - unsigned long rbytes_mmcq; /* Rd bytes MMC queue */ - unsigned long wbytes_mmcq; /* Wr bytes MMC queue */ unsigned long rbytes_drv; /* Rd bytes MMC Host */ unsigned long wbytes_drv; /* Wr bytes MMC Host */ - ktime_t rtime_mmcq; /* Rd time MMC queue */ - ktime_t wtime_mmcq; /* Wr time MMC queue */ ktime_t rtime_drv; /* Rd time MMC Host */ ktime_t wtime_drv; /* Wr time MMC Host */ ktime_t start; From aa9dc5064e95c7f7422144d14c8cfd92bb163b37 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Sat, 4 Feb 2012 16:14:50 -0500 Subject: [PATCH 137/472] mmc: core: sdio: Ensure clocks are always enabled before host interaction Ensure clocks are always enabled before any interaction with the host controller driver. This makes sure that there is no race between host execution and the core layer turning off clocks in different context with clock gating framework. Change-Id: Ib47d0641f02c4390d26567102ee178beaf63516c Signed-off-by: Sujit Reddy Thumma Acked-by: Linus Walleij Acked-by: Per Forlin Signed-off-by: Chris Ball [sboyd: Keep non-upstreamed bits for sdio.c] Signed-off-by: Stephen Boyd --- drivers/mmc/core/sdio.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 80966a6bf3ae..434d2b7a794f 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -626,8 +626,11 @@ try_again: /* * Call the optional HC's init_card function to handle quirks. */ - if (host->ops->init_card) + if (host->ops->init_card) { + mmc_host_clk_hold(host); host->ops->init_card(host, card); + mmc_host_clk_release(host); + } /* * If the host and card support UHS-I mode request the card From 883e57df750022f9197aad5134873c50ae5af6d9 Mon Sep 17 00:00:00 2001 From: "Author: Stephen Boyd" Date: Wed, 3 Dec 2014 19:49:13 +0200 Subject: [PATCH 138/472] revert "mmc: core: Set correct bus mode before card init" This reverts the if/else part of 44669034815a7ad263542ac605c581a10b22d146. We don't know why it was done though and this should probably be dropped after testing. Change-Id: Ie453df2f62b6716c6c0aa83dd4822b8555c4e555 Signed-off-by: Stephen Boyd [merez@codeaurora.org: fix conflicts] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 26108aacf7a0..0175430a0c97 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1251,9 +1251,10 @@ void mmc_set_initial_state(struct mmc_host *host) if (mmc_host_is_spi(host)) host->ios.chip_select = MMC_CS_HIGH; - else + else { host->ios.chip_select = MMC_CS_DONTCARE; - host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; host->ios.drv_type = 0; From fd7c52accff9a54a6ce2e82f06fb50ad22d775e2 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 27 Jul 2012 18:10:19 +0530 Subject: [PATCH 139/472] mmc: block: do not query the sd card if a fault is injected When the fault injection framework introduces an error to the data block, the current code queries the SD card to find the number of blocks actually programmed. This value would be as requested by the generic block layer. So the entire request would be completed. Say, request 0 is pulled from queue and submitted. When this is being processed, request 1 is pulled from queue and prepared. Request 0 though is successful, fault-injection framework injects an error and modifies the bytes_xferred variable to a random value less than requested transfer. Request 1 is not processed and during the handling of error, the SD card is queried for the actual bytes programmed. This would be the correct value. Thus blk_end_request would complete this request and the control would return to fetch request 2. In this process, request 1 is not processed at all and the application waits indefinitely for request 1 to be processed. No further requests are issued to the queue. This patch identifies, if the fault injection-framework has inserted an error to this request and doesn't query the card and uses bytes_xferred to complete the request. Change-Id: I496802e244745bc7550402027a594d967cf7b756 Signed-off-by: Asutosh Das [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 13 ++++++++----- drivers/mmc/core/core.c | 1 + include/linux/mmc/core.h | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 4ae50932c031..de7f2dacf330 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1487,6 +1487,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, brq->stop.arg = 0; brq->data.blocks = blk_rq_sectors(req); + brq->data.fault_injected = false; /* * The block layer doesn't support all sector count * restrictions, so we need to be prepared for too big @@ -1807,6 +1808,7 @@ static void mmc_blk_packed_hdr_wrq_prep(struct mmc_queue_req *mqrq, brq->data.blksz = 512; brq->data.blocks = packed->blocks + hdr_blocks; brq->data.flags |= MMC_DATA_WRITE; + brq->data.fault_injected = false; brq->stop.opcode = MMC_STOP_TRANSMISSION; brq->stop.arg = 0; @@ -1840,11 +1842,12 @@ static int mmc_blk_cmd_err(struct mmc_blk_data *md, struct mmc_card *card, */ if (mmc_card_sd(card)) { u32 blocks; - - blocks = mmc_sd_num_wr_blocks(card); - if (blocks != (u32)-1) { - ret = blk_end_request(req, 0, blocks << 9); - } + if (!brq->data.fault_injected) { + blocks = mmc_sd_num_wr_blocks(card); + if (blocks != (u32)-1) + ret = blk_end_request(req, 0, blocks << 9); + } else + ret = blk_end_request(req, 0, brq->data.bytes_xfered); } else { if (!mmc_packed_cmd(mq_rq->cmd_type)) ret = blk_end_request(req, 0, brq->data.bytes_xfered); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0175430a0c97..f4acc56bb2c8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -112,6 +112,7 @@ static void mmc_should_fail_request(struct mmc_host *host, data->error = data_errors[prandom_u32() % ARRAY_SIZE(data_errors)]; data->bytes_xfered = (prandom_u32() % (data->bytes_xfered >> 9)) << 9; + data->fault_injected = true; } #else /* CONFIG_FAIL_MMC_REQUEST */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index c5f305249919..0c2f98ba1d84 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -93,6 +93,7 @@ struct mmc_data { int sg_count; /* mapped sg entries */ struct scatterlist *sg; /* I/O scatter list */ s32 host_cookie; /* host private data */ + bool fault_injected; /* fault injected */ }; struct mmc_host; From 86dc987a4fc46b6fdad7cae3c28993e852567e84 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Wed, 3 Dec 2014 23:38:06 +0200 Subject: [PATCH 140/472] mmc: block: Add write packing control The write packing control will ensure that read requests latency is not increased due to long write packed commands. The trigger for enabling the write packing is managing to pack several write requests. The number of potential packed requests that will trigger the packing can be configured via sysfs by writing the required value to: /sys/block//num_wr_reqs_to_start_packing. The trigger for disabling the write packing is fetching a read request. Change-Id: I22e8ab04cd9aca220678784c39306068a0996790 Signed-off-by: Maya Erez Signed-off-by: Tatyana Brokhman [merez@codeaurora.org: fixed trivial conflicts] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- Documentation/mmc/mmc-dev-attrs.txt | 17 +++++ drivers/mmc/card/block.c | 97 +++++++++++++++++++++++++++++ drivers/mmc/card/queue.c | 8 +++ drivers/mmc/card/queue.h | 3 + include/linux/mmc/host.h | 1 + 5 files changed, 126 insertions(+) diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index 189bab09255a..d143f16afe69 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -8,6 +8,23 @@ The following attributes are read/write. force_ro Enforce read-only access even if write protect switch is off. + num_wr_reqs_to_start_packing This attribute is used to determine + the trigger for activating the write packing, in case the write + packing control feature is enabled. + + When the MMC manages to reach a point where num_wr_reqs_to_start_packing + write requests could be packed, it enables the write packing feature. + This allows us to start the write packing only when it is beneficial + and has minimum affect on the read latency. + + The number of potential packed requests that will trigger the packing + can be configured via sysfs by writing the required value to: + /sys/block//num_wr_reqs_to_start_packing. + + The default value of num_wr_reqs_to_start_packing was determined by + running parallel lmdd write and lmdd read operations and calculating + the max number of packed writes requests. + SD and MMC Device Attributes ============================ diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index de7f2dacf330..141c2850f72a 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -123,6 +123,7 @@ struct mmc_blk_data { unsigned int part_curr; struct device_attribute force_ro; struct device_attribute power_ro_lock; + struct device_attribute num_wr_reqs_to_start_packing; int area_type; }; @@ -289,6 +290,38 @@ out: return ret; } +static ssize_t +num_wr_reqs_to_start_packing_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int num_wr_reqs_to_start_packing; + int ret; + + num_wr_reqs_to_start_packing = md->queue.num_wr_reqs_to_start_packing; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", num_wr_reqs_to_start_packing); + + mmc_blk_put(md); + return ret; +} + +static ssize_t +num_wr_reqs_to_start_packing_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int value; + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + + sscanf(buf, "%d", &value); + if (value >= 0) + md->queue.num_wr_reqs_to_start_packing = value; + + mmc_blk_put(md); + return count; +} + static int mmc_blk_open(struct block_device *bdev, fmode_t mode) { struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk); @@ -1632,6 +1665,47 @@ static inline u8 mmc_calc_packed_hdr_segs(struct request_queue *q, return nr_segs; } +static void mmc_blk_write_packing_control(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_host *host = mq->card->host; + int data_dir; + + if (!(host->caps2 & MMC_CAP2_PACKED_WR)) + return; + + /* + * In case the packing control is not supported by the host, it should + * not have an effect on the write packing. Therefore we have to enable + * the write packing + */ + if (!(host->caps2 & MMC_CAP2_PACKED_WR_CONTROL)) { + mq->wr_packing_enabled = true; + return; + } + + if (!req || (req && (req->cmd_flags & REQ_FLUSH))) { + if (mq->num_of_potential_packed_wr_reqs > + mq->num_wr_reqs_to_start_packing) + mq->wr_packing_enabled = true; + return; + } + + data_dir = rq_data_dir(req); + + if (data_dir == READ) { + mq->num_of_potential_packed_wr_reqs = 0; + mq->wr_packing_enabled = false; + return; + } else if (data_dir == WRITE) { + mq->num_of_potential_packed_wr_reqs++; + } + + if (mq->num_of_potential_packed_wr_reqs > + mq->num_wr_reqs_to_start_packing) + mq->wr_packing_enabled = true; +} + static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) { struct request_queue *q = mq->queue; @@ -1649,6 +1723,9 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) if (!(md->flags & MMC_BLK_PACKED_CMD)) goto no_packed; + if (!mq->wr_packing_enabled) + goto no_packed; + if ((rq_data_dir(cur) == WRITE) && mmc_host_packed_wr(card->host)) max_packed_rw = card->ext_csd.max_packed_writes; @@ -1717,6 +1794,8 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) if (phys_segments > max_phys_segs) break; + if (rq_data_dir(next) == WRITE) + mq->num_of_potential_packed_wr_reqs++; list_add_tail(&next->queuelist, &mqrq->packed->list); cur = next; reqs++; @@ -2138,6 +2217,8 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) goto out; } + mmc_blk_write_packing_control(mq, req); + mq->flags &= ~MMC_QUEUE_NEW_REQUEST; if (cmd_flags & REQ_DISCARD) { /* complete ongoing async transfer before issuing discard */ @@ -2398,6 +2479,8 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md) mmc_cleanup_queue(&md->queue); if (md->flags & MMC_BLK_PACKED_CMD) mmc_packed_clean(&md->queue); + device_remove_file(disk_to_dev(md->disk), + &md->num_wr_reqs_to_start_packing); 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) && @@ -2460,6 +2543,20 @@ static int mmc_add_disk(struct mmc_blk_data *md) if (ret) goto power_ro_lock_fail; } + + md->num_wr_reqs_to_start_packing.show = + num_wr_reqs_to_start_packing_show; + md->num_wr_reqs_to_start_packing.store = + num_wr_reqs_to_start_packing_store; + sysfs_attr_init(&md->num_wr_reqs_to_start_packing.attr); + md->num_wr_reqs_to_start_packing.attr.name = + "num_wr_reqs_to_start_packing"; + md->num_wr_reqs_to_start_packing.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(disk_to_dev(md->disk), + &md->num_wr_reqs_to_start_packing); + if (ret) + goto power_ro_lock_fail; + return ret; power_ro_lock_fail: diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 219a39de05e5..61759f5559f4 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -24,6 +24,13 @@ #define MMC_QUEUE_BOUNCESZ 65536 +/* + * Based on benchmark tests the default num of requests to trigger the write + * packing was determined, to keep the read latency as low as possible and + * manage to keep the high write throughput. + */ +#define DEFAULT_NUM_REQS_TO_START_PACK 17 + /* * Prepare a MMC request. This just filters out odd stuff. */ @@ -211,6 +218,7 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, mq->mqrq_cur = mqrq_cur; mq->mqrq_prev = mqrq_prev; mq->queue->queuedata = mq; + mq->num_wr_reqs_to_start_packing = DEFAULT_NUM_REQS_TO_START_PACK; blk_queue_prep_rq(mq->queue, mmc_prep_request); queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue); diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 98acd0323046..971287b29e57 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -58,6 +58,9 @@ struct mmc_queue { struct mmc_queue_req mqrq[2]; struct mmc_queue_req *mqrq_cur; struct mmc_queue_req *mqrq_prev; + bool wr_packing_enabled; + int num_of_potential_packed_wr_reqs; + int num_wr_reqs_to_start_packing; }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 996969baca0a..1f4c2cc72b8c 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -302,6 +302,7 @@ struct mmc_host { #define MMC_CAP2_HSX00_1_2V (MMC_CAP2_HS200_1_2V_SDR | MMC_CAP2_HS400_1_2V) #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17) #define MMC_CAP2_NO_WRITE_PROTECT (1 << 18) /* No physical write protect pin, assume that card is always read-write */ +#define MMC_CAP2_PACKED_WR_CONTROL (1 << 19) /* Allow write packing control */ #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ mmc_pm_flag_t pm_caps; /* supported pm features */ From a37b4470259d3ef6e523b296775198de64e43362 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Sun, 7 Oct 2012 10:33:13 +0200 Subject: [PATCH 141/472] mmc: block: Add MMC write packing statistics The write packing statistics are used for the packed commands unit tests in order to determine test success or failure Change-Id: I5eea513991c794543fbb3d009d8b7db0b0912fc5 Signed-off-by: Maya Erez Signed-off-by: Tatyana Brokhman [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 72 ++++++++++++++-- drivers/mmc/core/bus.c | 4 + drivers/mmc/core/debugfs.c | 172 +++++++++++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 18 ++++ include/linux/mmc/card.h | 25 ++++++ 5 files changed, 285 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 141c2850f72a..8d780ea43a13 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -70,6 +70,12 @@ MODULE_ALIAS("mmc:block"); #define PACKED_CMD_VER 0x01 #define PACKED_CMD_WR 0x02 +#define MMC_BLK_UPDATE_STOP_REASON(stats, reason) \ + do { \ + if (stats->enabled) \ + stats->pack_stop_reason[reason]++; \ + } while (0) + static DEFINE_MUTEX(block_mutex); /* @@ -1706,6 +1712,35 @@ static void mmc_blk_write_packing_control(struct mmc_queue *mq, mq->wr_packing_enabled = true; } +struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics(struct mmc_card *card) +{ + if (!card) + return NULL; + + return &card->wr_pack_stats; +} +EXPORT_SYMBOL(mmc_blk_get_packed_statistics); + +void mmc_blk_init_packed_statistics(struct mmc_card *card) +{ + int max_num_of_packed_reqs = 0; + + if (!card || !card->wr_pack_stats.packing_events) + return; + + max_num_of_packed_reqs = card->ext_csd.max_packed_writes; + + spin_lock(&card->wr_pack_stats.lock); + memset(card->wr_pack_stats.packing_events, 0, + (max_num_of_packed_reqs + 1) * + sizeof(*card->wr_pack_stats.packing_events)); + memset(&card->wr_pack_stats.pack_stop_reason, 0, + sizeof(card->wr_pack_stats.pack_stop_reason)); + card->wr_pack_stats.enabled = true; + spin_unlock(&card->wr_pack_stats.lock); +} +EXPORT_SYMBOL(mmc_blk_init_packed_statistics); + static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) { struct request_queue *q = mq->queue; @@ -1719,6 +1754,7 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) bool put_back = true; u8 max_packed_rw = 0; u8 reqs = 0; + struct mmc_wr_pack_stats *stats = &card->wr_pack_stats; if (!(md->flags & MMC_BLK_PACKED_CMD)) goto no_packed; @@ -1757,6 +1793,7 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) phys_segments += mmc_calc_packed_hdr_segs(q, card); } + spin_lock(&stats->lock); do { if (reqs >= max_packed_rw - 1) { put_back = false; @@ -1767,32 +1804,46 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) next = blk_fetch_request(q); spin_unlock_irq(q->queue_lock); if (!next) { + MMC_BLK_UPDATE_STOP_REASON(stats, EMPTY_QUEUE); put_back = false; break; } if (mmc_large_sector(card) && - !IS_ALIGNED(blk_rq_sectors(next), 8)) + !IS_ALIGNED(blk_rq_sectors(next), 8)) { + MMC_BLK_UPDATE_STOP_REASON(stats, LARGE_SEC_ALIGN); break; + } if (next->cmd_flags & REQ_DISCARD || - next->cmd_flags & REQ_FLUSH) + next->cmd_flags & REQ_FLUSH) { + MMC_BLK_UPDATE_STOP_REASON(stats, FLUSH_OR_DISCARD); break; + } - if (rq_data_dir(cur) != rq_data_dir(next)) + if (rq_data_dir(cur) != rq_data_dir(next)) { + MMC_BLK_UPDATE_STOP_REASON(stats, WRONG_DATA_DIR); break; + } if (mmc_req_rel_wr(next) && - (md->flags & MMC_BLK_REL_WR) && !en_rel_wr) + (md->flags & MMC_BLK_REL_WR) && !en_rel_wr) { + MMC_BLK_UPDATE_STOP_REASON(stats, REL_WRITE); break; + } req_sectors += blk_rq_sectors(next); - if (req_sectors > max_blk_count) + if (req_sectors > max_blk_count) { + if (stats->enabled) + stats->pack_stop_reason[EXCEEDS_SECTORS]++; break; + } phys_segments += next->nr_phys_segments; - if (phys_segments > max_phys_segs) + if (phys_segments > max_phys_segs) { + MMC_BLK_UPDATE_STOP_REASON(stats, EXCEEDS_SEGMENTS); break; + } if (rq_data_dir(next) == WRITE) mq->num_of_potential_packed_wr_reqs++; @@ -1807,6 +1858,15 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) spin_unlock_irq(q->queue_lock); } + if (stats->enabled) { + if (reqs + 1 <= card->ext_csd.max_packed_writes) + stats->packing_events[reqs + 1]++; + if (reqs + 1 == max_packed_rw) + MMC_BLK_UPDATE_STOP_REASON(stats, THRESHOLD); + } + + spin_unlock(&stats->lock); + if (reqs > 0) { list_add(&req->queuelist, &mqrq->packed->list); mqrq->packed->nr_entries = ++reqs; diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 972ff844cf5a..483342e5dbfb 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -273,6 +273,8 @@ struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type) card->dev.release = mmc_release_card; card->dev.type = type; + spin_lock_init(&card->wr_pack_stats.lock); + return card; } @@ -380,6 +382,8 @@ void mmc_remove_card(struct mmc_card *card) of_node_put(card->dev.of_node); } + kfree(card->wr_pack_stats.packing_events); + put_device(&card->dev); } diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index cbc83344e0c7..46bb040cd335 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -402,6 +402,172 @@ static const struct file_operations mmc_dbg_ext_csd_fops = { .llseek = default_llseek, }; +static int mmc_wr_pack_stats_open(struct inode *inode, struct file *filp) +{ + struct mmc_card *card = inode->i_private; + + filp->private_data = card; + card->wr_pack_stats.print_in_read = 1; + return 0; +} + +#define TEMP_BUF_SIZE 256 +static ssize_t mmc_wr_pack_stats_read(struct file *filp, char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + struct mmc_card *card = filp->private_data; + struct mmc_wr_pack_stats *pack_stats; + int i; + int max_num_of_packed_reqs = 0; + char *temp_buf; + + if (!card) + return cnt; + + if (!card->wr_pack_stats.print_in_read) + return 0; + + if (!card->wr_pack_stats.enabled) { + pr_info("%s: write packing statistics are disabled\n", + mmc_hostname(card->host)); + goto exit; + } + + pack_stats = &card->wr_pack_stats; + + if (!pack_stats->packing_events) { + pr_info("%s: NULL packing_events\n", mmc_hostname(card->host)); + goto exit; + } + + max_num_of_packed_reqs = card->ext_csd.max_packed_writes; + + temp_buf = kmalloc(TEMP_BUF_SIZE, GFP_KERNEL); + if (!temp_buf) + goto exit; + + spin_lock(&pack_stats->lock); + + snprintf(temp_buf, TEMP_BUF_SIZE, "%s: write packing statistics:\n", + mmc_hostname(card->host)); + strlcat(ubuf, temp_buf, cnt); + + for (i = 1 ; i <= max_num_of_packed_reqs ; ++i) { + if (pack_stats->packing_events[i]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: Packed %d reqs - %d times\n", + mmc_hostname(card->host), i, + pack_stats->packing_events[i]); + strlcat(ubuf, temp_buf, cnt); + } + } + + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: stopped packing due to the following reasons:\n", + mmc_hostname(card->host)); + strlcat(ubuf, temp_buf, cnt); + + if (pack_stats->pack_stop_reason[EXCEEDS_SEGMENTS]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: exceed max num of segments\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[EXCEEDS_SEGMENTS]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[EXCEEDS_SECTORS]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: exceed max num of sectors\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[EXCEEDS_SECTORS]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[WRONG_DATA_DIR]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: wrong data direction\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[WRONG_DATA_DIR]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[FLUSH_OR_DISCARD]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: flush or discard\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[FLUSH_OR_DISCARD]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[EMPTY_QUEUE]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: empty queue\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[EMPTY_QUEUE]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[REL_WRITE]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: rel write\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[REL_WRITE]); + strlcat(ubuf, temp_buf, cnt); + } + if (pack_stats->pack_stop_reason[THRESHOLD]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: Threshold\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[THRESHOLD]); + strlcat(ubuf, temp_buf, cnt); + } + + if (pack_stats->pack_stop_reason[LARGE_SEC_ALIGN]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: Large sector alignment\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[LARGE_SEC_ALIGN]); + strlcat(ubuf, temp_buf, cnt); + } + + spin_unlock(&pack_stats->lock); + + kfree(temp_buf); + + pr_info("%s", ubuf); + +exit: + if (card->wr_pack_stats.print_in_read == 1) { + card->wr_pack_stats.print_in_read = 0; + return strnlen(ubuf, cnt); + } + + return 0; +} + +static ssize_t mmc_wr_pack_stats_write(struct file *filp, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct mmc_card *card = filp->private_data; + int value; + + if (!card) + return cnt; + + sscanf(ubuf, "%d", &value); + if (value) { + mmc_blk_init_packed_statistics(card); + } else { + spin_lock(&card->wr_pack_stats.lock); + card->wr_pack_stats.enabled = false; + spin_unlock(&card->wr_pack_stats.lock); + } + + return cnt; +} + +static const struct file_operations mmc_dbg_wr_pack_stats_fops = { + .open = mmc_wr_pack_stats_open, + .read = mmc_wr_pack_stats_read, + .write = mmc_wr_pack_stats_write, +}; + void mmc_add_card_debugfs(struct mmc_card *card) { struct mmc_host *host = card->host; @@ -434,6 +600,12 @@ void mmc_add_card_debugfs(struct mmc_card *card) &mmc_dbg_ext_csd_fops)) goto err; + if (mmc_card_mmc(card) && (card->ext_csd.rev >= 6) && + (card->host->caps2 & MMC_CAP2_PACKED_WR)) + if (!debugfs_create_file("wr_pack_stats", S_IRUSR, root, card, + &mmc_dbg_wr_pack_stats_fops)) + goto err; + return; err: diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index d9b64b50ce4b..05b5dff1da4f 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1723,6 +1723,24 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } else { card->ext_csd.packed_event_en = 1; } + + } + + if (!oldcard) { + if ((host->caps2 & MMC_CAP2_PACKED_CMD) && + (card->ext_csd.max_packed_writes > 0)) { + /* + * We would like to keep the statistics in an index + * that equals the num of packed requests + * (1 to max_packed_writes) + */ + card->wr_pack_stats.packing_events = kzalloc( + (card->ext_csd.max_packed_writes + 1) * + sizeof(*card->wr_pack_stats.packing_events), + GFP_KERNEL); + if (!card->wr_pack_stats.packing_events) + goto free_card; + } } return 0; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index d89132c1b067..8c70e624325b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -218,6 +218,26 @@ enum mmc_blk_status { MMC_BLK_NEW_REQUEST, }; +enum mmc_packed_stop_reasons { + EXCEEDS_SEGMENTS = 0, + EXCEEDS_SECTORS, + WRONG_DATA_DIR, + FLUSH_OR_DISCARD, + EMPTY_QUEUE, + REL_WRITE, + THRESHOLD, + LARGE_SEC_ALIGN, + MAX_REASONS, +}; + +struct mmc_wr_pack_stats { + u32 *packing_events; + u32 pack_stop_reason[MAX_REASONS]; + spinlock_t lock; + bool enabled; + bool print_in_read; +}; + /* The number of MMC physical partitions. These consist of: * boot partitions (2), general purpose partitions (4) and * RPMB partition (1) in MMC v4.4. @@ -316,6 +336,8 @@ struct mmc_card { struct dentry *debugfs_root; struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ unsigned int nr_parts; + + struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ }; /* @@ -534,5 +556,8 @@ extern void mmc_unregister_driver(struct mmc_driver *); extern void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table); +extern struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics( + struct mmc_card *card); +extern void mmc_blk_init_packed_statistics(struct mmc_card *card); #endif /* LINUX_MMC_CARD_H */ From 8a0840d289f1646ac3e359449ed624ab9bb95f97 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Sun, 7 Oct 2012 10:26:27 +0200 Subject: [PATCH 142/472] mmc: card: Fix packing control enabling algorithm When hitting a stop potential packing event, we should zero num_of_potential_packed_wr_reqs, so that the packing won't be enabled too early. Change-Id: I91d36765c4a50be0e2805cd07c58962fb87153c6 Signed-off-by: Maya Erez Signed-off-by: Tatyana Brokhman --- drivers/mmc/card/block.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 8d780ea43a13..feb05434c54e 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1694,6 +1694,7 @@ static void mmc_blk_write_packing_control(struct mmc_queue *mq, if (mq->num_of_potential_packed_wr_reqs > mq->num_wr_reqs_to_start_packing) mq->wr_packing_enabled = true; + mq->num_of_potential_packed_wr_reqs = 0; return; } From fececf257dc383a849cd53ecdaf4e0f1b0bc0a19 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Tue, 9 Oct 2012 13:50:56 +0200 Subject: [PATCH 143/472] mmc: card: Add eMMC4.5 write packed commands unit-tests Expose the following packed commands tests: - Test the write packed commands list preparation - Simulate a returned error code - Send an invalid packed command to the card Change-Id: I23a15f94571da3ff9553a342dc37e1a8de6827c6 Signed-off-by: Lee Susman Signed-off-by: Maya Erez Signed-off-by: Tatyana Brokhman --- drivers/mmc/card/Kconfig | 10 - drivers/mmc/card/Makefile | 1 + drivers/mmc/card/block.c | 71 +- drivers/mmc/card/mmc_block_test.c | 1532 +++++++++++++++++++++++++++++ drivers/mmc/card/queue.h | 4 + 5 files changed, 1607 insertions(+), 11 deletions(-) create mode 100644 drivers/mmc/card/mmc_block_test.c diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index b4da3db24103..5562308699bc 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -68,13 +68,3 @@ config MMC_TEST This driver is only of interest to those developing or testing a host driver. Most people should say N here. - -config MMC_BLOCK_TEST - tristate "MMC block test" - depends on MMC_BLOCK && IOSCHED_TEST - help - MMC block test can be used with test iosched to test the MMC block - device. - Currently used to test eMMC 4.5 features (packed commands, sanitize, - BKOPs). - diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index c73b406a06cd..d55107fb4551 100644 --- a/drivers/mmc/card/Makefile +++ b/drivers/mmc/card/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST) += mmc_test.o obj-$(CONFIG_SDIO_UART) += sdio_uart.o +obj-$(CONFIG_MMC_BLOCK_TEST) += mmc_block_test.o diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index feb05434c54e..8345571a8b1e 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1742,6 +1742,64 @@ void mmc_blk_init_packed_statistics(struct mmc_card *card) } EXPORT_SYMBOL(mmc_blk_init_packed_statistics); +void print_mmc_packing_stats(struct mmc_card *card) +{ + int i; + int max_num_of_packed_reqs = 0; + + if ((!card) || (!card->wr_pack_stats.packing_events)) + return; + + max_num_of_packed_reqs = card->ext_csd.max_packed_writes; + + spin_lock(&card->wr_pack_stats.lock); + + pr_info("%s: write packing statistics:\n", + mmc_hostname(card->host)); + + for (i = 1 ; i <= max_num_of_packed_reqs ; ++i) { + if (card->wr_pack_stats.packing_events[i] != 0) + pr_info("%s: Packed %d reqs - %d times\n", + mmc_hostname(card->host), i, + card->wr_pack_stats.packing_events[i]); + } + + pr_info("%s: stopped packing due to the following reasons:\n", + mmc_hostname(card->host)); + + if (card->wr_pack_stats.pack_stop_reason[EXCEEDS_SEGMENTS]) + pr_info("%s: %d times: exceedmax num of segments\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[EXCEEDS_SEGMENTS]); + if (card->wr_pack_stats.pack_stop_reason[EXCEEDS_SECTORS]) + pr_info("%s: %d times: exceeding the max num of sectors\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[EXCEEDS_SECTORS]); + if (card->wr_pack_stats.pack_stop_reason[WRONG_DATA_DIR]) + pr_info("%s: %d times: wrong data direction\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[WRONG_DATA_DIR]); + if (card->wr_pack_stats.pack_stop_reason[FLUSH_OR_DISCARD]) + pr_info("%s: %d times: flush or discard\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[FLUSH_OR_DISCARD]); + if (card->wr_pack_stats.pack_stop_reason[EMPTY_QUEUE]) + pr_info("%s: %d times: empty queue\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[EMPTY_QUEUE]); + if (card->wr_pack_stats.pack_stop_reason[REL_WRITE]) + pr_info("%s: %d times: rel write\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[REL_WRITE]); + if (card->wr_pack_stats.pack_stop_reason[THRESHOLD]) + pr_info("%s: %d times: Threshold\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[THRESHOLD]); + + spin_unlock(&card->wr_pack_stats.lock); +} +EXPORT_SYMBOL(print_mmc_packing_stats); + static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) { struct request_queue *q = mq->queue; @@ -1960,7 +2018,18 @@ static void mmc_blk_packed_hdr_wrq_prep(struct mmc_queue_req *mqrq, brq->data.sg_len = mmc_queue_map_sg(mq, mqrq); mqrq->mmc_active.mrq = &brq->mrq; - mqrq->mmc_active.err_check = mmc_blk_packed_err_check; + + /* + * This is intended for packed commands tests usage - in case these + * functions are not in use the respective pointers are NULL + */ + if (mq->err_check_fn) + mqrq->mmc_active.err_check = mq->err_check_fn; + else + mqrq->mmc_active.err_check = mmc_blk_packed_err_check; + + if (mq->packed_test_fn) + mq->packed_test_fn(mq->queue, mqrq); mmc_queue_bounce_pre(mqrq); } diff --git a/drivers/mmc/card/mmc_block_test.c b/drivers/mmc/card/mmc_block_test.c new file mode 100644 index 000000000000..a083efe1e09f --- /dev/null +++ b/drivers/mmc/card/mmc_block_test.c @@ -0,0 +1,1532 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* MMC block test */ + +#include +#include +#include +#include +#include +#include +#include +#include "queue.h" + +#define MODULE_NAME "mmc_block_test" +#define TEST_MAX_SECTOR_RANGE (600*1024*1024) /* 600 MB */ +#define TEST_MAX_BIOS_PER_REQ 120 +#define CMD23_PACKED_BIT (1 << 30) +#define LARGE_PRIME_1 1103515367 +#define LARGE_PRIME_2 35757 +#define PACKED_HDR_VER_MASK 0x000000FF +#define PACKED_HDR_RW_MASK 0x0000FF00 +#define PACKED_HDR_NUM_REQS_MASK 0x00FF0000 +#define PACKED_HDR_BITS_16_TO_29_SET 0x3FFF0000 + +#define test_pr_debug(fmt, args...) pr_debug("%s: "fmt"\n", MODULE_NAME, args) +#define test_pr_info(fmt, args...) pr_info("%s: "fmt"\n", MODULE_NAME, args) +#define test_pr_err(fmt, args...) pr_err("%s: "fmt"\n", MODULE_NAME, args) + +enum is_random { + NON_RANDOM_TEST, + RANDOM_TEST, +}; + +enum mmc_block_test_testcases { + /* Start of send write packing test group */ + SEND_WRITE_PACKING_MIN_TESTCASE, + TEST_STOP_DUE_TO_READ = SEND_WRITE_PACKING_MIN_TESTCASE, + TEST_STOP_DUE_TO_READ_AFTER_MAX_REQS, + TEST_STOP_DUE_TO_FLUSH, + TEST_STOP_DUE_TO_FLUSH_AFTER_MAX_REQS, + TEST_STOP_DUE_TO_EMPTY_QUEUE, + TEST_STOP_DUE_TO_MAX_REQ_NUM, + TEST_STOP_DUE_TO_THRESHOLD, + SEND_WRITE_PACKING_MAX_TESTCASE = TEST_STOP_DUE_TO_THRESHOLD, + + /* Start of err check test group */ + ERR_CHECK_MIN_TESTCASE, + TEST_RET_ABORT = ERR_CHECK_MIN_TESTCASE, + TEST_RET_PARTIAL_FOLLOWED_BY_SUCCESS, + TEST_RET_PARTIAL_FOLLOWED_BY_ABORT, + TEST_RET_PARTIAL_MULTIPLE_UNTIL_SUCCESS, + TEST_RET_PARTIAL_MAX_FAIL_IDX, + TEST_RET_RETRY, + TEST_RET_CMD_ERR, + TEST_RET_DATA_ERR, + ERR_CHECK_MAX_TESTCASE = TEST_RET_DATA_ERR, + + /* Start of send invalid test group */ + INVALID_CMD_MIN_TESTCASE, + TEST_HDR_INVALID_VERSION = INVALID_CMD_MIN_TESTCASE, + TEST_HDR_WRONG_WRITE_CODE, + TEST_HDR_INVALID_RW_CODE, + TEST_HDR_DIFFERENT_ADDRESSES, + TEST_HDR_REQ_NUM_SMALLER_THAN_ACTUAL, + TEST_HDR_REQ_NUM_LARGER_THAN_ACTUAL, + TEST_HDR_CMD23_PACKED_BIT_SET, + TEST_CMD23_MAX_PACKED_WRITES, + TEST_CMD23_ZERO_PACKED_WRITES, + TEST_CMD23_PACKED_BIT_UNSET, + TEST_CMD23_REL_WR_BIT_SET, + TEST_CMD23_BITS_16TO29_SET, + TEST_CMD23_HDR_BLK_NOT_IN_COUNT, + INVALID_CMD_MAX_TESTCASE = TEST_CMD23_HDR_BLK_NOT_IN_COUNT, +}; + +enum mmc_block_test_group { + TEST_NO_GROUP, + TEST_GENERAL_GROUP, + TEST_SEND_WRITE_PACKING_GROUP, + TEST_ERR_CHECK_GROUP, + TEST_SEND_INVALID_GROUP, +}; + +struct mmc_block_test_debug { + struct dentry *send_write_packing_test; + struct dentry *err_check_test; + struct dentry *send_invalid_packed_test; + struct dentry *random_test_seed; +}; + +struct mmc_block_test_data { + /* The number of write requests that the test will issue */ + int num_requests; + /* The expected write packing statistics for the current test */ + struct mmc_wr_pack_stats exp_packed_stats; + /* + * A user-defined seed for random choices of number of bios written in + * a request, and of number of requests issued in a test + * This field is randomly updated after each use + */ + unsigned int random_test_seed; + /* A retry counter used in err_check tests */ + int err_check_counter; + /* Can be one of the values of enum test_group */ + enum mmc_block_test_group test_group; + /* + * Indicates if the current testcase is running with random values of + * num_requests and num_bios (in each request) + */ + int is_random; + /* Data structure for debugfs dentrys */ + struct mmc_block_test_debug debug; + /* + * Data structure containing individual test information, including + * self-defined specific data + */ + struct test_info test_info; + /* mmc block device test */ + struct blk_dev_test_type bdt; +}; + +static struct mmc_block_test_data *mbtd; + +/* + * A callback assigned to the packed_test_fn field. + * Called from block layer in mmc_blk_packed_hdr_wrq_prep. + * Here we alter the packed header or CMD23 in order to send an invalid + * packed command to the card. + */ +static void test_invalid_packed_cmd(struct request_queue *q, + struct mmc_queue_req *mqrq) +{ + struct mmc_queue *mq = q->queuedata; + u32 *packed_cmd_hdr = mqrq->packed->cmd_hdr; + struct request *req = mqrq->req; + struct request *second_rq; + struct test_request *test_rq; + struct mmc_blk_request *brq = &mqrq->brq; + int num_requests; + int max_packed_reqs; + + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return; + } + + test_rq = (struct test_request *)req->elv.priv[0]; + if (!test_rq) { + test_pr_err("%s: NULL test_rq", __func__); + return; + } + max_packed_reqs = mq->card->ext_csd.max_packed_writes; + + switch (mbtd->test_info.testcase) { + case TEST_HDR_INVALID_VERSION: + test_pr_info("%s: set invalid header version", __func__); + /* Put 0 in header version field (1 byte, offset 0 in header) */ + packed_cmd_hdr[0] = packed_cmd_hdr[0] & ~PACKED_HDR_VER_MASK; + break; + case TEST_HDR_WRONG_WRITE_CODE: + test_pr_info("%s: wrong write code", __func__); + /* Set R/W field with R value (1 byte, offset 1 in header) */ + packed_cmd_hdr[0] = packed_cmd_hdr[0] & ~PACKED_HDR_RW_MASK; + packed_cmd_hdr[0] = packed_cmd_hdr[0] | 0x00000100; + break; + case TEST_HDR_INVALID_RW_CODE: + test_pr_info("%s: invalid r/w code", __func__); + /* Set R/W field with invalid value */ + packed_cmd_hdr[0] = packed_cmd_hdr[0] & ~PACKED_HDR_RW_MASK; + packed_cmd_hdr[0] = packed_cmd_hdr[0] | 0x00000400; + break; + case TEST_HDR_DIFFERENT_ADDRESSES: + test_pr_info("%s: different addresses", __func__); + second_rq = list_entry(req->queuelist.next, struct request, + queuelist); + test_pr_info("%s: test_rq->sector=%ld, second_rq->sector=%ld", + __func__, (long)req->__sector, + (long)second_rq->__sector); + /* + * Put start sector of second write request in the first write + * request's cmd25 argument in the packed header + */ + packed_cmd_hdr[3] = second_rq->__sector; + break; + case TEST_HDR_REQ_NUM_SMALLER_THAN_ACTUAL: + test_pr_info("%s: request num smaller than actual" , __func__); + num_requests = (packed_cmd_hdr[0] & PACKED_HDR_NUM_REQS_MASK) + >> 16; + /* num of entries is decremented by 1 */ + num_requests = (num_requests - 1) << 16; + /* + * Set number of requests field in packed write header to be + * smaller than the actual number (1 byte, offset 2 in header) + */ + packed_cmd_hdr[0] = (packed_cmd_hdr[0] & + ~PACKED_HDR_NUM_REQS_MASK) + num_requests; + break; + case TEST_HDR_REQ_NUM_LARGER_THAN_ACTUAL: + test_pr_info("%s: request num larger than actual" , __func__); + num_requests = (packed_cmd_hdr[0] & PACKED_HDR_NUM_REQS_MASK) + >> 16; + /* num of entries is incremented by 1 */ + num_requests = (num_requests + 1) << 16; + /* + * Set number of requests field in packed write header to be + * larger than the actual number (1 byte, offset 2 in header). + */ + packed_cmd_hdr[0] = (packed_cmd_hdr[0] & + ~PACKED_HDR_NUM_REQS_MASK) + num_requests; + break; + case TEST_HDR_CMD23_PACKED_BIT_SET: + test_pr_info("%s: header CMD23 packed bit set" , __func__); + /* + * Set packed bit (bit 30) in cmd23 argument of first and second + * write requests in packed write header. + * These are located at bytes 2 and 4 in packed write header + */ + packed_cmd_hdr[2] = packed_cmd_hdr[2] | CMD23_PACKED_BIT; + packed_cmd_hdr[4] = packed_cmd_hdr[4] | CMD23_PACKED_BIT; + break; + case TEST_CMD23_MAX_PACKED_WRITES: + test_pr_info("%s: CMD23 request num > max_packed_reqs", + __func__); + /* + * Set the individual packed cmd23 request num to + * max_packed_reqs + 1 + */ + brq->sbc.arg = MMC_CMD23_ARG_PACKED | (max_packed_reqs + 1); + break; + case TEST_CMD23_ZERO_PACKED_WRITES: + test_pr_info("%s: CMD23 request num = 0", __func__); + /* Set the individual packed cmd23 request num to zero */ + brq->sbc.arg = MMC_CMD23_ARG_PACKED; + break; + case TEST_CMD23_PACKED_BIT_UNSET: + test_pr_info("%s: CMD23 packed bit unset", __func__); + /* + * Set the individual packed cmd23 packed bit to 0, + * although there is a packed write request + */ + brq->sbc.arg &= ~CMD23_PACKED_BIT; + break; + case TEST_CMD23_REL_WR_BIT_SET: + test_pr_info("%s: CMD23 REL WR bit set", __func__); + /* Set the individual packed cmd23 reliable write bit */ + brq->sbc.arg = MMC_CMD23_ARG_PACKED | MMC_CMD23_ARG_REL_WR; + break; + case TEST_CMD23_BITS_16TO29_SET: + test_pr_info("%s: CMD23 bits [16-29] set", __func__); + brq->sbc.arg = MMC_CMD23_ARG_PACKED | + PACKED_HDR_BITS_16_TO_29_SET; + break; + case TEST_CMD23_HDR_BLK_NOT_IN_COUNT: + test_pr_info("%s: CMD23 hdr not in block count", __func__); + brq->sbc.arg = MMC_CMD23_ARG_PACKED | + ((rq_data_dir(req) == READ) ? 0 : mqrq->packed->blocks); + break; + default: + test_pr_err("%s: unexpected testcase %d", + __func__, mbtd->test_info.testcase); + break; + } +} + +/* + * A callback assigned to the err_check_fn field of the mmc_request by the + * MMC/card/block layer. + * Called upon request completion by the MMC/core layer. + * Here we emulate an error return value from the card. + */ +static int test_err_check(struct mmc_card *card, struct mmc_async_req *areq) +{ + struct mmc_queue_req *mq_rq = container_of(areq, struct mmc_queue_req, + mmc_active); + struct request_queue *req_q = test_iosched_get_req_queue(); + struct mmc_queue *mq; + int max_packed_reqs; + int ret = 0; + + if (req_q) + mq = req_q->queuedata; + else { + test_pr_err("%s: NULL request_queue", __func__); + return 0; + } + + if (!mq) { + test_pr_err("%s: %s: NULL mq", __func__, + mmc_hostname(card->host)); + return 0; + } + + max_packed_reqs = mq->card->ext_csd.max_packed_writes; + + if (!mq_rq) { + test_pr_err("%s: %s: NULL mq_rq", __func__, + mmc_hostname(card->host)); + return 0; + } + + switch (mbtd->test_info.testcase) { + case TEST_RET_ABORT: + test_pr_info("%s: return abort", __func__); + ret = MMC_BLK_ABORT; + break; + case TEST_RET_PARTIAL_FOLLOWED_BY_SUCCESS: + test_pr_info("%s: return partial followed by success", + __func__); + /* + * Since in this testcase num_requests is always >= 2, + * we can be sure that packed_fail_idx is always >= 1 + */ + mq_rq->packed->idx_failure = (mbtd->num_requests / 2); + test_pr_info("%s: packed_fail_idx = %d" + , __func__, mq_rq->packed->idx_failure); + mq->err_check_fn = NULL; + ret = MMC_BLK_PARTIAL; + break; + case TEST_RET_PARTIAL_FOLLOWED_BY_ABORT: + if (!mbtd->err_check_counter) { + test_pr_info("%s: return partial followed by abort", + __func__); + mbtd->err_check_counter++; + /* + * Since in this testcase num_requests is always >= 3, + * we have that packed_fail_idx is always >= 1 + */ + mq_rq->packed->idx_failure = (mbtd->num_requests / 2); + test_pr_info("%s: packed_fail_idx = %d" + , __func__, mq_rq->packed->idx_failure); + ret = MMC_BLK_PARTIAL; + break; + } + mbtd->err_check_counter = 0; + mq->err_check_fn = NULL; + ret = MMC_BLK_ABORT; + break; + case TEST_RET_PARTIAL_MULTIPLE_UNTIL_SUCCESS: + test_pr_info("%s: return partial multiple until success", + __func__); + if (++mbtd->err_check_counter >= (mbtd->num_requests)) { + mq->err_check_fn = NULL; + mbtd->err_check_counter = 0; + ret = MMC_BLK_PARTIAL; + break; + } + mq_rq->packed->idx_failure = 1; + ret = MMC_BLK_PARTIAL; + break; + case TEST_RET_PARTIAL_MAX_FAIL_IDX: + test_pr_info("%s: return partial max fail_idx", __func__); + mq_rq->packed->idx_failure = max_packed_reqs - 1; + mq->err_check_fn = NULL; + ret = MMC_BLK_PARTIAL; + break; + case TEST_RET_RETRY: + test_pr_info("%s: return retry", __func__); + ret = MMC_BLK_RETRY; + break; + case TEST_RET_CMD_ERR: + test_pr_info("%s: return cmd err", __func__); + ret = MMC_BLK_CMD_ERR; + break; + case TEST_RET_DATA_ERR: + test_pr_info("%s: return data err", __func__); + ret = MMC_BLK_DATA_ERR; + break; + default: + test_pr_err("%s: unexpected testcase %d", + __func__, mbtd->test_info.testcase); + } + + return ret; +} + +/* + * This is a specific implementation for the get_test_case_str_fn function + * pointer in the test_info data structure. Given a valid test_data instance, + * the function returns a string resembling the test name, based on the testcase + */ +static char *get_test_case_str(struct test_data *td) +{ + if (!td) { + test_pr_err("%s: NULL td", __func__); + return NULL; + } + + switch (td->test_info.testcase) { + case TEST_STOP_DUE_TO_FLUSH: + return "Test stop due to flush"; + case TEST_STOP_DUE_TO_FLUSH_AFTER_MAX_REQS: + return "Test stop due to flush after max-1 reqs"; + case TEST_STOP_DUE_TO_READ: + return "Test stop due to read"; + case TEST_STOP_DUE_TO_READ_AFTER_MAX_REQS: + return "Test stop due to read after max-1 reqs"; + case TEST_STOP_DUE_TO_EMPTY_QUEUE: + return "Test stop due to empty queue"; + case TEST_STOP_DUE_TO_MAX_REQ_NUM: + return "Test stop due to max req num"; + case TEST_STOP_DUE_TO_THRESHOLD: + return "Test stop due to exceeding threshold"; + case TEST_RET_ABORT: + return "Test err_check return abort"; + case TEST_RET_PARTIAL_FOLLOWED_BY_SUCCESS: + return "Test err_check return partial followed by success"; + case TEST_RET_PARTIAL_FOLLOWED_BY_ABORT: + return "Test err_check return partial followed by abort"; + case TEST_RET_PARTIAL_MULTIPLE_UNTIL_SUCCESS: + return "Test err_check return partial multiple until success"; + case TEST_RET_PARTIAL_MAX_FAIL_IDX: + return "Test err_check return partial max fail index"; + case TEST_RET_RETRY: + return "Test err_check return retry"; + case TEST_RET_CMD_ERR: + return "Test err_check return cmd error"; + case TEST_RET_DATA_ERR: + return "Test err_check return data error"; + case TEST_HDR_INVALID_VERSION: + return "Test invalid - wrong header version"; + case TEST_HDR_WRONG_WRITE_CODE: + return "Test invalid - wrong write code"; + case TEST_HDR_INVALID_RW_CODE: + return "Test invalid - wrong R/W code"; + case TEST_HDR_DIFFERENT_ADDRESSES: + return "Test invalid - header different addresses"; + case TEST_HDR_REQ_NUM_SMALLER_THAN_ACTUAL: + return "Test invalid - header req num smaller than actual"; + case TEST_HDR_REQ_NUM_LARGER_THAN_ACTUAL: + return "Test invalid - header req num larger than actual"; + case TEST_HDR_CMD23_PACKED_BIT_SET: + return "Test invalid - header cmd23 packed bit set"; + case TEST_CMD23_MAX_PACKED_WRITES: + return "Test invalid - cmd23 max packed writes"; + case TEST_CMD23_ZERO_PACKED_WRITES: + return "Test invalid - cmd23 zero packed writes"; + case TEST_CMD23_PACKED_BIT_UNSET: + return "Test invalid - cmd23 packed bit unset"; + case TEST_CMD23_REL_WR_BIT_SET: + return "Test invalid - cmd23 rel wr bit set"; + case TEST_CMD23_BITS_16TO29_SET: + return "Test invalid - cmd23 bits [16-29] set"; + case TEST_CMD23_HDR_BLK_NOT_IN_COUNT: + return "Test invalid - cmd23 header block not in count"; + default: + return "Unknown testcase"; + } + + return NULL; +} + +/* + * Compare individual testcase's statistics to the expected statistics: + * Compare stop reason and number of packing events + */ +static int check_wr_packing_statistics(struct test_data *td) +{ + struct mmc_wr_pack_stats *mmc_packed_stats; + struct mmc_queue *mq = td->req_q->queuedata; + int max_packed_reqs = mq->card->ext_csd.max_packed_writes; + int i; + struct mmc_card *card = mq->card; + struct mmc_wr_pack_stats expected_stats; + int *stop_reason; + int ret = 0; + + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + expected_stats = mbtd->exp_packed_stats; + + mmc_packed_stats = mmc_blk_get_packed_statistics(card); + if (!mmc_packed_stats) { + test_pr_err("%s: NULL mmc_packed_stats", __func__); + return -EINVAL; + } + + if (!mmc_packed_stats->packing_events) { + test_pr_err("%s: NULL packing_events", __func__); + return -EINVAL; + } + + spin_lock(&mmc_packed_stats->lock); + + if (!mmc_packed_stats->enabled) { + test_pr_err("%s write packing statistics are not enabled", + __func__); + ret = -EINVAL; + goto exit_err; + } + + stop_reason = mmc_packed_stats->pack_stop_reason; + + for (i = 1 ; i <= max_packed_reqs ; ++i) { + if (mmc_packed_stats->packing_events[i] != + expected_stats.packing_events[i]) { + test_pr_err( + "%s: Wrong pack stats in index %d, got %d, expected %d", + __func__, i, mmc_packed_stats->packing_events[i], + expected_stats.packing_events[i]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + } + + if (mmc_packed_stats->pack_stop_reason[EXCEEDS_SEGMENTS] != + expected_stats.pack_stop_reason[EXCEEDS_SEGMENTS]) { + test_pr_err( + "%s: Wrong pack stop reason EXCEEDS_SEGMENTS %d, expected %d", + __func__, stop_reason[EXCEEDS_SEGMENTS], + expected_stats.pack_stop_reason[EXCEEDS_SEGMENTS]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + + if (mmc_packed_stats->pack_stop_reason[EXCEEDS_SECTORS] != + expected_stats.pack_stop_reason[EXCEEDS_SECTORS]) { + test_pr_err( + "%s: Wrong pack stop reason EXCEEDS_SECTORS %d, expected %d", + __func__, stop_reason[EXCEEDS_SECTORS], + expected_stats.pack_stop_reason[EXCEEDS_SECTORS]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + + if (mmc_packed_stats->pack_stop_reason[WRONG_DATA_DIR] != + expected_stats.pack_stop_reason[WRONG_DATA_DIR]) { + test_pr_err( + "%s: Wrong pack stop reason WRONG_DATA_DIR %d, expected %d", + __func__, stop_reason[WRONG_DATA_DIR], + expected_stats.pack_stop_reason[WRONG_DATA_DIR]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + + if (mmc_packed_stats->pack_stop_reason[FLUSH_OR_DISCARD] != + expected_stats.pack_stop_reason[FLUSH_OR_DISCARD]) { + test_pr_err( + "%s: Wrong pack stop reason FLUSH_OR_DISCARD %d, expected %d", + __func__, stop_reason[FLUSH_OR_DISCARD], + expected_stats.pack_stop_reason[FLUSH_OR_DISCARD]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + + if (mmc_packed_stats->pack_stop_reason[EMPTY_QUEUE] != + expected_stats.pack_stop_reason[EMPTY_QUEUE]) { + test_pr_err( + "%s: Wrong pack stop reason EMPTY_QUEUE %d, expected %d", + __func__, stop_reason[EMPTY_QUEUE], + expected_stats.pack_stop_reason[EMPTY_QUEUE]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + + if (mmc_packed_stats->pack_stop_reason[REL_WRITE] != + expected_stats.pack_stop_reason[REL_WRITE]) { + test_pr_err( + "%s: Wrong pack stop reason REL_WRITE %d, expected %d", + __func__, stop_reason[REL_WRITE], + expected_stats.pack_stop_reason[REL_WRITE]); + if (td->fs_wr_reqs_during_test) + goto cancel_round; + ret = -EINVAL; + goto exit_err; + } + +exit_err: + spin_unlock(&mmc_packed_stats->lock); + if (ret && mmc_packed_stats->enabled) + print_mmc_packing_stats(card); + return ret; +cancel_round: + spin_unlock(&mmc_packed_stats->lock); + test_iosched_set_ignore_round(true); + return 0; +} + +/* + * Pseudo-randomly choose a seed based on the last seed, and update it in + * seed_number. then return seed_number (mod max_val), or min_val. + */ +static unsigned int pseudo_random_seed(unsigned int *seed_number, + unsigned int min_val, + unsigned int max_val) +{ + int ret = 0; + + if (!seed_number) + return 0; + + *seed_number = ((unsigned int)(((unsigned long)*seed_number * + (unsigned long)LARGE_PRIME_1) + LARGE_PRIME_2)); + ret = (unsigned int)((*seed_number) % max_val); + + return (ret > min_val ? ret : min_val); +} + +/* + * Given a pseudo-random seed, find a pseudo-random num_of_bios. + * Make sure that num_of_bios is not larger than TEST_MAX_SECTOR_RANGE + */ +static void pseudo_rnd_num_of_bios(unsigned int *num_bios_seed, + unsigned int *num_of_bios) +{ + do { + *num_of_bios = pseudo_random_seed(num_bios_seed, 1, + TEST_MAX_BIOS_PER_REQ); + if (!(*num_of_bios)) + *num_of_bios = 1; + } while ((*num_of_bios) * BIO_U32_SIZE * 4 > TEST_MAX_SECTOR_RANGE); +} + +/* Add a single read request to the given td's request queue */ +static int prepare_request_add_read(struct test_data *td) +{ + int ret; + int start_sec; + + if (td) + start_sec = td->start_sector; + else { + test_pr_err("%s: NULL td", __func__); + return 0; + } + + test_pr_info("%s: Adding a read request, first req_id=%d", __func__, + td->wr_rd_next_req_id); + + ret = test_iosched_add_wr_rd_test_req(0, READ, start_sec, 2, + TEST_PATTERN_5A, NULL); + if (ret) { + test_pr_err("%s: failed to add a read request", __func__); + return ret; + } + + return 0; +} + +/* Add a single flush request to the given td's request queue */ +static int prepare_request_add_flush(struct test_data *td) +{ + int ret; + + if (!td) { + test_pr_err("%s: NULL td", __func__); + return 0; + } + + test_pr_info("%s: Adding a flush request, first req_id=%d", __func__, + td->unique_next_req_id); + ret = test_iosched_add_unique_test_req(0, REQ_UNIQUE_FLUSH, + 0, 0, NULL); + if (ret) { + test_pr_err("%s: failed to add a flush request", __func__); + return ret; + } + + return ret; +} + +/* + * Add num_requets amount of write requests to the given td's request queue. + * If random test mode is chosen we pseudo-randomly choose the number of bios + * for each write request, otherwise add between 1 to 5 bio per request. + */ +static int prepare_request_add_write_reqs(struct test_data *td, + int num_requests, int is_err_expected, + int is_random) +{ + int i; + unsigned int start_sec; + int num_bios; + int ret = 0; + unsigned int *bio_seed = &mbtd->random_test_seed; + + if (td) + start_sec = td->start_sector; + else { + test_pr_err("%s: NULL td", __func__); + return ret; + } + + test_pr_info("%s: Adding %d write requests, first req_id=%d", __func__, + num_requests, td->wr_rd_next_req_id); + + for (i = 1 ; i <= num_requests ; i++) { + start_sec = td->start_sector + 4096 * td->num_of_write_bios; + if (is_random) + pseudo_rnd_num_of_bios(bio_seed, &num_bios); + else + /* + * For the non-random case, give num_bios a value + * between 1 and 5, to keep a small number of BIOs + */ + num_bios = (i%5)+1; + + ret = test_iosched_add_wr_rd_test_req(is_err_expected, WRITE, + start_sec, num_bios, TEST_PATTERN_5A, NULL); + + if (ret) { + test_pr_err("%s: failed to add a write request", + __func__); + return ret; + } + } + return 0; +} + +/* + * Prepare the write, read and flush requests for a generic packed commands + * testcase + */ +static int prepare_packed_requests(struct test_data *td, int is_err_expected, + int num_requests, int is_random) +{ + int ret = 0; + struct mmc_queue *mq; + int max_packed_reqs; + struct request_queue *req_q; + + if (!td) { + pr_err("%s: NULL td", __func__); + return -EINVAL; + } + + req_q = td->req_q; + + if (!req_q) { + pr_err("%s: NULL request queue", __func__); + return -EINVAL; + } + + mq = req_q->queuedata; + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + max_packed_reqs = mq->card->ext_csd.max_packed_writes; + + if (mbtd->random_test_seed <= 0) { + mbtd->random_test_seed = + (unsigned int)(get_jiffies_64() & 0xFFFF); + test_pr_info("%s: got seed from jiffies %d", + __func__, mbtd->random_test_seed); + } + + mmc_blk_init_packed_statistics(mq->card); + + ret = prepare_request_add_write_reqs(td, num_requests, is_err_expected, + is_random); + if (ret) + return ret; + + /* Avoid memory corruption in upcoming stats set */ + if (td->test_info.testcase == TEST_STOP_DUE_TO_THRESHOLD) + num_requests--; + + memset((void *)mbtd->exp_packed_stats.pack_stop_reason, 0, + sizeof(mbtd->exp_packed_stats.pack_stop_reason)); + memset(mbtd->exp_packed_stats.packing_events, 0, + (max_packed_reqs + 1) * sizeof(u32)); + if (num_requests <= max_packed_reqs) + mbtd->exp_packed_stats.packing_events[num_requests] = 1; + + switch (td->test_info.testcase) { + case TEST_STOP_DUE_TO_FLUSH: + case TEST_STOP_DUE_TO_FLUSH_AFTER_MAX_REQS: + ret = prepare_request_add_flush(td); + if (ret) + return ret; + + mbtd->exp_packed_stats.pack_stop_reason[FLUSH_OR_DISCARD] = 1; + break; + case TEST_STOP_DUE_TO_READ: + case TEST_STOP_DUE_TO_READ_AFTER_MAX_REQS: + ret = prepare_request_add_read(td); + if (ret) + return ret; + + mbtd->exp_packed_stats.pack_stop_reason[WRONG_DATA_DIR] = 1; + break; + case TEST_STOP_DUE_TO_THRESHOLD: + mbtd->exp_packed_stats.packing_events[num_requests] = 1; + mbtd->exp_packed_stats.packing_events[1] = 1; + mbtd->exp_packed_stats.pack_stop_reason[THRESHOLD] = 1; + mbtd->exp_packed_stats.pack_stop_reason[EMPTY_QUEUE] = 1; + break; + case TEST_STOP_DUE_TO_MAX_REQ_NUM: + case TEST_RET_PARTIAL_MAX_FAIL_IDX: + mbtd->exp_packed_stats.pack_stop_reason[THRESHOLD] = 1; + break; + default: + mbtd->exp_packed_stats.pack_stop_reason[EMPTY_QUEUE] = 1; + } + mbtd->num_requests = num_requests; + + return 0; +} + +/* + * Prepare requests for the TEST_RET_PARTIAL_FOLLOWED_BY_ABORT testcase. + * In this testcase we have mixed error expectations from different + * write requests, hence the special prepare function. + */ +static int prepare_partial_followed_by_abort(struct test_data *td, + int num_requests) +{ + int i, start_address; + int is_err_expected = 0; + int ret = 0; + struct mmc_queue *mq = test_iosched_get_req_queue()->queuedata; + int max_packed_reqs; + + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + max_packed_reqs = mq->card->ext_csd.max_packed_writes; + + mmc_blk_init_packed_statistics(mq->card); + + for (i = 1 ; i <= num_requests ; i++) { + if (i > (num_requests / 2)) + is_err_expected = 1; + + start_address = td->start_sector + 4096*td->num_of_write_bios; + ret = test_iosched_add_wr_rd_test_req(is_err_expected, WRITE, + start_address, (i%5)+1, TEST_PATTERN_5A, NULL); + if (ret) { + test_pr_err("%s: failed to add a write request", + __func__); + return ret; + } + } + + memset((void *)&mbtd->exp_packed_stats.pack_stop_reason, 0, + sizeof(mbtd->exp_packed_stats.pack_stop_reason)); + memset(mbtd->exp_packed_stats.packing_events, 0, + (max_packed_reqs + 1) * sizeof(u32)); + mbtd->exp_packed_stats.packing_events[num_requests] = 1; + mbtd->exp_packed_stats.pack_stop_reason[EMPTY_QUEUE] = 1; + + mbtd->num_requests = num_requests; + + return ret; +} + +/* + * Get number of write requests for current testcase. If random test mode was + * chosen, pseudo-randomly choose the number of requests, otherwise set to + * two less than the packing threshold. + */ +static int get_num_requests(struct test_data *td) +{ + int *seed = &mbtd->random_test_seed; + struct request_queue *req_q; + struct mmc_queue *mq; + int max_num_requests; + int num_requests; + int min_num_requests = 2; + int is_random = mbtd->is_random; + + req_q = test_iosched_get_req_queue(); + if (req_q) + mq = req_q->queuedata; + else { + test_pr_err("%s: NULL request queue", __func__); + return 0; + } + + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + max_num_requests = mq->card->ext_csd.max_packed_writes; + num_requests = max_num_requests - 2; + + if (is_random) { + if (td->test_info.testcase == + TEST_RET_PARTIAL_FOLLOWED_BY_ABORT) + min_num_requests = 3; + + num_requests = pseudo_random_seed(seed, min_num_requests, + max_num_requests - 1); + } + + return num_requests; +} + +/* + * An implementation for the prepare_test_fn pointer in the test_info + * data structure. According to the testcase we add the right number of requests + * and decide if an error is expected or not. + */ +static int prepare_test(struct test_data *td) +{ + struct mmc_queue *mq = test_iosched_get_req_queue()->queuedata; + int max_num_requests; + int num_requests = 0; + int ret = 0; + int is_random = mbtd->is_random; + + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + max_num_requests = mq->card->ext_csd.max_packed_writes; + + if (is_random && mbtd->random_test_seed == 0) { + mbtd->random_test_seed = + (unsigned int)(get_jiffies_64() & 0xFFFF); + test_pr_info("%s: got seed from jiffies %d", + __func__, mbtd->random_test_seed); + } + + num_requests = get_num_requests(td); + + if (mbtd->test_group == TEST_SEND_INVALID_GROUP) + mq->packed_test_fn = + test_invalid_packed_cmd; + + if (mbtd->test_group == TEST_ERR_CHECK_GROUP) + mq->err_check_fn = test_err_check; + + switch (td->test_info.testcase) { + case TEST_STOP_DUE_TO_FLUSH: + case TEST_STOP_DUE_TO_READ: + case TEST_RET_PARTIAL_FOLLOWED_BY_SUCCESS: + case TEST_RET_PARTIAL_MULTIPLE_UNTIL_SUCCESS: + case TEST_STOP_DUE_TO_EMPTY_QUEUE: + case TEST_CMD23_PACKED_BIT_UNSET: + ret = prepare_packed_requests(td, 0, num_requests, is_random); + break; + case TEST_STOP_DUE_TO_FLUSH_AFTER_MAX_REQS: + case TEST_STOP_DUE_TO_READ_AFTER_MAX_REQS: + ret = prepare_packed_requests(td, 0, max_num_requests - 1, + is_random); + break; + case TEST_RET_PARTIAL_FOLLOWED_BY_ABORT: + ret = prepare_partial_followed_by_abort(td, num_requests); + break; + case TEST_STOP_DUE_TO_MAX_REQ_NUM: + case TEST_RET_PARTIAL_MAX_FAIL_IDX: + ret = prepare_packed_requests(td, 0, max_num_requests, + is_random); + break; + case TEST_STOP_DUE_TO_THRESHOLD: + ret = prepare_packed_requests(td, 0, max_num_requests + 1, + is_random); + break; + case TEST_RET_ABORT: + case TEST_RET_RETRY: + case TEST_RET_CMD_ERR: + case TEST_RET_DATA_ERR: + case TEST_HDR_INVALID_VERSION: + case TEST_HDR_WRONG_WRITE_CODE: + case TEST_HDR_INVALID_RW_CODE: + case TEST_HDR_DIFFERENT_ADDRESSES: + case TEST_HDR_REQ_NUM_SMALLER_THAN_ACTUAL: + case TEST_HDR_REQ_NUM_LARGER_THAN_ACTUAL: + case TEST_CMD23_MAX_PACKED_WRITES: + case TEST_CMD23_ZERO_PACKED_WRITES: + case TEST_CMD23_REL_WR_BIT_SET: + case TEST_CMD23_BITS_16TO29_SET: + case TEST_CMD23_HDR_BLK_NOT_IN_COUNT: + case TEST_HDR_CMD23_PACKED_BIT_SET: + ret = prepare_packed_requests(td, 1, num_requests, is_random); + break; + default: + test_pr_info("%s: Invalid test case...", __func__); + return -EINVAL; + } + + return ret; +} + +/* + * An implementation for the post_test_fn in the test_info data structure. + * In our case we just reset the function pointers in the mmc_queue in order for + * the FS to be able to dispatch it's requests correctly after the test is + * finished. + */ +static int post_test(struct test_data *td) +{ + struct mmc_queue *mq; + + if (!td) + return -EINVAL; + + mq = td->req_q->queuedata; + + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + mq->packed_test_fn = NULL; + mq->err_check_fn = NULL; + + return 0; +} + +/* + * This function checks, based on the current test's test_group, that the + * packed commands capability and control are set right. In addition, we check + * if the card supports the packed command feature. + */ +static int validate_packed_commands_settings(void) +{ + struct request_queue *req_q; + struct mmc_queue *mq; + int max_num_requests; + struct mmc_host *host; + + req_q = test_iosched_get_req_queue(); + if (!req_q) { + test_pr_err("%s: test_iosched_get_req_queue failed", __func__); + test_iosched_set_test_result(TEST_FAILED); + return -EINVAL; + } + + mq = req_q->queuedata; + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + max_num_requests = mq->card->ext_csd.max_packed_writes; + host = mq->card->host; + + if (!(host->caps2 && MMC_CAP2_PACKED_WR)) { + test_pr_err("%s: Packed Write capability disabled, exit test", + __func__); + test_iosched_set_test_result(TEST_NOT_SUPPORTED); + return -EINVAL; + } + + if (max_num_requests == 0) { + test_pr_err( + "%s: no write packing support, ext_csd.max_packed_writes=%d", + __func__, mq->card->ext_csd.max_packed_writes); + test_iosched_set_test_result(TEST_NOT_SUPPORTED); + return -EINVAL; + } + + test_pr_info("%s: max number of packed requests supported is %d ", + __func__, max_num_requests); + + switch (mbtd->test_group) { + case TEST_SEND_WRITE_PACKING_GROUP: + case TEST_ERR_CHECK_GROUP: + case TEST_SEND_INVALID_GROUP: + /* disable the packing control */ + host->caps2 &= ~MMC_CAP2_PACKED_WR_CONTROL; + break; + default: + break; + } + + return 0; +} + +static bool message_repeat; +static int test_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + message_repeat = 1; + return 0; +} + +/* send_packing TEST */ +static ssize_t send_write_packing_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + int j = 0; + + test_pr_info("%s: -- send_write_packing TEST --", __func__); + + sscanf(buf, "%d", &number); + + if (number <= 0) + number = 1; + + + mbtd->test_group = TEST_SEND_WRITE_PACKING_GROUP; + + if (validate_packed_commands_settings()) + return count; + + if (mbtd->random_test_seed > 0) + test_pr_info("%s: Test seed: %d", __func__, + mbtd->random_test_seed); + + memset(&mbtd->test_info, 0, sizeof(struct test_info)); + + mbtd->test_info.data = mbtd; + mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; + mbtd->test_info.get_test_case_str_fn = get_test_case_str; + mbtd->test_info.post_test_fn = post_test; + + for (i = 0 ; i < number ; ++i) { + test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); + test_pr_info("%s: ====================", __func__); + + for (j = SEND_WRITE_PACKING_MIN_TESTCASE ; + j <= SEND_WRITE_PACKING_MAX_TESTCASE ; j++) { + + mbtd->test_info.testcase = j; + mbtd->is_random = RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) + break; + /* Allow FS requests to be dispatched */ + msleep(1000); + mbtd->test_info.testcase = j; + mbtd->is_random = NON_RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) + break; + /* Allow FS requests to be dispatched */ + msleep(1000); + } + } + + test_pr_info("%s: Completed all the test cases.", __func__); + + return count; +} + +static ssize_t send_write_packing_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nsend_write_packing_test\n" + "=========\n" + "Description:\n" + "This test checks the following scenarios\n" + "- Pack due to FLUSH message\n" + "- Pack due to FLUSH after threshold writes\n" + "- Pack due to READ message\n" + "- Pack due to READ after threshold writes\n" + "- Pack due to empty queue\n" + "- Pack due to threshold writes\n" + "- Pack due to one over threshold writes\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations send_write_packing_test_ops = { + .open = test_open, + .write = send_write_packing_test_write, + .read = send_write_packing_test_read, +}; + +/* err_check TEST */ +static ssize_t err_check_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + int j = 0; + + test_pr_info("%s: -- err_check TEST --", __func__); + + sscanf(buf, "%d", &number); + + if (number <= 0) + number = 1; + + mbtd->test_group = TEST_ERR_CHECK_GROUP; + + if (validate_packed_commands_settings()) + return count; + + if (mbtd->random_test_seed > 0) + test_pr_info("%s: Test seed: %d", __func__, + mbtd->random_test_seed); + + memset(&mbtd->test_info, 0, sizeof(struct test_info)); + + mbtd->test_info.data = mbtd; + mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; + mbtd->test_info.get_test_case_str_fn = get_test_case_str; + mbtd->test_info.post_test_fn = post_test; + + for (i = 0 ; i < number ; ++i) { + test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); + test_pr_info("%s: ====================", __func__); + + for (j = ERR_CHECK_MIN_TESTCASE; + j <= ERR_CHECK_MAX_TESTCASE ; j++) { + mbtd->test_info.testcase = j; + mbtd->is_random = RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) + break; + /* Allow FS requests to be dispatched */ + msleep(1000); + mbtd->test_info.testcase = j; + mbtd->is_random = NON_RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) + break; + /* Allow FS requests to be dispatched */ + msleep(1000); + } + } + + test_pr_info("%s: Completed all the test cases.", __func__); + + return count; +} + +static ssize_t err_check_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nerr_check_TEST\n" + "=========\n" + "Description:\n" + "This test checks the following scenarios\n" + "- Return ABORT\n" + "- Return PARTIAL followed by success\n" + "- Return PARTIAL followed by abort\n" + "- Return PARTIAL multiple times until success\n" + "- Return PARTIAL with fail index = threshold\n" + "- Return RETRY\n" + "- Return CMD_ERR\n" + "- Return DATA_ERR\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations err_check_test_ops = { + .open = test_open, + .write = err_check_test_write, + .read = err_check_test_read, +}; + +/* send_invalid_packed TEST */ +static ssize_t send_invalid_packed_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + int j = 0; + int num_of_failures = 0; + + test_pr_info("%s: -- send_invalid_packed TEST --", __func__); + + sscanf(buf, "%d", &number); + + if (number <= 0) + number = 1; + + mbtd->test_group = TEST_SEND_INVALID_GROUP; + + if (validate_packed_commands_settings()) + return count; + + if (mbtd->random_test_seed > 0) + test_pr_info("%s: Test seed: %d", __func__, + mbtd->random_test_seed); + + memset(&mbtd->test_info, 0, sizeof(struct test_info)); + + mbtd->test_info.data = mbtd; + mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; + mbtd->test_info.get_test_case_str_fn = get_test_case_str; + mbtd->test_info.post_test_fn = post_test; + + for (i = 0 ; i < number ; ++i) { + test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); + test_pr_info("%s: ====================", __func__); + + for (j = INVALID_CMD_MIN_TESTCASE; + j <= INVALID_CMD_MAX_TESTCASE ; j++) { + + mbtd->test_info.testcase = j; + mbtd->is_random = RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) + num_of_failures++; + /* Allow FS requests to be dispatched */ + msleep(1000); + + mbtd->test_info.testcase = j; + mbtd->is_random = NON_RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) + num_of_failures++; + /* Allow FS requests to be dispatched */ + msleep(1000); + } + } + + test_pr_info("%s: Completed all the test cases.", __func__); + + if (num_of_failures > 0) { + test_iosched_set_test_result(TEST_FAILED); + test_pr_err( + "There were %d failures during the test, TEST FAILED", + num_of_failures); + } + return count; +} + +static ssize_t send_invalid_packed_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nsend_invalid_packed_TEST\n" + "=========\n" + "Description:\n" + "This test checks the following scenarios\n" + "- Send an invalid header version\n" + "- Send the wrong write code\n" + "- Send an invalid R/W code\n" + "- Send wrong start address in header\n" + "- Send header with block_count smaller than actual\n" + "- Send header with block_count larger than actual\n" + "- Send header CMD23 packed bit set\n" + "- Send CMD23 with block count over threshold\n" + "- Send CMD23 with block_count equals zero\n" + "- Send CMD23 packed bit unset\n" + "- Send CMD23 reliable write bit set\n" + "- Send CMD23 bits [16-29] set\n" + "- Send CMD23 header block not in block_count\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations send_invalid_packed_test_ops = { + .open = test_open, + .write = send_invalid_packed_test_write, + .read = send_invalid_packed_test_read, +}; + +static void mmc_block_test_debugfs_cleanup(void) +{ + debugfs_remove(mbtd->debug.random_test_seed); + debugfs_remove(mbtd->debug.send_write_packing_test); + debugfs_remove(mbtd->debug.err_check_test); + debugfs_remove(mbtd->debug.send_invalid_packed_test); +} + +static int mmc_block_test_debugfs_init(void) +{ + struct dentry *utils_root, *tests_root; + + utils_root = test_iosched_get_debugfs_utils_root(); + tests_root = test_iosched_get_debugfs_tests_root(); + + if (!utils_root || !tests_root) + return -EINVAL; + + mbtd->debug.random_test_seed = debugfs_create_u32( + "random_test_seed", + S_IRUGO | S_IWUGO, + utils_root, + &mbtd->random_test_seed); + + if (!mbtd->debug.random_test_seed) + goto err_nomem; + + mbtd->debug.send_write_packing_test = + debugfs_create_file("send_write_packing_test", + S_IRUGO | S_IWUGO, + tests_root, + NULL, + &send_write_packing_test_ops); + + if (!mbtd->debug.send_write_packing_test) + goto err_nomem; + + mbtd->debug.err_check_test = + debugfs_create_file("err_check_test", + S_IRUGO | S_IWUGO, + tests_root, + NULL, + &err_check_test_ops); + + if (!mbtd->debug.err_check_test) + goto err_nomem; + + mbtd->debug.send_invalid_packed_test = + debugfs_create_file("send_invalid_packed_test", + S_IRUGO | S_IWUGO, + tests_root, + NULL, + &send_invalid_packed_test_ops); + + if (!mbtd->debug.send_invalid_packed_test) + goto err_nomem; + + return 0; + +err_nomem: + mmc_block_test_debugfs_cleanup(); + return -ENOMEM; +} + +static void mmc_block_test_probe(void) +{ + struct request_queue *q = test_iosched_get_req_queue(); + struct mmc_queue *mq; + int max_packed_reqs; + + if (!q) { + test_pr_err("%s: NULL request queue", __func__); + return; + } + + mq = q->queuedata; + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return; + } + + max_packed_reqs = mq->card->ext_csd.max_packed_writes; + mbtd->exp_packed_stats.packing_events = + kzalloc((max_packed_reqs + 1) * + sizeof(*mbtd->exp_packed_stats.packing_events), + GFP_KERNEL); + + mmc_block_test_debugfs_init(); +} + +static void mmc_block_test_remove(void) +{ + mmc_block_test_debugfs_cleanup(); +} + +static int __init mmc_block_test_init(void) +{ + mbtd = kzalloc(sizeof(struct mmc_block_test_data), GFP_KERNEL); + if (!mbtd) { + test_pr_err("%s: failed to allocate mmc_block_test_data", + __func__); + return -ENODEV; + } + + mbtd->bdt.init_fn = mmc_block_test_probe; + mbtd->bdt.exit_fn = mmc_block_test_remove; + INIT_LIST_HEAD(&mbtd->bdt.list); + test_iosched_register(&mbtd->bdt); + + return 0; +} + +static void __exit mmc_block_test_exit(void) +{ + test_iosched_unregister(&mbtd->bdt); + kfree(mbtd); +} + +module_init(mmc_block_test_init); +module_exit(mmc_block_test_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MMC block test"); diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 971287b29e57..656de4677cbd 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -61,6 +61,8 @@ struct mmc_queue { bool wr_packing_enabled; int num_of_potential_packed_wr_reqs; int num_wr_reqs_to_start_packing; + int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); + void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, @@ -79,4 +81,6 @@ extern void mmc_packed_clean(struct mmc_queue *); extern int mmc_access_rpmb(struct mmc_queue *); +extern void print_mmc_packing_stats(struct mmc_card *card); + #endif From cbaf05043f2e738526346ba947485cf99b2f3ad5 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Tue, 9 Oct 2012 13:53:43 +0200 Subject: [PATCH 144/472] mmc: card: Add eMMC4.5 packing control unit-tests Expose the packing control test. Test the packing control feature under these scenarios: - Packing expected: entering write packing state, staying in write packing state. - Packing not expected: not entering write packing state when not supposed to. - Mixed states: test the ability to shift from packing to no-packing and back, and the opposite. Change-Id: I5a93df8b5b7c48d355c3cc881699a3f9592914e6 Signed-off-by: Lee Susman Signed-off-by: Tatyana Brokhman --- drivers/mmc/card/mmc_block_test.c | 433 +++++++++++++++++++++++++++++- 1 file changed, 423 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/card/mmc_block_test.c b/drivers/mmc/card/mmc_block_test.c index a083efe1e09f..df7bbb2d4c03 100644 --- a/drivers/mmc/card/mmc_block_test.c +++ b/drivers/mmc/card/mmc_block_test.c @@ -82,6 +82,25 @@ enum mmc_block_test_testcases { TEST_CMD23_BITS_16TO29_SET, TEST_CMD23_HDR_BLK_NOT_IN_COUNT, INVALID_CMD_MAX_TESTCASE = TEST_CMD23_HDR_BLK_NOT_IN_COUNT, + + /* + * Start of packing control test group. + * in these next testcases the abbreviation FB = followed by + */ + PACKING_CONTROL_MIN_TESTCASE, + TEST_PACKING_EXP_ONE_OVER_TRIGGER_FB_READ = + PACKING_CONTROL_MIN_TESTCASE, + TEST_PACKING_EXP_N_OVER_TRIGGER, + TEST_PACKING_EXP_N_OVER_TRIGGER_FB_READ, + TEST_PACKING_EXP_N_OVER_TRIGGER_FLUSH_N, + TEST_PACKING_EXP_THRESHOLD_OVER_TRIGGER, + TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS, + TEST_PACKING_NOT_EXP_TRIGGER_REQUESTS, + TEST_PACKING_NOT_EXP_TRIGGER_READ_TRIGGER, + TEST_PACKING_NOT_EXP_TRIGGER_FLUSH_TRIGGER, + TEST_PACK_MIX_PACKED_NO_PACKED_PACKED, + TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED, + PACKING_CONTROL_MAX_TESTCASE = TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED, }; enum mmc_block_test_group { @@ -90,6 +109,7 @@ enum mmc_block_test_group { TEST_SEND_WRITE_PACKING_GROUP, TEST_ERR_CHECK_GROUP, TEST_SEND_INVALID_GROUP, + TEST_PACKING_CONTROL_GROUP, }; struct mmc_block_test_debug { @@ -97,6 +117,7 @@ struct mmc_block_test_debug { struct dentry *err_check_test; struct dentry *send_invalid_packed_test; struct dentry *random_test_seed; + struct dentry *packing_control_test; }; struct mmc_block_test_data { @@ -453,6 +474,28 @@ static char *get_test_case_str(struct test_data *td) return "Test invalid - cmd23 bits [16-29] set"; case TEST_CMD23_HDR_BLK_NOT_IN_COUNT: return "Test invalid - cmd23 header block not in count"; + case TEST_PACKING_EXP_N_OVER_TRIGGER: + return "\nTest packing control - pack n"; + case TEST_PACKING_EXP_N_OVER_TRIGGER_FB_READ: + return "\nTest packing control - pack n followed by read"; + case TEST_PACKING_EXP_N_OVER_TRIGGER_FLUSH_N: + return "\nTest packing control - pack n followed by flush"; + case TEST_PACKING_EXP_ONE_OVER_TRIGGER_FB_READ: + return "\nTest packing control - pack one followed by read"; + case TEST_PACKING_EXP_THRESHOLD_OVER_TRIGGER: + return "\nTest packing control - pack threshold"; + case TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS: + return "\nTest packing control - no packing"; + case TEST_PACKING_NOT_EXP_TRIGGER_REQUESTS: + return "\nTest packing control - no packing, trigger requests"; + case TEST_PACKING_NOT_EXP_TRIGGER_READ_TRIGGER: + return "\nTest packing control - no pack, trigger-read-trigger"; + case TEST_PACKING_NOT_EXP_TRIGGER_FLUSH_TRIGGER: + return "\nTest packing control- no pack, trigger-flush-trigger"; + case TEST_PACK_MIX_PACKED_NO_PACKED_PACKED: + return "\nTest packing control - mix: pack -> no pack -> pack"; + case TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED: + return "\nTest packing control - mix: no pack->pack->no pack"; default: return "Unknown testcase"; } @@ -504,7 +547,7 @@ static int check_wr_packing_statistics(struct test_data *td) stop_reason = mmc_packed_stats->pack_stop_reason; - for (i = 1 ; i <= max_packed_reqs ; ++i) { + for (i = 1; i <= max_packed_reqs; ++i) { if (mmc_packed_stats->packing_events[i] != expected_stats.packing_events[i]) { test_pr_err( @@ -709,7 +752,7 @@ static int prepare_request_add_write_reqs(struct test_data *td, test_pr_info("%s: Adding %d write requests, first req_id=%d", __func__, num_requests, td->wr_rd_next_req_id); - for (i = 1 ; i <= num_requests ; i++) { + for (i = 1; i <= num_requests; i++) { start_sec = td->start_sector + 4096 * td->num_of_write_bios; if (is_random) pseudo_rnd_num_of_bios(bio_seed, &num_bios); @@ -824,6 +867,185 @@ static int prepare_packed_requests(struct test_data *td, int is_err_expected, return 0; } +/* + * Prepare the write, read and flush requests for the packing control + * testcases + */ +static int prepare_packed_control_tests_requests(struct test_data *td, + int is_err_expected, int num_requests, int is_random) +{ + int ret = 0; + struct mmc_queue *mq; + int max_packed_reqs; + int temp_num_req = num_requests; + struct request_queue *req_q; + int test_packed_trigger; + int num_packed_reqs; + + if (!td) { + test_pr_err("%s: NULL td\n", __func__); + return -EINVAL; + } + + req_q = td->req_q; + + if (!req_q) { + test_pr_err("%s: NULL request queue\n", __func__); + return -EINVAL; + } + + mq = req_q->queuedata; + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + + max_packed_reqs = mq->card->ext_csd.max_packed_writes; + test_packed_trigger = mq->num_wr_reqs_to_start_packing; + num_packed_reqs = num_requests - test_packed_trigger; + + if (mbtd->random_test_seed == 0) { + mbtd->random_test_seed = + (unsigned int)(get_jiffies_64() & 0xFFFF); + test_pr_info("%s: got seed from jiffies %d", + __func__, mbtd->random_test_seed); + } + + mmc_blk_init_packed_statistics(mq->card); + + if (td->test_info.testcase == + TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED) { + temp_num_req = num_requests; + num_requests = test_packed_trigger - 1; + } + + /* Verify that the packing is disabled before starting the test */ + mq->wr_packing_enabled = false; + mq->num_of_potential_packed_wr_reqs = 0; + + if (td->test_info.testcase == TEST_PACK_MIX_PACKED_NO_PACKED_PACKED) { + mq->num_of_potential_packed_wr_reqs = test_packed_trigger + 1; + mq->wr_packing_enabled = true; + num_requests = test_packed_trigger + 2; + } + + ret = prepare_request_add_write_reqs(td, num_requests, is_err_expected, + is_random); + if (ret) + goto exit; + + if (td->test_info.testcase == TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED) + num_requests = temp_num_req; + + memset((void *)mbtd->exp_packed_stats.pack_stop_reason, 0, + sizeof(mbtd->exp_packed_stats.pack_stop_reason)); + memset(mbtd->exp_packed_stats.packing_events, 0, + (max_packed_reqs + 1) * sizeof(u32)); + + switch (td->test_info.testcase) { + case TEST_PACKING_EXP_N_OVER_TRIGGER_FB_READ: + case TEST_PACKING_EXP_ONE_OVER_TRIGGER_FB_READ: + ret = prepare_request_add_read(td); + if (ret) + goto exit; + + mbtd->exp_packed_stats.pack_stop_reason[WRONG_DATA_DIR] = 1; + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 1; + break; + case TEST_PACKING_EXP_N_OVER_TRIGGER_FLUSH_N: + ret = prepare_request_add_flush(td); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, num_packed_reqs, + is_err_expected, is_random); + if (ret) + goto exit; + + mbtd->exp_packed_stats.pack_stop_reason[EMPTY_QUEUE] = 1; + mbtd->exp_packed_stats.pack_stop_reason[FLUSH_OR_DISCARD] = 1; + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 2; + break; + case TEST_PACKING_NOT_EXP_TRIGGER_READ_TRIGGER: + ret = prepare_request_add_read(td); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, test_packed_trigger, + is_err_expected, is_random); + if (ret) + goto exit; + + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 1; + break; + case TEST_PACKING_NOT_EXP_TRIGGER_FLUSH_TRIGGER: + ret = prepare_request_add_flush(td); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, test_packed_trigger, + is_err_expected, is_random); + if (ret) + goto exit; + + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 1; + break; + case TEST_PACK_MIX_PACKED_NO_PACKED_PACKED: + ret = prepare_request_add_read(td); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, test_packed_trigger-1, + is_err_expected, is_random); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, num_requests, + is_err_expected, is_random); + if (ret) + goto exit; + + mbtd->exp_packed_stats.packing_events[num_requests] = 1; + mbtd->exp_packed_stats.packing_events[num_requests-1] = 1; + mbtd->exp_packed_stats.pack_stop_reason[WRONG_DATA_DIR] = 1; + mbtd->exp_packed_stats.pack_stop_reason[EMPTY_QUEUE] = 1; + break; + case TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED: + ret = prepare_request_add_read(td); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, num_requests, + is_err_expected, is_random); + if (ret) + goto exit; + + ret = prepare_request_add_read(td); + if (ret) + goto exit; + + ret = prepare_request_add_write_reqs(td, test_packed_trigger-1, + is_err_expected, is_random); + if (ret) + goto exit; + + mbtd->exp_packed_stats.pack_stop_reason[WRONG_DATA_DIR] = 1; + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 1; + break; + case TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS: + case TEST_PACKING_NOT_EXP_TRIGGER_REQUESTS: + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 1; + break; + default: + mbtd->exp_packed_stats.pack_stop_reason[EMPTY_QUEUE] = 1; + mbtd->exp_packed_stats.packing_events[num_packed_reqs] = 1; + } + mbtd->num_requests = num_requests; + +exit: + return ret; +} + /* * Prepare requests for the TEST_RET_PARTIAL_FOLLOWED_BY_ABORT testcase. * In this testcase we have mixed error expectations from different @@ -847,13 +1069,14 @@ static int prepare_partial_followed_by_abort(struct test_data *td, mmc_blk_init_packed_statistics(mq->card); - for (i = 1 ; i <= num_requests ; i++) { + for (i = 1; i <= num_requests; i++) { if (i > (num_requests / 2)) is_err_expected = 1; - start_address = td->start_sector + 4096*td->num_of_write_bios; + start_address = td->start_sector + 4096 * td->num_of_write_bios; ret = test_iosched_add_wr_rd_test_req(is_err_expected, WRITE, - start_address, (i%5)+1, TEST_PATTERN_5A, NULL); + start_address, (i % 5) + 1, TEST_PATTERN_5A, + NULL); if (ret) { test_pr_err("%s: failed to add a write request", __func__); @@ -887,6 +1110,8 @@ static int get_num_requests(struct test_data *td) int num_requests; int min_num_requests = 2; int is_random = mbtd->is_random; + int max_for_double; + int test_packed_trigger; req_q = test_iosched_get_req_queue(); if (req_q) @@ -903,16 +1128,52 @@ static int get_num_requests(struct test_data *td) max_num_requests = mq->card->ext_csd.max_packed_writes; num_requests = max_num_requests - 2; + test_packed_trigger = mq->num_wr_reqs_to_start_packing; + + /* + * Here max_for_double is intended for packed control testcases + * in which we issue many write requests. It's purpose is to prevent + * exceeding max number of req_queue requests. + */ + max_for_double = max_num_requests - 10; + + if (td->test_info.testcase == + TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS) + /* Don't expect packing, so issue up to trigger-1 reqs */ + num_requests = test_packed_trigger - 1; if (is_random) { if (td->test_info.testcase == TEST_RET_PARTIAL_FOLLOWED_BY_ABORT) + /* + * Here we don't want num_requests to be less than 1 + * as a consequence of division by 2. + */ min_num_requests = 3; + if (td->test_info.testcase == + TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS) + /* Don't expect packing, so issue up to trigger reqs */ + max_num_requests = test_packed_trigger; + num_requests = pseudo_random_seed(seed, min_num_requests, max_num_requests - 1); } + if (td->test_info.testcase == + TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS) + num_requests -= test_packed_trigger; + + if (td->test_info.testcase == TEST_PACKING_EXP_N_OVER_TRIGGER_FLUSH_N) + num_requests = + num_requests > max_for_double ? max_for_double : num_requests; + + if (mbtd->test_group == TEST_PACKING_CONTROL_GROUP) + num_requests += test_packed_trigger; + + if (td->test_info.testcase == TEST_PACKING_NOT_EXP_TRIGGER_REQUESTS) + num_requests = test_packed_trigger; + return num_requests; } @@ -928,6 +1189,7 @@ static int prepare_test(struct test_data *td) int num_requests = 0; int ret = 0; int is_random = mbtd->is_random; + int test_packed_trigger = mq->num_wr_reqs_to_start_packing; if (!mq) { test_pr_err("%s: NULL mq", __func__); @@ -996,6 +1258,33 @@ static int prepare_test(struct test_data *td) case TEST_HDR_CMD23_PACKED_BIT_SET: ret = prepare_packed_requests(td, 1, num_requests, is_random); break; + case TEST_PACKING_EXP_N_OVER_TRIGGER: + case TEST_PACKING_EXP_N_OVER_TRIGGER_FB_READ: + case TEST_PACKING_NOT_EXP_TRIGGER_REQUESTS: + case TEST_PACKING_NOT_EXP_LESS_THAN_TRIGGER_REQUESTS: + case TEST_PACK_MIX_PACKED_NO_PACKED_PACKED: + case TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED: + ret = prepare_packed_control_tests_requests(td, 0, num_requests, + is_random); + break; + case TEST_PACKING_EXP_THRESHOLD_OVER_TRIGGER: + ret = prepare_packed_control_tests_requests(td, 0, + max_num_requests, is_random); + break; + case TEST_PACKING_EXP_ONE_OVER_TRIGGER_FB_READ: + ret = prepare_packed_control_tests_requests(td, 0, + test_packed_trigger + 1, + is_random); + break; + case TEST_PACKING_EXP_N_OVER_TRIGGER_FLUSH_N: + ret = prepare_packed_control_tests_requests(td, 0, num_requests, + is_random); + break; + case TEST_PACKING_NOT_EXP_TRIGGER_READ_TRIGGER: + case TEST_PACKING_NOT_EXP_TRIGGER_FLUSH_TRIGGER: + ret = prepare_packed_control_tests_requests(td, 0, + test_packed_trigger, is_random); + break; default: test_pr_info("%s: Invalid test case...", __func__); return -EINVAL; @@ -1083,6 +1372,9 @@ static int validate_packed_commands_settings(void) /* disable the packing control */ host->caps2 &= ~MMC_CAP2_PACKED_WR_CONTROL; break; + case TEST_PACKING_CONTROL_GROUP: + host->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; + break; default: break; } @@ -1134,12 +1426,12 @@ static ssize_t send_write_packing_test_write(struct file *file, mbtd->test_info.get_test_case_str_fn = get_test_case_str; mbtd->test_info.post_test_fn = post_test; - for (i = 0 ; i < number ; ++i) { + for (i = 0; i < number; ++i) { test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); test_pr_info("%s: ====================", __func__); - for (j = SEND_WRITE_PACKING_MIN_TESTCASE ; - j <= SEND_WRITE_PACKING_MAX_TESTCASE ; j++) { + for (j = SEND_WRITE_PACKING_MIN_TESTCASE; + j <= SEND_WRITE_PACKING_MAX_TESTCASE; j++) { mbtd->test_info.testcase = j; mbtd->is_random = RANDOM_TEST; @@ -1232,7 +1524,7 @@ static ssize_t err_check_test_write(struct file *file, mbtd->test_info.get_test_case_str_fn = get_test_case_str; mbtd->test_info.post_test_fn = post_test; - for (i = 0 ; i < number ; ++i) { + for (i = 0; i < number; ++i) { test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); test_pr_info("%s: ====================", __func__); @@ -1331,7 +1623,7 @@ static ssize_t send_invalid_packed_test_write(struct file *file, mbtd->test_info.get_test_case_str_fn = get_test_case_str; mbtd->test_info.post_test_fn = post_test; - for (i = 0 ; i < number ; ++i) { + for (i = 0; i < number; ++i) { test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); test_pr_info("%s: ====================", __func__); @@ -1407,12 +1699,123 @@ const struct file_operations send_invalid_packed_test_ops = { .read = send_invalid_packed_test_read, }; +/* packing_control TEST */ +static ssize_t write_packing_control_test_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *ppos) +{ + int ret = 0; + int i = 0; + int number = -1; + int j = 0; + struct mmc_queue *mq = test_iosched_get_req_queue()->queuedata; + int max_num_requests = mq->card->ext_csd.max_packed_writes; + int test_successful = 1; + + test_pr_info("%s: -- write_packing_control TEST --", __func__); + + sscanf(buf, "%d", &number); + + if (number <= 0) + number = 1; + + test_pr_info("%s: max_num_requests = %d ", __func__, + max_num_requests); + + memset(&mbtd->test_info, 0, sizeof(struct test_info)); + mbtd->test_group = TEST_PACKING_CONTROL_GROUP; + + if (validate_packed_commands_settings()) + return count; + + mbtd->test_info.data = mbtd; + mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; + mbtd->test_info.get_test_case_str_fn = get_test_case_str; + + for (i = 0; i < number; ++i) { + test_pr_info("%s: Cycle # %d / %d", __func__, i+1, number); + test_pr_info("%s: ====================", __func__); + + for (j = PACKING_CONTROL_MIN_TESTCASE; + j <= PACKING_CONTROL_MAX_TESTCASE; j++) { + + test_successful = 1; + mbtd->test_info.testcase = j; + mbtd->is_random = RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) { + test_successful = 0; + break; + } + /* Allow FS requests to be dispatched */ + msleep(1000); + + mbtd->test_info.testcase = j; + mbtd->is_random = NON_RANDOM_TEST; + ret = test_iosched_start_test(&mbtd->test_info); + if (ret) { + test_successful = 0; + break; + } + /* Allow FS requests to be dispatched */ + msleep(1000); + } + + if (!test_successful) + break; + } + + test_pr_info("%s: Completed all the test cases.", __func__); + + return count; +} + +static ssize_t write_packing_control_test_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *offset) +{ + memset((void *)buffer, 0, count); + + snprintf(buffer, count, + "\nwrite_packing_control_test\n" + "=========\n" + "Description:\n" + "This test checks the following scenarios\n" + "- Packing expected - one over trigger\n" + "- Packing expected - N over trigger\n" + "- Packing expected - N over trigger followed by read\n" + "- Packing expected - N over trigger followed by flush\n" + "- Packing expected - threshold over trigger FB by flush\n" + "- Packing not expected - less than trigger\n" + "- Packing not expected - trigger requests\n" + "- Packing not expected - trigger, read, trigger\n" + "- Mixed state - packing -> no packing -> packing\n" + "- Mixed state - no packing -> packing -> no packing\n"); + + if (message_repeat == 1) { + message_repeat = 0; + return strnlen(buffer, count); + } else { + return 0; + } +} + +const struct file_operations write_packing_control_test_ops = { + .open = test_open, + .write = write_packing_control_test_write, + .read = write_packing_control_test_read, +}; + static void mmc_block_test_debugfs_cleanup(void) { debugfs_remove(mbtd->debug.random_test_seed); debugfs_remove(mbtd->debug.send_write_packing_test); debugfs_remove(mbtd->debug.err_check_test); debugfs_remove(mbtd->debug.send_invalid_packed_test); + debugfs_remove(mbtd->debug.packing_control_test); } static int mmc_block_test_debugfs_init(void) @@ -1464,6 +1867,16 @@ static int mmc_block_test_debugfs_init(void) if (!mbtd->debug.send_invalid_packed_test) goto err_nomem; + mbtd->debug.packing_control_test = debugfs_create_file( + "packing_control_test", + S_IRUGO | S_IWUGO, + tests_root, + NULL, + &write_packing_control_test_ops); + + if (!mbtd->debug.packing_control_test) + goto err_nomem; + return 0; err_nomem: From eb587df25d81e29ddcbb72c0e78ad5feac510668 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 4 Dec 2014 00:15:42 +0200 Subject: [PATCH 145/472] mmc: block: Fix error handling of device attributes creation When a failure occurs while creating a device attribute, we need to remove previously created attributes prior to deleting the disk. Change-Id: I01a8d4437f372abdf33230c34a73b5806e97188b Signed-off-by: Maya Erez [merez@codeaurora.org: fixed conflicts as BKOPS is not taken] Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 8345571a8b1e..da56cd79c78f 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2685,10 +2685,12 @@ static int mmc_add_disk(struct mmc_blk_data *md) ret = device_create_file(disk_to_dev(md->disk), &md->num_wr_reqs_to_start_packing); if (ret) - goto power_ro_lock_fail; + goto num_wr_reqs_to_start_packing_fail; return ret; +num_wr_reqs_to_start_packing_fail: + device_remove_file(disk_to_dev(md->disk), &md->power_ro_lock); power_ro_lock_fail: device_remove_file(disk_to_dev(md->disk), &md->force_ro); force_ro_fail: From 7e79d2183d0feaf4ad1cf0ae7c876eb08e6be23d Mon Sep 17 00:00:00 2001 From: Lee Susman Date: Sun, 4 Nov 2012 15:04:41 +0200 Subject: [PATCH 146/472] mmc: relocation of print_mmc_packed_stats This function belongs to the file mmc_block_test.c since it is used only in this file. Change-Id: Ia832da6341b3053f0e4825d711668a3642482221 Signed-off-by: Lee Susman --- drivers/mmc/card/block.c | 58 ---------------------------- drivers/mmc/card/mmc_block_test.c | 63 +++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 61 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index da56cd79c78f..af253a228904 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1742,64 +1742,6 @@ void mmc_blk_init_packed_statistics(struct mmc_card *card) } EXPORT_SYMBOL(mmc_blk_init_packed_statistics); -void print_mmc_packing_stats(struct mmc_card *card) -{ - int i; - int max_num_of_packed_reqs = 0; - - if ((!card) || (!card->wr_pack_stats.packing_events)) - return; - - max_num_of_packed_reqs = card->ext_csd.max_packed_writes; - - spin_lock(&card->wr_pack_stats.lock); - - pr_info("%s: write packing statistics:\n", - mmc_hostname(card->host)); - - for (i = 1 ; i <= max_num_of_packed_reqs ; ++i) { - if (card->wr_pack_stats.packing_events[i] != 0) - pr_info("%s: Packed %d reqs - %d times\n", - mmc_hostname(card->host), i, - card->wr_pack_stats.packing_events[i]); - } - - pr_info("%s: stopped packing due to the following reasons:\n", - mmc_hostname(card->host)); - - if (card->wr_pack_stats.pack_stop_reason[EXCEEDS_SEGMENTS]) - pr_info("%s: %d times: exceedmax num of segments\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[EXCEEDS_SEGMENTS]); - if (card->wr_pack_stats.pack_stop_reason[EXCEEDS_SECTORS]) - pr_info("%s: %d times: exceeding the max num of sectors\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[EXCEEDS_SECTORS]); - if (card->wr_pack_stats.pack_stop_reason[WRONG_DATA_DIR]) - pr_info("%s: %d times: wrong data direction\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[WRONG_DATA_DIR]); - if (card->wr_pack_stats.pack_stop_reason[FLUSH_OR_DISCARD]) - pr_info("%s: %d times: flush or discard\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[FLUSH_OR_DISCARD]); - if (card->wr_pack_stats.pack_stop_reason[EMPTY_QUEUE]) - pr_info("%s: %d times: empty queue\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[EMPTY_QUEUE]); - if (card->wr_pack_stats.pack_stop_reason[REL_WRITE]) - pr_info("%s: %d times: rel write\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[REL_WRITE]); - if (card->wr_pack_stats.pack_stop_reason[THRESHOLD]) - pr_info("%s: %d times: Threshold\n", - mmc_hostname(card->host), - card->wr_pack_stats.pack_stop_reason[THRESHOLD]); - - spin_unlock(&card->wr_pack_stats.lock); -} -EXPORT_SYMBOL(print_mmc_packing_stats); - static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) { struct request_queue *q = mq->queue; diff --git a/drivers/mmc/card/mmc_block_test.c b/drivers/mmc/card/mmc_block_test.c index df7bbb2d4c03..f7cd8012cfe5 100644 --- a/drivers/mmc/card/mmc_block_test.c +++ b/drivers/mmc/card/mmc_block_test.c @@ -153,6 +153,63 @@ struct mmc_block_test_data { static struct mmc_block_test_data *mbtd; +void print_mmc_packing_stats(struct mmc_card *card) +{ + int i; + int max_num_of_packed_reqs = 0; + + if ((!card) || (!card->wr_pack_stats.packing_events)) + return; + + max_num_of_packed_reqs = card->ext_csd.max_packed_writes; + + spin_lock(&card->wr_pack_stats.lock); + + pr_info("%s: write packing statistics:\n", + mmc_hostname(card->host)); + + for (i = 1 ; i <= max_num_of_packed_reqs ; ++i) { + if (card->wr_pack_stats.packing_events[i] != 0) + pr_info("%s: Packed %d reqs - %d times\n", + mmc_hostname(card->host), i, + card->wr_pack_stats.packing_events[i]); + } + + pr_info("%s: stopped packing due to the following reasons:\n", + mmc_hostname(card->host)); + + if (card->wr_pack_stats.pack_stop_reason[EXCEEDS_SEGMENTS]) + pr_info("%s: %d times: exceedmax num of segments\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[EXCEEDS_SEGMENTS]); + if (card->wr_pack_stats.pack_stop_reason[EXCEEDS_SECTORS]) + pr_info("%s: %d times: exceeding the max num of sectors\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[EXCEEDS_SECTORS]); + if (card->wr_pack_stats.pack_stop_reason[WRONG_DATA_DIR]) + pr_info("%s: %d times: wrong data direction\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[WRONG_DATA_DIR]); + if (card->wr_pack_stats.pack_stop_reason[FLUSH_OR_DISCARD]) + pr_info("%s: %d times: flush or discard\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[FLUSH_OR_DISCARD]); + if (card->wr_pack_stats.pack_stop_reason[EMPTY_QUEUE]) + pr_info("%s: %d times: empty queue\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[EMPTY_QUEUE]); + if (card->wr_pack_stats.pack_stop_reason[REL_WRITE]) + pr_info("%s: %d times: rel write\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[REL_WRITE]); + if (card->wr_pack_stats.pack_stop_reason[THRESHOLD]) + pr_info("%s: %d times: Threshold\n", + mmc_hostname(card->host), + card->wr_pack_stats.pack_stop_reason[THRESHOLD]); + + spin_unlock(&card->wr_pack_stats.lock); +} + /* * A callback assigned to the packed_test_fn field. * Called from block layer in mmc_blk_packed_hdr_wrq_prep. @@ -419,11 +476,11 @@ static char *get_test_case_str(struct test_data *td) switch (td->test_info.testcase) { case TEST_STOP_DUE_TO_FLUSH: - return "Test stop due to flush"; + return " stop due to flush"; case TEST_STOP_DUE_TO_FLUSH_AFTER_MAX_REQS: - return "Test stop due to flush after max-1 reqs"; + return " stop due to flush after max-1 reqs"; case TEST_STOP_DUE_TO_READ: - return "Test stop due to read"; + return " stop due to read"; case TEST_STOP_DUE_TO_READ_AFTER_MAX_REQS: return "Test stop due to read after max-1 reqs"; case TEST_STOP_DUE_TO_EMPTY_QUEUE: From 3c0d768b709658651db5f9ff72c70714b546e4f1 Mon Sep 17 00:00:00 2001 From: Yaniv Gardi Date: Thu, 4 Dec 2014 00:26:23 +0200 Subject: [PATCH 147/472] mmc: card: re-run test because of timing issues Due to timing issues, the BKOPS test: Level 1 with HPI, might not yield the expected scenario. In this case we wouldn't like to have a FAILURE but to mark the test as a test that should be re-run. Change-Id: If47d7f9ce250c46249c5ddc061d8b808100d2f94 Signed-off-by: Yaniv Gardi [merez@codeaurora.org: fix conflicts due to removal of BKOPS] Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 26 +++++++++++++++++++++++--- drivers/mmc/card/queue.c | 4 +++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index af253a228904..da0c62a692a4 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -319,13 +319,33 @@ num_wr_reqs_to_start_packing_store(struct device *dev, { int value; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + struct mmc_card *card = md->queue.card; + int ret = count; + + if (!card) { + ret = -EINVAL; + goto exit; + } sscanf(buf, "%d", &value); - if (value >= 0) - md->queue.num_wr_reqs_to_start_packing = value; + if (value >= 0) { + md->queue.num_wr_reqs_to_start_packing = + min_t(int, value, (int)card->ext_csd.max_packed_writes); + + pr_debug("%s: trigger to pack: new value = %d", + mmc_hostname(card->host), + md->queue.num_wr_reqs_to_start_packing); + } else { + pr_err("%s: value %d is not valid. old value remains = %d", + mmc_hostname(card->host), value, + md->queue.num_wr_reqs_to_start_packing); + ret = -EINVAL; + } + +exit: mmc_blk_put(md); - return count; + return ret; } static int mmc_blk_open(struct block_device *bdev, fmode_t mode) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 61759f5559f4..de3c1dd0a569 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -218,7 +218,9 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, mq->mqrq_cur = mqrq_cur; mq->mqrq_prev = mqrq_prev; mq->queue->queuedata = mq; - mq->num_wr_reqs_to_start_packing = DEFAULT_NUM_REQS_TO_START_PACK; + mq->num_wr_reqs_to_start_packing = + min_t(int, (int)card->ext_csd.max_packed_writes, + DEFAULT_NUM_REQS_TO_START_PACK); blk_queue_prep_rq(mq->queue, mmc_prep_request); queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue); From 45af9e56df24904bad335486b1dc2bf6dcff3535 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 4 Dec 2014 09:37:48 +0200 Subject: [PATCH 148/472] mmc: sdio: fix sdio_reset_comm() to take care of UHS card reset sdio_reset_comm() function may be invoked by the SDIO card function driver to execute soft reset of the card but this function currently fails to reset the UHS SDIO cards. This change fixes this issue by simply invoking SDIO power restore function which is capable of resetting both UHS and non-UHS SDIO cards. CRs-Fixed: 424685 Change-Id: I4f1d76a6bfc4bfec16661aedba85c45b601f24ac Signed-off-by: Subhash Jadavani [merez@codeaurora.org: fix trivial conflicts] Signed-off-by: Maya Erez --- drivers/mmc/core/sdio.c | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 434d2b7a794f..0aa19ac04091 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -1241,38 +1241,6 @@ err: int sdio_reset_comm(struct mmc_card *card) { - struct mmc_host *host = card->host; - u32 ocr; - u32 rocr; - int err; - - printk("%s():\n", __func__); - mmc_claim_host(host); - - mmc_go_idle(host); - - mmc_set_clock(host, host->f_min); - - err = mmc_send_io_op_cond(host, 0, &ocr); - if (err) - goto err; - - rocr = mmc_select_voltage(host, ocr); - if (!rocr) { - err = -EINVAL; - goto err; - } - - err = mmc_sdio_init_card(host, rocr, card, 0); - if (err) - goto err; - - mmc_release_host(host); - return 0; -err: - printk("%s: Error resetting SDIO communications (%d)\n", - mmc_hostname(host), err); - mmc_release_host(host); - return err; + return mmc_power_restore_host(card->host); } EXPORT_SYMBOL(sdio_reset_comm); From baf82d92c551c8d87e124ead42e9b603a9bd9b1b Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Thu, 4 Dec 2014 09:57:23 +0200 Subject: [PATCH 149/472] mmc: core: Add load based clock scaling support The SD3.0/eMMC4.5/SDIO3.0 cards can support clock rates upto 200MHz (SDR104 or HS200 bus speed modes). For some workloads like video playback it isn't necessary for these cards to run at such high speed. Running at lower frequency, say 50MHz, in such cases can still meet the deadlines for data transfers. Scaling down the clock frequency dynamically has huge power savings not only because the bus is running at lower frequency but also has an advantage of scaling down the system core voltage, if supported. Provide an ondemand clock scaling support similar to cpufreq ondemand governor having two thresholds, up_threshold and down_threshold to decide whether to increase the frequency or scale it down respectively. The sampling interval is in the order of milliseconds and should be chosen by host drivers that enable MMC_CAP2_CLK_SCALE capability to take advantage of clock scaling. The sampling interval mainly depends on the the clock switching delays and hence a host driver decision. If sampling interval is too low frequent switching of frequencies can lead to high power consumption and if sampling interval is too high, the clock scaling logic would take long time to realize that the underlying hardware (controller and card) is busy and scale up the clocks. Change-Id: I22a5054beec41b0b66b3bf030ddfcf284de448b3 Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fixed conflicts due to changes in 3.14] Signed-off-by: Maya Erez [venkatg@codeaurora.org: runtime pm related changes to accommodate pm framework from 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed merge conflicts and fixed compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 297 +++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/core.h | 7 + drivers/mmc/core/host.c | 7 + drivers/mmc/core/mmc.c | 25 +++- drivers/mmc/core/sd.c | 18 +++ include/linux/mmc/host.h | 36 ++--- 6 files changed, 370 insertions(+), 20 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index f4acc56bb2c8..ae96334b9c8f 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -52,6 +53,8 @@ /* If the device is not responding */ #define MMC_CORE_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */ +static void mmc_clk_scaling(struct mmc_host *host, bool from_wq); + /* * Background operations can take a long time, depending on the housekeeping * operations the card has to perform. @@ -139,6 +142,10 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) #ifdef CONFIG_MMC_PERF_PROFILING ktime_t diff; #endif + if (host->card && host->clk_scaling.enable) + host->clk_scaling.busy_time_us += + ktime_to_us(ktime_sub(ktime_get(), + host->clk_scaling.start_busy)); /* Flag re-tuning needed on CRC errors */ if ((cmd->opcode != MMC_SEND_TUNING_BLOCK && @@ -324,6 +331,19 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) } mmc_host_clk_hold(host); led_trigger_event(host->led, LED_FULL); + + if (host->card && host->clk_scaling.enable) { + /* + * Check if we need to scale the clocks. Clocks + * will be scaled up immediately if necessary + * conditions are satisfied. Scaling down the + * frequency will be done after current thread + * releases host. + */ + mmc_clk_scaling(host, false); + host->clk_scaling.start_busy = ktime_get(); + } + __mmc_start_request(host, mrq); return 0; @@ -2629,6 +2649,283 @@ int mmc_hw_reset(struct mmc_host *host) } EXPORT_SYMBOL(mmc_hw_reset); +/** + * mmc_reset_clk_scale_stats() - reset clock scaling statistics + * @host: pointer to mmc host structure + */ +void mmc_reset_clk_scale_stats(struct mmc_host *host) +{ + host->clk_scaling.busy_time_us = 0; + host->clk_scaling.window_time = jiffies; +} +EXPORT_SYMBOL_GPL(mmc_reset_clk_scale_stats); + +/** + * mmc_get_max_frequency() - get max. frequency supported + * @host: pointer to mmc host structure + * + * Returns max. frequency supported by card/host. If the + * timing mode is SDR50/SDR104/HS200/DDR50 return appropriate + * max. frequency in these modes else, use the current frequency. + * Also, allow host drivers to overwrite the frequency in case + * they support "get_max_frequency" host ops. + */ +unsigned long mmc_get_max_frequency(struct mmc_host *host) +{ + unsigned long freq; + + if (host->ops && host->ops->get_max_frequency) { + freq = host->ops->get_max_frequency(host); + goto out; + } + + switch (host->ios.timing) { + case MMC_TIMING_UHS_SDR50: + freq = UHS_SDR50_MAX_DTR; + break; + case MMC_TIMING_UHS_SDR104: + freq = UHS_SDR104_MAX_DTR; + break; + case MMC_TIMING_MMC_HS200: + freq = MMC_HS200_MAX_DTR; + break; + case MMC_TIMING_UHS_DDR50: + freq = UHS_DDR50_MAX_DTR; + break; + default: + mmc_host_clk_hold(host); + freq = host->ios.clock; + mmc_host_clk_release(host); + break; + } + +out: + return freq; +} +EXPORT_SYMBOL_GPL(mmc_get_max_frequency); + +/** + * mmc_get_min_frequency() - get min. frequency supported + * @host: pointer to mmc host structure + * + * Returns min. frequency supported by card/host which doesn't impair + * performance for most usecases. If the timing mode is SDR50/SDR104/HS200 + * return 50MHz value. If timing mode is DDR50 return 25MHz so that + * throughput would be equivalent to SDR50/SDR104 in 50MHz. Also, allow + * host drivers to overwrite the frequency in case they support + * "get_min_frequency" host ops. + */ +static unsigned long mmc_get_min_frequency(struct mmc_host *host) +{ + unsigned long freq; + + if (host->ops && host->ops->get_min_frequency) { + freq = host->ops->get_min_frequency(host); + goto out; + } + + switch (host->ios.timing) { + case MMC_TIMING_UHS_SDR50: + case MMC_TIMING_UHS_SDR104: + freq = UHS_SDR25_MAX_DTR; + break; + case MMC_TIMING_MMC_HS200: + freq = MMC_HIGH_52_MAX_DTR; + break; + case MMC_TIMING_UHS_DDR50: + freq = UHS_DDR50_MAX_DTR / 2; + break; + default: + mmc_host_clk_hold(host); + freq = host->ios.clock; + mmc_host_clk_release(host); + break; + } + +out: + return freq; +} + +/* + * Scale down clocks to minimum frequency supported. + * The delayed work re-arms itself in case it cannot + * claim the host. + */ +static void mmc_clk_scale_work(struct work_struct *work) +{ + struct mmc_host *host = container_of(work, struct mmc_host, + clk_scaling.work.work); + + if (!host->card || !host->bus_ops || + !host->bus_ops->change_bus_speed || + !host->clk_scaling.enable || !host->ios.clock) + goto out; + + mmc_clk_scaling(host, true); + mmc_release_host(host); +out: + return; +} + + +/** + * mmc_clk_scaling() - clock scaling decision algorithm + * @host: pointer to mmc host structure + * @from_wq: variable that specifies the context in which + * mmc_clk_scaling() is called. + * + * Calculate load percentage based on host busy time + * and total sampling interval and decide clock scaling + * based on scale up/down thresholds. + * If load is greater than up threshold increase the + * frequency to maximum as supported by host. Else, + * if load is less than down threshold, scale down the + * frequency to minimum supported by the host. Otherwise, + * retain current frequency and do nothing. + */ +static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) +{ + int err = 0; + struct mmc_card *card = host->card; + unsigned long total_time_ms = 0; + unsigned long busy_time_ms = 0; + unsigned long freq; + unsigned int up_threshold = host->clk_scaling.up_threshold; + unsigned int down_threshold = host->clk_scaling.down_threshold; + bool queue_scale_down_work = false; + + if (!card || !host->bus_ops || !host->bus_ops->change_bus_speed) { + pr_err("%s: %s: invalid entry\n", mmc_hostname(host), __func__); + goto out; + } + + /* Check if the clocks are already gated. */ + if (!host->ios.clock) + goto out; + + if (time_is_after_jiffies(host->clk_scaling.window_time + + msecs_to_jiffies(host->clk_scaling.polling_delay_ms))) + goto out; + + /* handle time wrap */ + total_time_ms = jiffies_to_msecs((long)jiffies - + (long)host->clk_scaling.window_time); + + /* Check if we re-enter during clock switching */ + if (unlikely(host->clk_scaling.in_progress)) + goto out; + + host->clk_scaling.in_progress = true; + + busy_time_ms = host->clk_scaling.busy_time_us / USEC_PER_MSEC; + + freq = host->clk_scaling.curr_freq; + + /* + * Note that the max. and min. frequency should be based + * on the timing modes that the card and host handshake + * during initialization. + */ + if ((busy_time_ms * 100 > total_time_ms * up_threshold)) { + freq = mmc_get_max_frequency(host); + } else if ((busy_time_ms * 100 < total_time_ms * down_threshold)) { + if (!from_wq) + queue_scale_down_work = true; + freq = mmc_get_min_frequency(host); + } + + if (freq != host->clk_scaling.curr_freq) { + if (!queue_scale_down_work) { + if (!from_wq) + cancel_delayed_work_sync( + &host->clk_scaling.work); + err = host->bus_ops->change_bus_speed(host, &freq); + if (!err) + host->clk_scaling.curr_freq = freq; + else + pr_err("%s: %s: failed (%d) at freq=%lu\n", + mmc_hostname(host), __func__, err, + freq); + } else { + /* + * We hold claim host while queueing the scale down + * work, so delay atleast one timer tick to release + * host and re-claim while scaling down the clocks. + */ + queue_delayed_work(system_wq, + &host->clk_scaling.work, 1); + host->clk_scaling.in_progress = false; + goto out; + } + } + + mmc_reset_clk_scale_stats(host); + host->clk_scaling.in_progress = false; +out: + return; +} + +/** + * mmc_disable_clk_scaling() - Disable clock scaling + * @host: pointer to mmc host structure + * + * Disables clock scaling temporarily by setting enable + * property to false. To disable completely, one also + * need to set 'initialized' variable to false. + */ +void mmc_disable_clk_scaling(struct mmc_host *host) +{ + cancel_delayed_work_sync(&host->clk_scaling.work); + host->clk_scaling.enable = false; +} +EXPORT_SYMBOL_GPL(mmc_disable_clk_scaling); + +/** + * mmc_can_scale_clk() - Check if clock scaling is initialized + * @host: pointer to mmc host structure + */ +bool mmc_can_scale_clk(struct mmc_host *host) +{ + return host->clk_scaling.initialized; +} +EXPORT_SYMBOL_GPL(mmc_can_scale_clk); + +/** + * mmc_init_clk_scaling() - Initialize clock scaling + * @host: pointer to mmc host structure + * + * Initialize clock scaling for supported hosts. + * It is assumed that the caller ensure clock is + * running at maximum possible frequency before + * calling this function. + */ +void mmc_init_clk_scaling(struct mmc_host *host) +{ + if (!host->card || !(host->caps2 & MMC_CAP2_CLK_SCALE)) + return; + + INIT_DELAYED_WORK(&host->clk_scaling.work, mmc_clk_scale_work); + host->clk_scaling.curr_freq = mmc_get_max_frequency(host); + mmc_reset_clk_scale_stats(host); + host->clk_scaling.enable = true; + host->clk_scaling.initialized = true; + pr_debug("%s: clk scaling enabled\n", mmc_hostname(host)); +} +EXPORT_SYMBOL_GPL(mmc_init_clk_scaling); + +/** + * mmc_exit_clk_scaling() - Disable clock scaling + * @host: pointer to mmc host structure + * + * Disable clock scaling permanently. + */ +void mmc_exit_clk_scaling(struct mmc_host *host) +{ + cancel_delayed_work_sync(&host->clk_scaling.work); + memset(&host->clk_scaling, 0, sizeof(host->clk_scaling)); +} +EXPORT_SYMBOL_GPL(mmc_exit_clk_scaling); + static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index d57daacbfcca..9bf804ee35a2 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -92,6 +92,13 @@ void mmc_remove_card_debugfs(struct mmc_card *card); void mmc_init_context_info(struct mmc_host *host); +extern void mmc_disable_clk_scaling(struct mmc_host *host); +extern bool mmc_can_scale_clk(struct mmc_host *host); +extern void mmc_init_clk_scaling(struct mmc_host *host); +extern void mmc_exit_clk_scaling(struct mmc_host *host); +extern void mmc_reset_clk_scale_stats(struct mmc_host *host); +extern unsigned long mmc_get_max_frequency(struct mmc_host *host); + int mmc_execute_tuning(struct mmc_card *card); int mmc_hs200_to_hs400(struct mmc_card *card); int mmc_hs400_to_hs200(struct mmc_card *card); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 44560b15ce57..90c863688806 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -167,6 +167,9 @@ void mmc_host_clk_hold(struct mmc_host *host) if (host->clk_gated) { spin_unlock_irqrestore(&host->clk_lock, flags); mmc_ungate_clock(host); + + /* Reset clock scaling stats as host is out of idle */ + mmc_reset_clk_scale_stats(host); spin_lock_irqsave(&host->clk_lock, flags); pr_debug("%s: ungated MCI clock\n", mmc_hostname(host)); } @@ -697,6 +700,10 @@ int mmc_add_host(struct mmc_host *host) #endif mmc_host_clk_sysfs_init(host); + host->clk_scaling.up_threshold = 35; + host->clk_scaling.down_threshold = 5; + host->clk_scaling.polling_delay_ms = 100; + err = sysfs_create_group(&host->parent->kobj, &dev_attr_grp); if (err) pr_err("%s: failed to create sysfs group with err %d\n", diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 05b5dff1da4f..d020236477bb 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1848,6 +1848,7 @@ static void mmc_remove(struct mmc_host *host) mmc_claim_host(host); host->card = NULL; + mmc_exit_clk_scaling(host); mmc_release_host(host); } @@ -1902,6 +1903,12 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (mmc_card_suspended(host->card)) goto out; + /* + * Disable clock scaling before suspend and enable it after resume so + * as to avoid clock scaling decisions kicking in during this window. + */ + mmc_disable_clk_scaling(host); + if (mmc_card_doing_bkops(host->card)) { err = mmc_stop_bkops(host->card); if (err) @@ -1958,15 +1965,25 @@ static int _mmc_resume(struct mmc_host *host) mmc_claim_host(host); - if (!mmc_card_suspended(host->card)) + if (!mmc_card_suspended(host->card)) { + mmc_release_host(host); goto out; + } mmc_power_up(host, host->card->ocr); err = mmc_init_card(host, host->card->ocr, host->card); mmc_card_clr_suspended(host->card); -out: mmc_release_host(host); + + /* + * We have done full initialization of the card, + * reset the clk scale stats and current frequency. + */ + if (mmc_can_scale_clk(host)) + mmc_init_clk_scaling(host); + +out: return err; } @@ -2041,6 +2058,10 @@ static int mmc_runtime_resume(struct mmc_host *host) pr_err("%s: error %d doing aggressive resume\n", mmc_hostname(host), err); + /* Initialize clock scaling only for high frequency modes */ + if (mmc_card_hs200(host->card)) + mmc_init_clk_scaling(host); + return 0; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 29b3447bff9e..ccab284c3ce5 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1113,6 +1113,7 @@ static void mmc_sd_remove(struct mmc_host *host) mmc_claim_host(host); host->card = NULL; + mmc_exit_clk_scaling(host); mmc_release_host(host); } @@ -1180,6 +1181,12 @@ static int _mmc_sd_suspend(struct mmc_host *host) BUG_ON(!host); BUG_ON(!host->card); + /* + * Disable clock scaling before suspend and enable it after resume so + * as to avoid clock scaling decisions kicking in during this window. + */ + mmc_disable_clk_scaling(host); + mmc_claim_host(host); if (mmc_card_suspended(host->card)) @@ -1293,6 +1300,13 @@ static int mmc_sd_runtime_suspend(struct mmc_host *host) pr_err("%s: error %d doing aggressive suspend\n", mmc_hostname(host), err); + /* + * We have done full initialization of the card, + * reset the clk scale stats and current frequency. + */ + if (mmc_can_scale_clk(host)) + mmc_init_clk_scaling(host); + return err; } @@ -1311,6 +1325,10 @@ static int mmc_sd_runtime_resume(struct mmc_host *host) pr_err("%s: error %d doing aggressive resume\n", mmc_hostname(host), err); + /* Initialize clock scaling only for high frequency modes */ + if (mmc_card_uhs(host->card)) + mmc_init_clk_scaling(host); + return 0; } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 1f4c2cc72b8c..dd68bc9a805d 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -156,6 +156,10 @@ struct mmc_host_ops { */ int (*multi_io_quirk)(struct mmc_card *card, unsigned int direction, int blk_size); + + unsigned long (*get_max_frequency)(struct mmc_host *host); + unsigned long (*get_min_frequency)(struct mmc_host *host); + int (*notify_load)(struct mmc_host *, enum mmc_load); }; @@ -303,6 +307,7 @@ struct mmc_host { #define MMC_CAP2_SDIO_IRQ_NOTHREAD (1 << 17) #define MMC_CAP2_NO_WRITE_PROTECT (1 << 18) /* No physical write protect pin, assume that card is always read-write */ #define MMC_CAP2_PACKED_WR_CONTROL (1 << 19) /* Allow write packing control */ +#define MMC_CAP2_CLK_SCALE (1 << 20) /* Allow dynamic clk scaling */ #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ mmc_pm_flag_t pm_caps; /* supported pm features */ @@ -407,23 +412,6 @@ struct mmc_host { } embedded_sdio_data; #endif - struct { - unsigned long busy_time_us; - unsigned long window_time; - unsigned long curr_freq; - unsigned long polling_delay_ms; - unsigned int up_threshold; - unsigned int down_threshold; - ktime_t start_busy; - bool enable; - bool initialized; - bool in_progress; - /* freq. transitions are not allowed in invalid state */ - bool invalid_state; - struct delayed_work work; - enum mmc_load state; - } clk_scaling; - /* * Set to 1 to just stop the SDCLK to the card without * actually disabling the clock from it's source. @@ -441,7 +429,19 @@ struct mmc_host { } perf; bool perf_enable; #endif - + struct { + unsigned long busy_time_us; + unsigned long window_time; + unsigned long curr_freq; + unsigned long polling_delay_ms; + unsigned int up_threshold; + unsigned int down_threshold; + ktime_t start_busy; + bool enable; + bool initialized; + bool in_progress; + struct delayed_work work; + } clk_scaling; unsigned long private[0] ____cacheline_aligned; }; From f7635417938de041bf6e5a32ca342188798d3f81 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Sun, 2 Dec 2012 13:27:15 +0200 Subject: [PATCH 150/472] mmc: card: activate packing control only for eMMC4.5 cards Since the write packing is an eMMc4.5 feature, packing control should also be activated only for eMMc4.5 cards. Change-Id: If094658943a536f39afc814f6684c0dbb0806778 Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index da0c62a692a4..fd1fed3381a0 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1700,6 +1700,10 @@ static void mmc_blk_write_packing_control(struct mmc_queue *mq, if (!(host->caps2 & MMC_CAP2_PACKED_WR)) return; + /* Support for the write packing on eMMC 4.5 or later */ + if (mq->card->ext_csd.rev <= 5) + return; + /* * In case the packing control is not supported by the host, it should * not have an effect on the write packing. Therefore we have to enable From d98ae31eeefcaf2d0b7ef419f2a9d5028afb0ade Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 7 Nov 2012 21:23:14 +0530 Subject: [PATCH 151/472] mmc: core: Add sysfs entries for dynamic control of clock scaling Add sysfs attributes to allow dynamic control of clock scaling parameters. These attributes are used to enable/disable clock scaling at runtime and change the up_threshold, down_threshold, and polling_interval values. Complete documentation for these sysfs entries are provided at "Documentation/mmc/mmc-dev-attrs.txt". Change-Id: I4753c75c3b3eeea91e93bceba7af2535b793612e Signed-off-by: Sujit Reddy Thumma Signed-off-by: Subhash Jadavani --- Documentation/mmc/mmc-dev-attrs.txt | 40 ++++++- drivers/mmc/core/host.c | 164 +++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt index d143f16afe69..e9d2debb29d3 100644 --- a/Documentation/mmc/mmc-dev-attrs.txt +++ b/Documentation/mmc/mmc-dev-attrs.txt @@ -30,7 +30,7 @@ SD and MMC Device Attributes All attributes are read-only. - cid Card Identifaction Register + cid Card Identification Register csd Card Specific Data Register scr SD Card Configuration Register (SD only) date Manufacturing Date (from CID Register) @@ -99,3 +99,41 @@ This attribute appears only if CONFIG_MMC_CLKGATE is enabled. clkgate_delay Tune the clock gating delay with desired value in milliseconds. echo > /sys/class/mmc_host/mmcX/clkgate_delay + +SD/MMC/SDIO Clock Scaling Attributes +==================================== + +Read and write accesses are provided to following attributes. + + polling_interval Measured in milliseconds, this attribute + defines how often we need to check the card + usage and make decisions on frequency scaling. + + up_threshold This attribute defines what should be the + average card usage between the polling + interval for the mmc core to make a decision + on whether it should increase the frequency. + For example when it is set to '35' it means + that between the checking intervals the card + needs to be on average more than 35% in use to + scale up the frequency. The value should be + between 0 - 100 so that it can be compared + against load percentage. + + down_threshold Similar to up_threshold, but on lowering the + frequency. For example, when it is set to '2' + it means that between the checking intervals + the card needs to be on average less than 2% + in use to scale down the clocks to minimum + frequency. The value should be between 0 - 100 + so that it can be compared against load + percentage. + + enable Enable clock scaling for hosts (and cards) + that support ultrahigh speed modes + (SDR104, DDR50, HS200). + +echo > /sys/class/mmc_host/mmcX/clk_scaling/polling_interval +echo > /sys/class/mmc_host/mmcX/clk_scaling/up_threshold +echo > /sys/class/mmc_host/mmcX/clk_scaling/down_threshold +echo > /sys/class/mmc_host/mmcX/clk_scaling/enable diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 90c863688806..72eda77a6f3b 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -4,6 +4,7 @@ * Copyright (C) 2003 Russell King, All Rights Reserved. * Copyright (C) 2007-2008 Pierre Ossman * Copyright (C) 2010 Linus Walleij + * Copyright (c) 2012, The Linux Foundation. All rights reserved. * * 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 @@ -613,6 +614,162 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev) } EXPORT_SYMBOL(mmc_alloc_host); + +static ssize_t show_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + + if (!host) + return -EINVAL; + + return snprintf(buf, PAGE_SIZE, "%d\n", mmc_can_scale_clk(host)); +} + +static ssize_t store_enable(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 value, freq; + int retval = -EINVAL; + + if (!host || !host->card || kstrtoul(buf, 0, &value)) + goto err; + + if (value && !mmc_can_scale_clk(host)) { + if (mmc_card_hs200(host->card) || + mmc_card_uhs(host->card)) { + host->caps2 |= MMC_CAP2_CLK_SCALE; + mmc_init_clk_scaling(host); + } + + if (!mmc_can_scale_clk(host)) { + host->caps2 &= ~MMC_CAP2_CLK_SCALE; + goto err; + } + } else if (!value && mmc_can_scale_clk(host)) { + host->caps2 &= ~MMC_CAP2_CLK_SCALE; + mmc_disable_clk_scaling(host); + + /* Set to max. frequency, since we are disabling */ + if (host->bus_ops && host->bus_ops->change_bus_speed) { + freq = mmc_get_max_frequency(host); + if (host->bus_ops->change_bus_speed(host, &freq)) + goto err; + } + host->clk_scaling.initialized = false; + } + return count; +err: + return retval; +} + +static ssize_t show_up_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + + if (!host) + return -EINVAL; + + return snprintf(buf, PAGE_SIZE, "%d\n", host->clk_scaling.up_threshold); +} + +#define MAX_PERCENTAGE 100 +static ssize_t store_up_threshold(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 value; + + if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE)) + return -EINVAL; + + host->clk_scaling.up_threshold = value; + + pr_debug("%s: clkscale_up_thresh set to %lu\n", + mmc_hostname(host), value); + return count; +} + +static ssize_t show_down_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + + if (!host) + return -EINVAL; + + return snprintf(buf, PAGE_SIZE, "%d\n", + host->clk_scaling.down_threshold); +} + +static ssize_t store_down_threshold(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 value; + + if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE)) + return -EINVAL; + + host->clk_scaling.down_threshold = value; + + pr_debug("%s: clkscale_down_thresh set to %lu\n", + mmc_hostname(host), value); + return count; +} + +static ssize_t show_polling(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_host *host = cls_dev_to_mmc_host(dev); + + if (!host) + return -EINVAL; + + return snprintf(buf, PAGE_SIZE, "%lu milliseconds\n", + host->clk_scaling.polling_delay_ms); +} + +static ssize_t store_polling(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 value; + + if (!host || kstrtoul(buf, 0, &value)) + return -EINVAL; + + host->clk_scaling.polling_delay_ms = value; + + pr_debug("%s: clkscale_polling_delay_ms set to %lu\n", + mmc_hostname(host), value); + return count; +} + +DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, + show_enable, store_enable); +DEVICE_ATTR(polling_interval, S_IRUGO | S_IWUSR, + show_polling, store_polling); +DEVICE_ATTR(up_threshold, S_IRUGO | S_IWUSR, + show_up_threshold, store_up_threshold); +DEVICE_ATTR(down_threshold, S_IRUGO | S_IWUSR, + show_down_threshold, store_down_threshold); + +static struct attribute *clk_scaling_attrs[] = { + &dev_attr_enable.attr, + &dev_attr_up_threshold.attr, + &dev_attr_down_threshold.attr, + &dev_attr_polling_interval.attr, + NULL, +}; + +static struct attribute_group clk_scaling_attr_grp = { + .name = "clk_scaling", + .attrs = clk_scaling_attrs, +}; + #ifdef CONFIG_MMC_PERF_PROFILING static ssize_t show_perf(struct device *dev, struct device_attribute *attr, char *buf) @@ -704,6 +861,11 @@ int mmc_add_host(struct mmc_host *host) host->clk_scaling.down_threshold = 5; host->clk_scaling.polling_delay_ms = 100; + err = sysfs_create_group(&host->class_dev.kobj, &clk_scaling_attr_grp); + if (err) + pr_err("%s: failed to create clk scale sysfs group with err %d\n", + __func__, err); + err = sysfs_create_group(&host->parent->kobj, &dev_attr_grp); if (err) pr_err("%s: failed to create sysfs group with err %d\n", @@ -737,7 +899,7 @@ void mmc_remove_host(struct mmc_host *host) mmc_remove_host_debugfs(host); #endif sysfs_remove_group(&host->parent->kobj, &dev_attr_grp); - + sysfs_remove_group(&host->class_dev.kobj, &clk_scaling_attr_grp); device_del(&host->class_dev); From 7a2c9513a1690947f568453f6977a96b62c8c2e6 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Wed, 12 Dec 2012 09:16:58 +0530 Subject: [PATCH 152/472] mmc: core: claim mmc host while enabling clock scaling from userspace A race condition can occur while enabling clock scaling and removal of the card. If the card is removed just after the host->card check is successful then there could be NULL pointer dereference later. Claim mmc host before card availability check to avoid this race condition. Change-Id: I6339cad5857732ec6b66280940cd6b8b7ed30614 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/host.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 72eda77a6f3b..b3363212c92e 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -633,7 +633,11 @@ static ssize_t store_enable(struct device *dev, unsigned long value, freq; int retval = -EINVAL; - if (!host || !host->card || kstrtoul(buf, 0, &value)) + if (!host) + goto out; + + mmc_claim_host(host); + if (!host->card || kstrtoul(buf, 0, &value)) goto err; if (value && !mmc_can_scale_clk(host)) { @@ -659,8 +663,10 @@ static ssize_t store_enable(struct device *dev, } host->clk_scaling.initialized = false; } - return count; + retval = count; err: + mmc_release_host(host); +out: return retval; } From 62649ebdb6dd7a6e4b558dcfd23b32212ff685e7 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 4 Dec 2014 15:07:32 +0200 Subject: [PATCH 153/472] mmc: core: interrupt Background Operations if it takes too long Currently we have 4 mins timeout for the blocking BKOPs to complete but we have seen instances where card is surprisingly taking even longer time to complete background operations. If card doesn't complete the BKOPs within specified timeout, we will send the HPI command to interrupt the ongoing BKOPs. Change-Id: I5df81bdfd9b19bee30a394ee0ff4390b292691d0 Signed-off-by: Subhash Jadavani [merez@codeaurora.org: fix conflicts due to changes in 3.14] Signed-off-by: Maya Erez --- drivers/mmc/core/core.c | 8 ++++---- drivers/mmc/core/mmc_ops.c | 2 ++ include/linux/mmc/core.h | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index ae96334b9c8f..843edbed3ac9 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -546,15 +546,15 @@ static void mmc_wait_for_req_done(struct mmc_host *host, cmd = mrq->cmd; /* - * If host has timed out waiting for the sanitize + * If host has timed out waiting for the sanitize/bkops * to complete, card might be still in programming state * so let's try to bring the card out of programming * state. */ - if (cmd->sanitize_busy && cmd->error == -ETIMEDOUT) { + if ((cmd->bkops_busy || cmd->sanitize_busy) && cmd->error == -ETIMEDOUT) { if (!mmc_interrupt_hpi(host->card)) { - pr_warn("%s: %s: Interrupted sanitize\n", - mmc_hostname(host), __func__); + pr_warn("%s: %s: Interrupted sanitize/bkops\n", + mmc_hostname(host), __func__); cmd->error = 0; break; } else { diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 71686caa0f6a..91248c14af01 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -521,6 +521,8 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, if (index == EXT_CSD_SANITIZE_START) cmd.sanitize_busy = true; + else if (index == EXT_CSD_BKOPS_START) + cmd.bkops_busy = true; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); if (err) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 0c2f98ba1d84..ba296ab25899 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -67,6 +67,8 @@ struct mmc_command { unsigned int busy_timeout; /* busy detect timeout in ms */ /* Set this flag only for blocking sanitize request */ bool sanitize_busy; + /* Set this flag only for blocking bkops request */ + bool bkops_busy; struct mmc_data *data; /* data segment associated with cmd */ struct mmc_request *mrq; /* associated request */ From 23bf7111217b01b45af465f2cb4cf4bf37c0842b Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 4 Dec 2014 15:13:59 +0200 Subject: [PATCH 154/472] mmc: do not pack random requests Packed commands causes higher latency since the completion of each request is sent to the upper layer upon completion of the complete packed request. The benefit from this feature is card dependent. Some of the card vendors do not have any benefit from using packed commands for random requests. In case there is no benefit in random requests packing, it is better to disable the packing to prevent this high latency. This patch also add the new stop packing reason to the write packing statistics. Change-Id: I141887dcef2ceee14848634cc27c3c85f8edc7a5 Signed-off-by: Maya Erez [merez@codeaurora.org: fix conflicts due to removal of BKOPS] Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 72 ++++++++++++++++++++++++++++++++++++++ drivers/mmc/card/queue.h | 1 + drivers/mmc/core/debugfs.c | 7 ++++ include/linux/mmc/card.h | 1 + 4 files changed, 81 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index fd1fed3381a0..50b37f38e6e8 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -130,6 +130,7 @@ struct mmc_blk_data { struct device_attribute force_ro; struct device_attribute power_ro_lock; struct device_attribute num_wr_reqs_to_start_packing; + struct device_attribute no_pack_for_random; int area_type; }; @@ -348,6 +349,55 @@ exit: return ret; } +static ssize_t +no_pack_for_random_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + int ret; + + ret = snprintf(buf, PAGE_SIZE, "%d\n", md->queue.no_pack_for_random); + + mmc_blk_put(md); + return ret; +} + +static ssize_t +no_pack_for_random_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int value; + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + struct mmc_card *card = md->queue.card; + int ret = count; + + if (!card) { + ret = -EINVAL; + goto exit; + } + + sscanf(buf, "%d", &value); + + if (value < 0) { + pr_err("%s: value %d is not valid. old value remains = %d", + mmc_hostname(card->host), value, + md->queue.no_pack_for_random); + ret = -EINVAL; + goto exit; + } + + md->queue.no_pack_for_random = (value > 0) ? true : false; + + pr_debug("%s: no_pack_for_random: new value = %d", + mmc_hostname(card->host), + md->queue.no_pack_for_random); + +exit: + mmc_blk_put(md); + return ret; +} + static int mmc_blk_open(struct block_device *bdev, fmode_t mode) { struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk); @@ -1870,6 +1920,15 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) break; } + if (mq->no_pack_for_random) { + if ((blk_rq_pos(cur) + blk_rq_sectors(cur)) != + blk_rq_pos(next)) { + MMC_BLK_UPDATE_STOP_REASON(stats, RANDOM); + put_back = 1; + break; + } + } + if (rq_data_dir(next) == WRITE) mq->num_of_potential_packed_wr_reqs++; list_add_tail(&next->queuelist, &mqrq->packed->list); @@ -2653,8 +2712,21 @@ static int mmc_add_disk(struct mmc_blk_data *md) if (ret) goto num_wr_reqs_to_start_packing_fail; + md->no_pack_for_random.show = no_pack_for_random_show; + md->no_pack_for_random.store = no_pack_for_random_store; + sysfs_attr_init(&md->no_pack_for_random.attr); + md->no_pack_for_random.attr.name = "no_pack_for_random"; + md->no_pack_for_random.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(disk_to_dev(md->disk), + &md->no_pack_for_random); + if (ret) + goto no_pack_for_random_fails; + return ret; +no_pack_for_random_fails: + device_remove_file(disk_to_dev(md->disk), + &md->num_wr_reqs_to_start_packing); num_wr_reqs_to_start_packing_fail: device_remove_file(disk_to_dev(md->disk), &md->power_ro_lock); power_ro_lock_fail: diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 656de4677cbd..900137715ceb 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -61,6 +61,7 @@ struct mmc_queue { bool wr_packing_enabled; int num_of_potential_packed_wr_reqs; int num_wr_reqs_to_start_packing; + bool no_pack_for_random; int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); }; diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 46bb040cd335..98930066d952 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -524,6 +524,13 @@ static ssize_t mmc_wr_pack_stats_read(struct file *filp, char __user *ubuf, pack_stats->pack_stop_reason[LARGE_SEC_ALIGN]); strlcat(ubuf, temp_buf, cnt); } + if (pack_stats->pack_stop_reason[RANDOM]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: random request\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[RANDOM]); + strlcat(ubuf, temp_buf, cnt); + } spin_unlock(&pack_stats->lock); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 8c70e624325b..01fe9263d2f2 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -227,6 +227,7 @@ enum mmc_packed_stop_reasons { REL_WRITE, THRESHOLD, LARGE_SEC_ALIGN, + RANDOM, MAX_REASONS, }; From 18941a3b87ccea2595ad5638c65fe7ba69f86eef Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 4 Dec 2014 15:16:17 +0200 Subject: [PATCH 155/472] mmc: block: reduce the block timeout to 30 secs After completion of block write request, MMC block driver waits for the card to come out of the programming state. If card doesn't come out of the programming state in 10 mins, it would be considered as error condition and card would reinitialized. But this 10 mins is just too huge and if card is continuously stuck in programming state for 10 mins, mmcqd thread would take increase the CPU load significantly as we are continuously polling for the card status (by sending status commands). This patch reduces this timeout from 10 mins to 30 secs which is quite reasonable for all well-behaved cards. Change-Id: I4e8eaf29c836a81419220f312ee867b0dd5cccc7 Signed-off-by: Subhash Jadavani [merez@codeaurora.org: fix trivial conflicts] Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 50b37f38e6e8..9559ddfef99a 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -61,7 +61,7 @@ MODULE_ALIAS("mmc:block"); #define INAND_CMD38_ARG_SECERASE 0x80 #define INAND_CMD38_ARG_SECTRIM1 0x81 #define INAND_CMD38_ARG_SECTRIM2 0x88 -#define MMC_BLK_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */ +#define MMC_BLK_TIMEOUT_MS (30 * 1000) /* 30 sec timeout */ #define MMC_SANITIZE_REQ_TIMEOUT 240000 #define MMC_EXTRACT_INDEX_FROM_ARG(x) ((x & 0x00FF0000) >> 16) From 26a145722e298ecfe2ac45f99baf3d6a08b74388 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Thu, 4 Dec 2014 15:20:57 +0200 Subject: [PATCH 156/472] mmc: card: Fix RPMB IOCTL to handle all cards The eMMC 4.5 spec for RPMB accesses is not very clear on whether user parition accesses can be allowed in the middle of RPMB accesses. Due to this ambiguity, it turns out this is implementation defined and certain cards support it while others do not. In order to allow this feature to function across a wide variety of cards, this patch takes the pessimistic approach and ensures that any RPMB access is completed before user partition can be accessed. Change-Id: I77959f462c874771a0a854d9a2bc48df446eff56 Signed-off-by: Krishna Konda Signed-off-by: Oluwafemi Adeyemi Signed-off-by: Venkat Gopalakrishnan [merez@codeaurora: fix conflicts due to changes in 3.14] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed merge conflicts and fixed compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 186 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 173 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 9559ddfef99a..d0dcbf6366c4 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -579,15 +579,10 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, struct mmc_request mrq = {NULL}; struct scatterlist sg; int err; - int is_rpmb = false; - u32 status = 0; if (!card || !md || !idata) return -EINVAL; - if (md->area_type & MMC_BLK_DATA_AREA_RPMB) - is_rpmb = true; - cmd.opcode = idata->ic.opcode; cmd.arg = idata->ic.arg; cmd.flags = idata->ic.flags; @@ -640,13 +635,6 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, return err; } - if (is_rpmb) { - err = mmc_set_blockcount(card, data.blocks, - idata->ic.write_flag & (1 << 31)); - if (err) - return err; - } - if ((MMC_EXTRACT_INDEX_FROM_ARG(cmd.arg) == EXT_CSD_SANITIZE_START) && (cmd.opcode == MMC_SWITCH)) { err = ioctl_do_sanitize(card); @@ -680,7 +668,164 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, memcpy(&(idata->ic.response), cmd.resp, sizeof(cmd.resp)); - if (is_rpmb) { + return err; +} + +struct mmc_blk_ioc_rpmb_data { + struct mmc_blk_ioc_data *data[MMC_IOC_MAX_RPMB_CMD]; +}; + +static struct mmc_blk_ioc_rpmb_data *mmc_blk_ioctl_rpmb_copy_from_user( + struct mmc_ioc_rpmb __user *user) +{ + struct mmc_blk_ioc_rpmb_data *idata; + int err, i; + + idata = kzalloc(sizeof(*idata), GFP_KERNEL); + if (!idata) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < MMC_IOC_MAX_RPMB_CMD; i++) { + idata->data[i] = mmc_blk_ioctl_copy_from_user(&(user->cmds[i])); + if (IS_ERR(idata->data[i])) { + err = PTR_ERR(idata->data[i]); + goto copy_err; + } + } + + return idata; + +copy_err: + while (--i >= 0) { + kfree(idata->data[i]->buf); + kfree(idata->data[i]); + } + kfree(idata); +out: + return ERR_PTR(err); +} + +static int mmc_blk_ioctl_rpmb_cmd(struct block_device *bdev, + struct mmc_ioc_rpmb __user *ic_ptr) +{ + struct mmc_blk_ioc_rpmb_data *idata; + struct mmc_blk_data *md; + struct mmc_card *card; + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + struct mmc_request mrq = {NULL}; + struct scatterlist sg; + int err = 0, i = 0; + u32 status = 0; + + /* The caller must have CAP_SYS_RAWIO */ + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + md = mmc_blk_get(bdev->bd_disk); + /* make sure this is a rpmb partition */ + if ((!md) || (!(md->area_type & MMC_BLK_DATA_AREA_RPMB))) { + err = -EINVAL; + goto cmd_done; + } + + idata = mmc_blk_ioctl_rpmb_copy_from_user(ic_ptr); + if (IS_ERR(idata)) { + err = PTR_ERR(idata); + goto cmd_done; + } + + card = md->queue.card; + if (IS_ERR(card)) { + err = PTR_ERR(card); + goto idata_free; + } + + mmc_claim_host(card->host); + + err = mmc_blk_part_switch(card, md); + if (err) + goto cmd_rel_host; + + for (i = 0; i < MMC_IOC_MAX_RPMB_CMD; i++) { + struct mmc_blk_ioc_data *curr_data; + struct mmc_ioc_cmd *curr_cmd; + + curr_data = idata->data[i]; + curr_cmd = &curr_data->ic; + if (!curr_cmd->opcode) + break; + + cmd.opcode = curr_cmd->opcode; + cmd.arg = curr_cmd->arg; + cmd.flags = curr_cmd->flags; + + if (curr_data->buf_bytes) { + data.sg = &sg; + data.sg_len = 1; + data.blksz = curr_cmd->blksz; + data.blocks = curr_cmd->blocks; + + sg_init_one(data.sg, curr_data->buf, + curr_data->buf_bytes); + + if (curr_cmd->write_flag) + data.flags = MMC_DATA_WRITE; + else + data.flags = MMC_DATA_READ; + + /* data.flags must already be set before doing this. */ + mmc_set_data_timeout(&data, card); + + /* + * Allow overriding the timeout_ns for empirical tuning. + */ + if (curr_cmd->data_timeout_ns) + data.timeout_ns = curr_cmd->data_timeout_ns; + + mrq.data = &data; + } + + mrq.cmd = &cmd; + + err = mmc_set_blockcount(card, data.blocks, + curr_cmd->write_flag & (1 << 31)); + if (err) + goto cmd_rel_host; + + mmc_wait_for_req(card->host, &mrq); + + if (cmd.error) { + dev_err(mmc_dev(card->host), "%s: cmd error %d\n", + __func__, cmd.error); + err = cmd.error; + goto cmd_rel_host; + } + if (data.error) { + dev_err(mmc_dev(card->host), "%s: data error %d\n", + __func__, data.error); + err = data.error; + goto cmd_rel_host; + } + + if (copy_to_user(&(ic_ptr->cmds[i].response), cmd.resp, + sizeof(cmd.resp))) { + err = -EFAULT; + goto cmd_rel_host; + } + + if (!curr_cmd->write_flag) { + if (copy_to_user((void __user *)(unsigned long) + curr_cmd->data_ptr, + curr_data->buf, + curr_data->buf_bytes)) { + err = -EFAULT; + goto cmd_rel_host; + } + } + /* * Ensure RPMB command has completed by polling CMD13 * "Send Status". @@ -692,6 +837,18 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, __func__, status, err); } +cmd_rel_host: + mmc_put_card(card); + +idata_free: + for (i = 0; i < MMC_IOC_MAX_RPMB_CMD; i++) { + kfree(idata->data[i]->buf); + kfree(idata->data[i]); + } + kfree(idata); + +cmd_done: + mmc_blk_put(md); return err; } @@ -812,6 +969,9 @@ static int mmc_blk_ioctl(struct block_device *bdev, fmode_t mode, case MMC_IOC_CMD: return mmc_blk_ioctl_cmd(bdev, (struct mmc_ioc_cmd __user *)arg); + case MMC_IOC_RPMB_CMD: + return mmc_blk_ioctl_rpmb_cmd(bdev, + (struct mmc_ioc_rpmb __user *)arg); case MMC_IOC_MULTI_CMD: return mmc_blk_ioctl_multi_cmd(bdev, (struct mmc_ioc_multi_cmd __user *)arg); From 93896acd78892518724a2a4f2342bc85da1d74f2 Mon Sep 17 00:00:00 2001 From: Oluwafemi Adeyemi Date: Thu, 3 Jan 2013 11:32:53 -0800 Subject: [PATCH 157/472] mmc: Keep track of current hard partition eMMC may have several hard partitions such as BOOT and RPMB in addition to the main USER partition. On resume(if card is reinitialized), the current hard partition is always switched to the default USER partition. But this switch to default partition is not propagated to the MMC block driver, which may have set the hard partition to any partition(BOOT/RPMB) other than USER before suspend. After resume, the MMC block driver still assumes it is accessing the previously set partition(BOOT/RPMB), and instead overwrites the USER partition(the current selected partition on the eMMC device). To prevent this, make MMC block driver aware of the partition switches done as part of card reinitialization. CRs-Fixed: 439313 Change-Id: I3e959101a1c56c1e6631da3d660f4b914e100503 Signed-off-by: Oluwafemi Adeyemi Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 4 +++- drivers/mmc/core/mmc.c | 2 ++ include/linux/mmc/card.h | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index d0dcbf6366c4..9faf3284e322 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1005,7 +1005,8 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, int ret; struct mmc_blk_data *main_md = dev_get_drvdata(&card->dev); - if (main_md->part_curr == md->part_type) + if ((main_md->part_curr == md->part_type) && + (card->part_curr == md->part_type)) return 0; if (mmc_card_mmc(card)) { @@ -1021,6 +1022,7 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, return ret; card->ext_csd.part_config = part_config; + card->part_curr = md->part_type; } main_md->part_curr = md->part_type; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index d020236477bb..8f47853de0ae 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1610,6 +1610,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, card->ext_csd.part_time); if (err && err != -EBADMSG) goto free_card; + card->part_curr = card->ext_csd.part_config & + EXT_CSD_PART_CONFIG_ACC_MASK; } /* diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 01fe9263d2f2..20630f657db5 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -337,6 +337,7 @@ struct mmc_card { struct dentry *debugfs_root; struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ unsigned int nr_parts; + unsigned int part_curr; struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ }; From 0b0df0844f6429385cfb35d8a3237cc7d7eae24b Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 10 Jan 2013 23:35:42 +0200 Subject: [PATCH 158/472] mmc card: Initialize the packing info before running the test We need to do all the packing related initialization when running the test instead of in the test preparation to prevent a race condition between FS requests and the test requests CRs-Fixed: 430138 Change-Id: I0bc96d9faa823f3574623edd7948f141f0d40b79 Signed-off-by: Maya Erez --- drivers/mmc/card/mmc_block_test.c | 48 +++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/card/mmc_block_test.c b/drivers/mmc/card/mmc_block_test.c index f7cd8012cfe5..967affa11d9e 100644 --- a/drivers/mmc/card/mmc_block_test.c +++ b/drivers/mmc/card/mmc_block_test.c @@ -871,8 +871,6 @@ static int prepare_packed_requests(struct test_data *td, int is_err_expected, __func__, mbtd->random_test_seed); } - mmc_blk_init_packed_statistics(mq->card); - ret = prepare_request_add_write_reqs(td, num_requests, is_err_expected, is_random); if (ret) @@ -968,8 +966,6 @@ static int prepare_packed_control_tests_requests(struct test_data *td, __func__, mbtd->random_test_seed); } - mmc_blk_init_packed_statistics(mq->card); - if (td->test_info.testcase == TEST_PACK_MIX_NO_PACKED_PACKED_NO_PACKED) { temp_num_req = num_requests; @@ -1124,8 +1120,6 @@ static int prepare_partial_followed_by_abort(struct test_data *td, max_packed_reqs = mq->card->ext_csd.max_packed_writes; - mmc_blk_init_packed_statistics(mq->card); - for (i = 1; i <= num_requests; i++) { if (i > (num_requests / 2)) is_err_expected = 1; @@ -1350,6 +1344,44 @@ static int prepare_test(struct test_data *td) return ret; } +static int run_packed_test(struct test_data *td) +{ + struct mmc_queue *mq; + struct request_queue *req_q; + + if (!td) { + pr_err("%s: NULL td", __func__); + return -EINVAL; + } + + req_q = td->req_q; + + if (!req_q) { + pr_err("%s: NULL request queue", __func__); + return -EINVAL; + } + + mq = req_q->queuedata; + if (!mq) { + test_pr_err("%s: NULL mq", __func__); + return -EINVAL; + } + mmc_blk_init_packed_statistics(mq->card); + + if (td->test_info.testcase != TEST_PACK_MIX_PACKED_NO_PACKED_PACKED) { + /* + * Verify that the packing is disabled before starting the + * test + */ + mq->wr_packing_enabled = false; + mq->num_of_potential_packed_wr_reqs = 0; + } + + __blk_run_queue(td->req_q); + + return 0; +} + /* * An implementation for the post_test_fn in the test_info data structure. * In our case we just reset the function pointers in the mmc_queue in order for @@ -1479,6 +1511,7 @@ static ssize_t send_write_packing_test_write(struct file *file, mbtd->test_info.data = mbtd; mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.run_test_fn = run_packed_test; mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; mbtd->test_info.get_test_case_str_fn = get_test_case_str; mbtd->test_info.post_test_fn = post_test; @@ -1577,6 +1610,7 @@ static ssize_t err_check_test_write(struct file *file, mbtd->test_info.data = mbtd; mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.run_test_fn = run_packed_test; mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; mbtd->test_info.get_test_case_str_fn = get_test_case_str; mbtd->test_info.post_test_fn = post_test; @@ -1676,6 +1710,7 @@ static ssize_t send_invalid_packed_test_write(struct file *file, mbtd->test_info.data = mbtd; mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.run_test_fn = run_packed_test; mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; mbtd->test_info.get_test_case_str_fn = get_test_case_str; mbtd->test_info.post_test_fn = post_test; @@ -1788,6 +1823,7 @@ static ssize_t write_packing_control_test_write(struct file *file, mbtd->test_info.data = mbtd; mbtd->test_info.prepare_test_fn = prepare_test; + mbtd->test_info.run_test_fn = run_packed_test; mbtd->test_info.check_test_result_fn = check_wr_packing_statistics; mbtd->test_info.get_test_case_str_fn = get_test_case_str; From fec8ffcb2b32902a07fb298453f254bebdc503d0 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Fri, 1 Feb 2013 17:14:33 +0530 Subject: [PATCH 159/472] mmc: core: run clock scaling only in valid card state Clock scaling requires the tuning and if the tuning command is sent to the card in invalid state, tuning procedure will fail. With urgent request mechanism implemented, there is a chance that clock scaling invoked when card is not in a proper state to receive the tuning command. This change checks the card state (by sending the status command) before invoking the clock scaling request. Change-Id: Icb71a8e74a9afdc70380a5901cd3d28931959e9c Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 843edbed3ac9..2e94974d4d14 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2767,6 +2767,30 @@ out: return; } +static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + u32 status; + bool ret = false; + + if (!card) + goto out; + + if (mmc_send_status(card, &status)) { + pr_err("%s: Get card status fail\n", mmc_hostname(card->host)); + goto out; + } + + switch (R1_CURRENT_STATE(status)) { + case R1_STATE_TRAN: + ret = true; + break; + default: + break; + } +out: + return ret; +} /** * mmc_clk_scaling() - clock scaling decision algorithm @@ -2839,6 +2863,10 @@ static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) if (!from_wq) cancel_delayed_work_sync( &host->clk_scaling.work); + + if (!mmc_is_vaild_state_for_clk_scaling(host)) + goto bypass_scaling; + err = host->bus_ops->change_bus_speed(host, &freq); if (!err) host->clk_scaling.curr_freq = freq; @@ -2860,6 +2888,7 @@ static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) } mmc_reset_clk_scale_stats(host); +bypass_scaling: host->clk_scaling.in_progress = false; out: return; From aa4228ea9d4dcf1eb307c4f0b9c7732fdaf315ee Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 5 Feb 2013 15:45:53 +0200 Subject: [PATCH 160/472] mmc: block: add mmc_blk_disable_wr_packing() disables packing mode The function disables packing mode. Right now the only reason is change in data direction. Change-Id: I0f4edba3da93fde28cf47ac95754a95e411fa2af Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/block.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 9faf3284e322..dd45524b8813 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1903,6 +1903,20 @@ static inline u8 mmc_calc_packed_hdr_segs(struct request_queue *q, return nr_segs; } +/** + * mmc_blk_disable_wr_packing() - disables packing mode + * @mq: MMC queue. + * + */ +void mmc_blk_disable_wr_packing(struct mmc_queue *mq) +{ + if (mq) { + mq->wr_packing_enabled = false; + mq->num_of_potential_packed_wr_reqs = 0; + } +} +EXPORT_SYMBOL(mmc_blk_disable_wr_packing); + static void mmc_blk_write_packing_control(struct mmc_queue *mq, struct request *req) { @@ -1937,8 +1951,7 @@ static void mmc_blk_write_packing_control(struct mmc_queue *mq, data_dir = rq_data_dir(req); if (data_dir == READ) { - mq->num_of_potential_packed_wr_reqs = 0; - mq->wr_packing_enabled = false; + mmc_blk_disable_wr_packing(mq); return; } else if (data_dir == WRITE) { mq->num_of_potential_packed_wr_reqs++; From 8cd10b13a9bd1d815f69a4bd1a49d11972091a0d Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 5 Feb 2013 16:26:19 +0200 Subject: [PATCH 161/472] mmc: block: do not pack REQ_FUA request This change will prevent packing of a request marked with REQ_FUA flag, because it has the same semantics as REQ_FLUSH. Also packing statistics are updated with FUA stop reason. Change-Id: Iaad37044ec43f93e898ed0c011b0bce7b91ae13d Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/block.c | 8 ++++++++ drivers/mmc/core/debugfs.c | 7 +++++++ include/linux/mmc/card.h | 1 + 3 files changed, 16 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index dd45524b8813..982264d2db87 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2027,6 +2027,9 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) !IS_ALIGNED(blk_rq_sectors(cur), 8)) goto no_packed; + if (cur->cmd_flags & REQ_FUA) + goto no_packed; + mmc_blk_clear_packed(mqrq); max_blk_count = min(card->host->max_blk_count, @@ -2071,6 +2074,11 @@ static u8 mmc_blk_prep_packed_list(struct mmc_queue *mq, struct request *req) break; } + if (next->cmd_flags & REQ_FUA) { + MMC_BLK_UPDATE_STOP_REASON(stats, FUA); + break; + } + if (rq_data_dir(cur) != rq_data_dir(next)) { MMC_BLK_UPDATE_STOP_REASON(stats, WRONG_DATA_DIR); break; diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 98930066d952..734d2cce0a56 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -531,6 +531,13 @@ static ssize_t mmc_wr_pack_stats_read(struct file *filp, char __user *ubuf, pack_stats->pack_stop_reason[RANDOM]); strlcat(ubuf, temp_buf, cnt); } + if (pack_stats->pack_stop_reason[FUA]) { + snprintf(temp_buf, TEMP_BUF_SIZE, + "%s: %d times: fua request\n", + mmc_hostname(card->host), + pack_stats->pack_stop_reason[FUA]); + strlcat(ubuf, temp_buf, cnt); + } spin_unlock(&pack_stats->lock); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 20630f657db5..04a6c02d7b32 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -228,6 +228,7 @@ enum mmc_packed_stop_reasons { THRESHOLD, LARGE_SEC_ALIGN, RANDOM, + FUA, MAX_REASONS, }; From 6bfd64e43fc7c890c052994f217000dccf45e7c3 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Tue, 29 Jan 2013 10:32:49 +0530 Subject: [PATCH 162/472] mmc: sdio: Update the argument for data copy Incorrect arguments were passed to strlcpy() which causes Manufacturer information to be copied partially in the SDIO core layer code. Use appropriate arguments to copy the string value properly. CRs-Fixed: 434831 Change-Id: I4a70bfc8dff31b216a6097c08f824a1f1b002d5d Signed-off-by: Pratibhasagar V --- drivers/mmc/core/sdio_cis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 76a4508126dd..4a8508ec8ffd 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -55,7 +55,7 @@ static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, for (i = 0; i < nr_strings; i++) { buffer[i] = string; - strlcpy(string, buf, sizeof(string)); + strlcpy(string, buf, strlen(buf)); string += strlen(string) + 1; buf += strlen(buf) + 1; } From 34ca0afe98ef37108154d0d428461665006c03c8 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 14 Mar 2013 13:44:02 +0530 Subject: [PATCH 163/472] mmc: host: Fix driver level perf measurement Add the "perf" sysfs node to mmc_host class dev instead of adding to it's parent platform device inorder to make it compatible for all platform drivers such as SDCC and SDHC. Change-Id: Ie830ac218b242be7da63ef3b8082404a8f2f20f5 Signed-off-by: Sahitya Tummala --- drivers/mmc/core/host.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b3363212c92e..99b558762450 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -780,7 +780,7 @@ static struct attribute_group clk_scaling_attr_grp = { static ssize_t show_perf(struct device *dev, struct device_attribute *attr, char *buf) { - struct mmc_host *host = dev_get_drvdata(dev); + struct mmc_host *host = cls_dev_to_mmc_host(dev); int64_t rtime_drv, wtime_drv; unsigned long rbytes_drv, wbytes_drv; @@ -806,8 +806,8 @@ static ssize_t set_perf(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct mmc_host *host = cls_dev_to_mmc_host(dev); int64_t value; - struct mmc_host *host = dev_get_drvdata(dev); sscanf(buf, "%lld", &value); spin_lock(&host->lock); @@ -872,7 +872,7 @@ int mmc_add_host(struct mmc_host *host) pr_err("%s: failed to create clk scale sysfs group with err %d\n", __func__, err); - err = sysfs_create_group(&host->parent->kobj, &dev_attr_grp); + err = sysfs_create_group(&host->class_dev.kobj, &dev_attr_grp); if (err) pr_err("%s: failed to create sysfs group with err %d\n", __func__, err); From 553d6cb4e2604798725cc5fb6960d9b69ac0365f Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 2 Apr 2013 16:41:04 +0530 Subject: [PATCH 164/472] mmc: core: Bypass clock scaling while accessing RPMB partition According to eMMC specification, only commands of classes 0, 2, 4 are admitted while accessing RPMB partition. Still usage of any other command other than CMD0, CMD6, CMD8, CMD12, CMD13, CMD15, CMD18, CMD23, CMD25 is illegal. If the MMC clock scaling algorithm decides to switch the clocks while accessing RPMB partition it might need to send tuning command (CMD21) which is illegal. Since RPMB accesses are short and doesn't depend on throughput bypass clock scaling while the current partion mode is RPMB. The clock scaling statistics still take into account the duration of access and hence able to respond quickly on the transfers made after partition switch. Change-Id: I422f2e6acb33ab97105944e3f7f90c3afb37ef47 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/core.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 2e94974d4d14..150dbb2a7d8a 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2773,7 +2773,13 @@ static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) u32 status; bool ret = false; - if (!card) + /* + * If the current partition type is RPMB, clock switching may not + * work properly as sending tuning command (CMD21) is illegal in + * this mode. + */ + if (!card || (mmc_card_mmc(card) && + card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB)) goto out; if (mmc_send_status(card, &status)) { From b4b98fb480f72639d3a4166cdb2f1015b4c331f9 Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Thu, 14 Feb 2013 08:13:52 +0530 Subject: [PATCH 165/472] mmc: core: Log MMC clock frequency transitions Use kernel's ftrace support to capture MMC clock frequency transitions which can be useful for debugging issues related to power consumption. Usage: mount -t debugfs none /sys/kernel/debug echo 1 > /sys/kernel/debug/tracing/events/mmc/mmc_clk/enable cat /sys/kernel/debug/tracing/trace_pipe Change-Id: I25c4ee39dcbe30e7665902a9f723a5a421b55ca3 Signed-off-by: Sujit Reddy Thumma --- drivers/mmc/core/core.c | 13 +++++++++++++ include/linux/mmc/host.h | 2 ++ include/trace/events/mmc.h | 20 +++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 150dbb2a7d8a..b085b34f703c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1122,6 +1122,19 @@ void mmc_set_ios(struct mmc_host *host) if (ios->clock > 0) mmc_set_ungated(host); host->ops->set_ios(host, ios); + if (ios->old_rate != ios->clock) { + if (likely(ios->clk_ts)) { + char trace_info[80]; + snprintf(trace_info, 80, + "%s: freq_KHz %d --> %d | t = %d", + mmc_hostname(host), ios->old_rate / 1000, + ios->clock / 1000, jiffies_to_msecs( + (long)jiffies - (long)ios->clk_ts)); + trace_mmc_clk(trace_info); + } + ios->old_rate = ios->clock; + ios->clk_ts = jiffies; + } } EXPORT_SYMBOL(mmc_set_ios); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index dd68bc9a805d..246151e79477 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -23,6 +23,8 @@ struct mmc_ios { unsigned int clock; /* clock rate */ + unsigned int old_rate; /* saved clock rate */ + unsigned long clk_ts; /* time stamp of last updated clock */ unsigned short vdd; /* vdd stores the bit number of the selected voltage range from below. */ diff --git a/include/trace/events/mmc.h b/include/trace/events/mmc.h index 22cf81b2d9ff..9606997a8f2e 100644 --- a/include/trace/events/mmc.h +++ b/include/trace/events/mmc.h @@ -158,7 +158,25 @@ DEFINE_EVENT(mmc_adma_class, mmc_adma_table_post, TP_PROTO(unsigned int cmd, unsigned int len), TP_ARGS(cmd, len)); -#endif /* _TRACE_MMC_H */ +TRACE_EVENT(mmc_clk, + TP_PROTO(char *print_info), + + TP_ARGS(print_info), + + TP_STRUCT__entry( + __string(print_info, print_info) + ), + + TP_fast_assign( + __assign_str(print_info, print_info); + ), + + TP_printk("%s", + __get_str(print_info) + ) +); + +#endif /* if !defined(_TRACE_MMC_H) || defined(TRACE_HEADER_MULTI_READ) */ /* This part must be outside protection */ #include From bca5c01383ae6bd9345a506a139d7fa8bd462b81 Mon Sep 17 00:00:00 2001 From: Lee Susman Date: Tue, 23 Apr 2013 17:59:26 +0300 Subject: [PATCH 166/472] mmc: add dynamic trigger for packed control In the current implementation packing is enabled according to a statically defined trigger. This patch updates the packing control mechanism to use a dynamically defined trigger. The trigger's value is calculated by the relation between the number of potential packed write requests and the mean value of all previous potential values: If the current potential is greater than the mean potential then the heuristic is that the following workload will contain many write requests, therefore we lower the packed trigger. In the opposite case we want to increase the trigger in order to get less packing events. In case we get an urgent request we 'punish' the packing control by increasing the trigger. Change-Id: I775e1582ad32a8f798e8b2bd2b3178aef357e747 Signed-off-by: Lee Susman --- drivers/mmc/card/block.c | 91 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 982264d2db87..06748b32d5d4 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -69,6 +69,7 @@ MODULE_ALIAS("mmc:block"); (rq_data_dir(req) == WRITE)) #define PACKED_CMD_VER 0x01 #define PACKED_CMD_WR 0x02 +#define PACKED_TRIGGER_MAX_ELEMENTS 5000 #define MMC_BLK_UPDATE_STOP_REASON(stats, reason) \ do { \ @@ -76,6 +77,12 @@ MODULE_ALIAS("mmc:block"); stats->pack_stop_reason[reason]++; \ } while (0) +#define PCKD_TRGR_INIT_MEAN_POTEN 17 +#define PCKD_TRGR_POTEN_LOWER_BOUND 5 +#define PCKD_TRGR_URGENT_PENALTY 2 +#define PCKD_TRGR_LOWER_BOUND 5 +#define PCKD_TRGR_PRECISION_MULTIPLIER 100 + static DEFINE_MUTEX(block_mutex); /* @@ -1917,6 +1924,80 @@ void mmc_blk_disable_wr_packing(struct mmc_queue *mq) } EXPORT_SYMBOL(mmc_blk_disable_wr_packing); +static int get_packed_trigger(int potential, struct mmc_card *card, + struct request *req, int curr_trigger) +{ + static int num_mean_elements = 1; + static unsigned long mean_potential = PCKD_TRGR_INIT_MEAN_POTEN; + unsigned int trigger = curr_trigger; + unsigned int pckd_trgr_upper_bound = card->ext_csd.max_packed_writes; + + /* scale down the upper bound to 75% */ + pckd_trgr_upper_bound = (pckd_trgr_upper_bound * 3) / 4; + + /* + * since the most common calls for this function are with small + * potential write values and since we don't want these calls to affect + * the packed trigger, set a lower bound and ignore calls with + * potential lower than that bound + */ + if (potential <= PCKD_TRGR_POTEN_LOWER_BOUND) + return trigger; + + /* + * this is to prevent integer overflow in the following calculation: + * once every PACKED_TRIGGER_MAX_ELEMENTS reset the algorithm + */ + if (num_mean_elements > PACKED_TRIGGER_MAX_ELEMENTS) { + num_mean_elements = 1; + mean_potential = PCKD_TRGR_INIT_MEAN_POTEN; + } + + /* + * get next mean value based on previous mean value and current + * potential packed writes. Calculation is as follows: + * mean_pot[i+1] = + * ((mean_pot[i] * num_mean_elem) + potential)/(num_mean_elem + 1) + */ + mean_potential *= num_mean_elements; + /* + * add num_mean_elements so that the division of two integers doesn't + * lower mean_potential too much + */ + if (potential > mean_potential) + mean_potential += num_mean_elements; + mean_potential += potential; + /* this is for gaining more precision when dividing two integers */ + mean_potential *= PCKD_TRGR_PRECISION_MULTIPLIER; + /* this completes the mean calculation */ + mean_potential /= ++num_mean_elements; + mean_potential /= PCKD_TRGR_PRECISION_MULTIPLIER; + + /* + * if current potential packed writes is greater than the mean potential + * then the heuristic is that the following workload will contain many + * write requests, therefore we lower the packed trigger. In the + * opposite case we want to increase the trigger in order to get less + * packing events. + */ + if (potential >= mean_potential) + trigger = (trigger <= PCKD_TRGR_LOWER_BOUND) ? + PCKD_TRGR_LOWER_BOUND : trigger - 1; + else + trigger = (trigger >= pckd_trgr_upper_bound) ? + pckd_trgr_upper_bound : trigger + 1; + + /* + * an urgent read request indicates a packed list being interrupted + * by this read, therefore we aim for less packing, hence the trigger + * gets increased + */ + if (req && (req->cmd_flags & REQ_URGENT) && (rq_data_dir(req) == READ)) + trigger += PCKD_TRGR_URGENT_PENALTY; + + return trigger; +} + static void mmc_blk_write_packing_control(struct mmc_queue *mq, struct request *req) { @@ -1944,6 +2025,10 @@ static void mmc_blk_write_packing_control(struct mmc_queue *mq, if (mq->num_of_potential_packed_wr_reqs > mq->num_wr_reqs_to_start_packing) mq->wr_packing_enabled = true; + mq->num_wr_reqs_to_start_packing = + get_packed_trigger(mq->num_of_potential_packed_wr_reqs, + mq->card, req, + mq->num_wr_reqs_to_start_packing); mq->num_of_potential_packed_wr_reqs = 0; return; } @@ -1952,6 +2037,12 @@ static void mmc_blk_write_packing_control(struct mmc_queue *mq, if (data_dir == READ) { mmc_blk_disable_wr_packing(mq); + mq->num_wr_reqs_to_start_packing = + get_packed_trigger(mq->num_of_potential_packed_wr_reqs, + mq->card, req, + mq->num_wr_reqs_to_start_packing); + mq->num_of_potential_packed_wr_reqs = 0; + mq->wr_packing_enabled = false; return; } else if (data_dir == WRITE) { mq->num_of_potential_packed_wr_reqs++; From 2023cd8d36564268d3859b64a61b94e9791b2d5f Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Thu, 4 Dec 2014 16:03:03 +0200 Subject: [PATCH 167/472] mmc: core: Add support for notifying host driver while scaling clocks Host drivers can participate in clock scaling by registering ->notify_load host operation, which allows host driver to carry out platform specific operations for further power savings or increasing throughput based on whether load is LOW or HIGH respectively. This can be applicable to non-ultra high speed cards as well so remove the check for ultra high speed cards to initialize clock scaling. Change-Id: Icaab9520135e384f5470db68b2f25c5cdce5663a Signed-off-by: Sujit Reddy Thumma [merez:codeaurora@org: fix conflicts due to removal of stop transmission] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 62 +++++++++++++++++++++++++++++++--------- drivers/mmc/core/host.c | 11 +++---- drivers/mmc/core/mmc.c | 4 +-- drivers/mmc/core/sd.c | 4 +-- include/linux/mmc/host.h | 1 + 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b085b34f703c..a4858ab50015 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2811,6 +2811,40 @@ out: return ret; } +static int mmc_clk_update_freq(struct mmc_host *host, + unsigned long freq, enum mmc_load state) +{ + int err = 0; + + if (host->ops->notify_load) { + err = host->ops->notify_load(host, state); + if (err) + goto out; + } + + if (freq != host->clk_scaling.curr_freq) { + if (!mmc_is_vaild_state_for_clk_scaling(host)) { + err = -EAGAIN; + goto error; + } + + err = host->bus_ops->change_bus_speed(host, &freq); + if (!err) + host->clk_scaling.curr_freq = freq; + else + pr_err("%s: %s: failed (%d) at freq=%lu\n", + mmc_hostname(host), __func__, err, freq); + } +error: + if (err) { + /* restore previous state */ + if (host->ops->notify_load) + host->ops->notify_load(host, host->clk_scaling.state); + } +out: + return err; +} + /** * mmc_clk_scaling() - clock scaling decision algorithm * @host: pointer to mmc host structure @@ -2836,6 +2870,7 @@ static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) unsigned int up_threshold = host->clk_scaling.up_threshold; unsigned int down_threshold = host->clk_scaling.down_threshold; bool queue_scale_down_work = false; + enum mmc_load state; if (!card || !host->bus_ops || !host->bus_ops->change_bus_speed) { pr_err("%s: %s: invalid entry\n", mmc_hostname(host), __func__); @@ -2863,6 +2898,7 @@ static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) busy_time_ms = host->clk_scaling.busy_time_us / USEC_PER_MSEC; freq = host->clk_scaling.curr_freq; + state = host->clk_scaling.state; /* * Note that the max. and min. frequency should be based @@ -2871,28 +2907,24 @@ static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) */ if ((busy_time_ms * 100 > total_time_ms * up_threshold)) { freq = mmc_get_max_frequency(host); + state = MMC_LOAD_HIGH; } else if ((busy_time_ms * 100 < total_time_ms * down_threshold)) { if (!from_wq) queue_scale_down_work = true; freq = mmc_get_min_frequency(host); + state = MMC_LOAD_LOW; } - if (freq != host->clk_scaling.curr_freq) { + if (state != host->clk_scaling.state) { if (!queue_scale_down_work) { if (!from_wq) cancel_delayed_work_sync( &host->clk_scaling.work); - - if (!mmc_is_vaild_state_for_clk_scaling(host)) - goto bypass_scaling; - - err = host->bus_ops->change_bus_speed(host, &freq); + err = mmc_clk_update_freq(host, freq, state); if (!err) - host->clk_scaling.curr_freq = freq; - else - pr_err("%s: %s: failed (%d) at freq=%lu\n", - mmc_hostname(host), __func__, err, - freq); + host->clk_scaling.state = state; + else if (err == -EAGAIN) + goto no_reset_stats; } else { /* * We hold claim host while queueing the scale down @@ -2901,13 +2933,12 @@ static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) */ queue_delayed_work(system_wq, &host->clk_scaling.work, 1); - host->clk_scaling.in_progress = false; - goto out; + goto no_reset_stats; } } mmc_reset_clk_scale_stats(host); -bypass_scaling: +no_reset_stats: host->clk_scaling.in_progress = false; out: return; @@ -2954,6 +2985,9 @@ void mmc_init_clk_scaling(struct mmc_host *host) INIT_DELAYED_WORK(&host->clk_scaling.work, mmc_clk_scale_work); host->clk_scaling.curr_freq = mmc_get_max_frequency(host); + if (host->ops->notify_load) + host->ops->notify_load(host, MMC_LOAD_HIGH); + host->clk_scaling.state = MMC_LOAD_HIGH; mmc_reset_clk_scale_stats(host); host->clk_scaling.enable = true; host->clk_scaling.initialized = true; diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 99b558762450..a0e19466bafc 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -641,11 +641,8 @@ static ssize_t store_enable(struct device *dev, goto err; if (value && !mmc_can_scale_clk(host)) { - if (mmc_card_hs200(host->card) || - mmc_card_uhs(host->card)) { - host->caps2 |= MMC_CAP2_CLK_SCALE; - mmc_init_clk_scaling(host); - } + host->caps2 |= MMC_CAP2_CLK_SCALE; + mmc_init_clk_scaling(host); if (!mmc_can_scale_clk(host)) { host->caps2 &= ~MMC_CAP2_CLK_SCALE; @@ -661,6 +658,10 @@ static ssize_t store_enable(struct device *dev, if (host->bus_ops->change_bus_speed(host, &freq)) goto err; } + if (host->ops->notify_load && + host->ops->notify_load(host, MMC_LOAD_HIGH)) + goto err; + host->clk_scaling.state = MMC_LOAD_HIGH; host->clk_scaling.initialized = false; } retval = count; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 8f47853de0ae..440d7a0a99be 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2060,9 +2060,7 @@ static int mmc_runtime_resume(struct mmc_host *host) pr_err("%s: error %d doing aggressive resume\n", mmc_hostname(host), err); - /* Initialize clock scaling only for high frequency modes */ - if (mmc_card_hs200(host->card)) - mmc_init_clk_scaling(host); + mmc_init_clk_scaling(host); return 0; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index ccab284c3ce5..b1385634d11b 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1325,9 +1325,7 @@ static int mmc_sd_runtime_resume(struct mmc_host *host) pr_err("%s: error %d doing aggressive resume\n", mmc_hostname(host), err); - /* Initialize clock scaling only for high frequency modes */ - if (mmc_card_uhs(host->card)) - mmc_init_clk_scaling(host); + mmc_init_clk_scaling(host); return 0; } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 246151e79477..959d52b324ec 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -443,6 +443,7 @@ struct mmc_host { bool initialized; bool in_progress; struct delayed_work work; + enum mmc_load state; } clk_scaling; unsigned long private[0] ____cacheline_aligned; }; From 83d95c50fb7c138177290641436a926ed4700e9b Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 4 Dec 2014 21:42:45 +0200 Subject: [PATCH 168/472] mmc: sdio: enable asynchronous interrupt support in 4-bit mode SDIO 3.0 specification has added the support for asynchronous interrupt period during which card allows the clock to be gated off. Host needs to first read the "Support Asynchronous Interrupt" bit in CCCR register space to check if the card supports the feature or not. If yes and if the host wants to enable the feature, host needs to write '1' to "Enable Asynchronous Interrupt" bit in CCCR register space. This change allows the host controller driver to control whether to enable the asynchronous interrupt in card or not and if the asynchronous interrupt is enabled then clock gating feature would be enabled for such cards. Change-Id: I678cffb63af6a2013640a5eafa6ce9bfad8a51d6 Signed-off-by: Subhash Jadavani [merez@codeaurora.org: fix CAPS2 index] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/host.c | 8 ++++++++ drivers/mmc/core/sdio.c | 17 +++++++++++++++++ include/linux/mmc/card.h | 3 ++- include/linux/mmc/host.h | 2 ++ include/linux/mmc/sdio.h | 4 ++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index a0e19466bafc..b0e8a7b69774 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -188,6 +188,14 @@ bool mmc_host_may_gate_card(struct mmc_card *card) /* If there is no card we may gate it */ if (!card) return true; + + /* + * SDIO3.0 card allows the clock to be gated off so check if + * that is the case or not. + */ + if (mmc_card_sdio(card) && card->cccr.async_intr_sup) + return true; + /* * Don't gate SDIO cards! These need to be clocked at all times * since they may be independent systems generating interrupts diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 0aa19ac04091..b0ce838f7ee6 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -187,6 +187,23 @@ static int sdio_read_cccr(struct mmc_card *card, u32 ocr) 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; + + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_CCCR_INTERRUPT_EXTENSION, 0, &data); + if (ret) + goto out; + if (data & SDIO_SUPPORT_ASYNC_INTR) { + if (card->host->caps2 & + MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE) { + data |= SDIO_ENABLE_ASYNC_INTR; + ret = mmc_io_rw_direct(card, 1, 0, + SDIO_CCCR_INTERRUPT_EXTENSION, + data, NULL); + if (ret) + goto out; + card->cccr.async_intr_sup = 1; + } + } } /* if no uhs mode ensure we check for high speed */ diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 04a6c02d7b32..968bf8b113e6 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -189,7 +189,8 @@ struct sdio_cccr { wide_bus:1, high_power:1, high_speed:1, - disable_cd:1; + disable_cd:1, + async_intr_sup:1; }; struct sdio_cis { diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 959d52b324ec..8b459bdcc797 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -310,6 +310,8 @@ struct mmc_host { #define MMC_CAP2_NO_WRITE_PROTECT (1 << 18) /* No physical write protect pin, assume that card is always read-write */ #define MMC_CAP2_PACKED_WR_CONTROL (1 << 19) /* Allow write packing control */ #define MMC_CAP2_CLK_SCALE (1 << 20) /* Allow dynamic clk scaling */ +/* Allows Asynchronous SDIO irq while card is in 4-bit mode */ +#define MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE (1 << 21) #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ mmc_pm_flag_t pm_caps; /* supported pm features */ diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index 2c274d0530d2..3fc07d701cd1 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -164,6 +164,10 @@ #define SDIO_DTSx_SET_TYPE_A (1 << SDIO_DRIVE_DTSx_SHIFT) #define SDIO_DTSx_SET_TYPE_C (2 << SDIO_DRIVE_DTSx_SHIFT) #define SDIO_DTSx_SET_TYPE_D (3 << SDIO_DRIVE_DTSx_SHIFT) + +#define SDIO_CCCR_INTERRUPT_EXTENSION 0x16 +#define SDIO_SUPPORT_ASYNC_INTR (1<<0) +#define SDIO_ENABLE_ASYNC_INTR (1<<1) /* * Function Basic Registers (FBR) */ From 15209ec725e499e1e53b6e1104b6249e0430bf94 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Thu, 4 Dec 2014 22:03:42 +0200 Subject: [PATCH 169/472] mmc: Add long power off notification support At the moment only POWER_OFF_SHORT is sent to the device in case the host is suspended. This patch adds the support of sending POWER_OFF_LONG notification in case the device is powered off. According to device vendors the POWER_OFF_LONG notification will shorten the initialization time of the eMMC card during next boot up. Change-Id: I3c6f224398450cf10463cbb316613fd430d1e8d2 Signed-off-by: Tatyana Brokhman Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fix conflicts as some of the code was already included] Signed-off-by: Maya Erez [venkatg@codeaurora.org: Remove PM related code] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 36 ++++++++++++++++++++++++++++++++++++ include/linux/mmc/card.h | 5 ++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 440d7a0a99be..461a150109fb 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "core.h" #include "host.h" @@ -1364,6 +1365,20 @@ out: return err; } +static int mmc_reboot_notify(struct notifier_block *notify_block, + unsigned long event, void *unused) +{ + struct mmc_card *card = container_of( + notify_block, struct mmc_card, reboot_notify); + + if (event != SYS_RESTART) + card->issue_long_pon = true; + else + card->issue_long_pon = false; + + return NOTIFY_OK; +} + /* * Activate High Speed or HS200 mode if supported. */ @@ -1495,6 +1510,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, card->rca = 1; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); host->card = card; + card->reboot_notify.notifier_call = mmc_reboot_notify; } /* @@ -1838,6 +1854,22 @@ static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) return err; } +int mmc_send_long_pon(struct mmc_card *card) +{ + int err = 0; + struct mmc_host *host = card->host; + + mmc_claim_host(host); + if (card->issue_long_pon && mmc_can_poweroff_notify(card)) { + err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_LONG); + if (err) + pr_warning("%s: error %d sending Long PON", + mmc_hostname(host), err); + } + mmc_release_host(host); + return err; +} + /* * Host is being removed. Free up the current card. */ @@ -1846,6 +1878,7 @@ static void mmc_remove(struct mmc_host *host) BUG_ON(!host); BUG_ON(!host->card); + unregister_reboot_notifier(&host->card->reboot_notify); mmc_remove_card(host->card); mmc_claim_host(host); @@ -2166,6 +2199,9 @@ int mmc_attach_mmc(struct mmc_host *host) goto remove_card; mmc_claim_host(host); + + register_reboot_notifier(&host->card->reboot_notify); + return 0; remove_card: diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 968bf8b113e6..c18556450f08 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -13,6 +13,7 @@ #include #include #include +#include struct mmc_cid { unsigned int manfid; @@ -342,6 +343,8 @@ struct mmc_card { unsigned int part_curr; struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ + struct notifier_block reboot_notify; + bool issue_long_pon; }; /* @@ -563,5 +566,5 @@ extern void mmc_fixup_device(struct mmc_card *card, extern struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics( struct mmc_card *card); extern void mmc_blk_init_packed_statistics(struct mmc_card *card); - +extern int mmc_send_long_pon(struct mmc_card *card); #endif /* LINUX_MMC_CARD_H */ From 954eba25460cb85a0a06c2be6055239c761d5ce9 Mon Sep 17 00:00:00 2001 From: Eugene Yasman Date: Thu, 4 Dec 2014 22:07:18 +0200 Subject: [PATCH 170/472] mmc: core: add checks for pointers before driver shutdown Verify drv and card pointers before calling for driver shutdown. Change-Id: I855e32f988ec1af475df6ed91f04618525e1a59f Signed-off-by: Eugene Yasman [merez@codeaurora.org: fix conflicts due to changes in 3.14] Signed-off-by: Maya Erez --- drivers/mmc/core/bus.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 483342e5dbfb..15e94c25b06e 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -132,6 +132,16 @@ static void mmc_bus_shutdown(struct device *dev) struct mmc_host *host = card->host; int ret; + if (!drv) { + pr_debug("%s: %s: drv is NULL\n", dev_name(dev), __func__); + return; + } + + if (!card) { + pr_debug("%s: %s: card is NULL\n", dev_name(dev), __func__); + return; + } + if (dev->driver && drv->shutdown) drv->shutdown(card); From 65dee48836422bcaa97b84b6f30a96d45bfa15fe Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 20 Jun 2013 18:15:50 +0530 Subject: [PATCH 171/472] mmc: block: fix the block driver shutdown mmc_queue_suspend() function returns the -EBUSY error if the MMC request queue is not empty as this function was getting called from the system suspend path which enforces time limit on the completion of the driver suspend callback. But recently the driver shutdown routine also started using mmc_queue_suspend() function but in shutdown case, we would really want to wait for the MMC request queue to be empty. To fix above issue, this change have added new argument named "wait" to mmc_queue_suspend() function which would tell whether it needs to wait for the MMC request queue to be empty or not. Driver shutdown callback will tell the mmc_queue_suspend() to wait but suspend callback won't. CRs-Fixed: 503227 Change-Id: I86f32d68ec4c4799648785681c5776f090ea6e36 Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 4 ++-- drivers/mmc/card/queue.c | 8 ++++++-- drivers/mmc/card/queue.h | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 06748b32d5d4..c3abd5c6ce52 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3180,11 +3180,11 @@ static int _mmc_blk_suspend(struct mmc_card *card) int rc = 0; if (md) { - rc = mmc_queue_suspend(&md->queue); + rc = mmc_queue_suspend(&md->queue, 0); if (rc) goto out; list_for_each_entry(part_md, &md->part, part) { - rc = mmc_queue_suspend(&part_md->queue); + rc = mmc_queue_suspend(&part_md->queue, 0); if (rc) goto out_resume; } diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index de3c1dd0a569..4756193e9a0f 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -421,12 +421,13 @@ void mmc_packed_clean(struct mmc_queue *mq) /** * mmc_queue_suspend - suspend a MMC request queue * @mq: MMC queue to suspend + * @wait: Wait till MMC request queue is empty * * Stop the block request queue, and wait for our thread to * complete any outstanding requests. This ensures that we * won't suspend while a request is being processed. */ -int mmc_queue_suspend(struct mmc_queue *mq) +int mmc_queue_suspend(struct mmc_queue *mq, int wait) { struct request_queue *q = mq->queue; unsigned long flags; @@ -440,7 +441,7 @@ int mmc_queue_suspend(struct mmc_queue *mq) spin_unlock_irqrestore(q->queue_lock, flags); rc = down_trylock(&mq->thread_sem); - if (rc) { + if (rc && !wait) { /* * Failed to take the lock so better to abort the * suspend because mmcqd thread is processing requests. @@ -450,6 +451,9 @@ int mmc_queue_suspend(struct mmc_queue *mq) blk_start_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); rc = -EBUSY; + } else if (rc && wait) { + down(&mq->thread_sem); + rc = 0; } } return rc; diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 900137715ceb..2e23b6d849ae 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -69,7 +69,7 @@ struct mmc_queue { extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, const char *); extern void mmc_cleanup_queue(struct mmc_queue *); -extern int mmc_queue_suspend(struct mmc_queue *); +extern int mmc_queue_suspend(struct mmc_queue *, int); extern void mmc_queue_resume(struct mmc_queue *); extern unsigned int mmc_queue_map_sg(struct mmc_queue *, From ca1367cb223366e7540bea2d3e03d927ebfeac81 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Fri, 21 Jun 2013 22:56:36 +0530 Subject: [PATCH 172/472] mmc: sdio: Update the buffer copy to include the last char During the copy the last character was getting truncated. Update the buffer copy to include all the characters. CRs-Fixed: 499579 Change-Id: I8e1df1d1f2294e85068aebcc876bef701e088b07 Signed-off-by: Pratibhasagar V --- drivers/mmc/core/sdio_cis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 4a8508ec8ffd..8b4266a11ee0 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -55,7 +55,7 @@ static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, for (i = 0; i < nr_strings; i++) { buffer[i] = string; - strlcpy(string, buf, strlen(buf)); + strlcpy(string, buf, strlen(buf) + 1); string += strlen(string) + 1; buf += strlen(buf) + 1; } From 358ac29e04226897063b5f3be85d9eb636b99820 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Thu, 4 Dec 2014 22:57:04 +0200 Subject: [PATCH 173/472] mmc: core: dont send PON during suspend During suspend, eMMC VCC can be turned off. So instead of sending a power off notification, send a sleep command. According to the eMMC 4.5 specification, this is the only time where its clearly mentioned that the VCC regulator can be turned OFF. This has been clarified in eMMC 5.0 specification and this applies even if PON with sleep is not sent to the card. Change-Id: I6c424bed3158132af7f9c2a2a701af7369fd5ec7 Signed-off-by: Krishna Konda [merez@codeaurora.org: fix conflicts due to changes in 3.14] Signed-off-by: Maya Erez --- drivers/mmc/core/mmc.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 461a150109fb..e5731432963d 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1927,8 +1927,6 @@ static void mmc_detect(struct mmc_host *host) static int _mmc_suspend(struct mmc_host *host, bool is_suspend) { int err = 0; - unsigned int notify_type = is_suspend ? EXT_CSD_POWER_OFF_SHORT : - EXT_CSD_POWER_OFF_LONG; BUG_ON(!host); BUG_ON(!host->card); @@ -1954,10 +1952,7 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (err) goto out; - if (mmc_can_poweroff_notify(host->card) && - ((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend)) - err = mmc_poweroff_notify(host->card, notify_type); - else if (mmc_can_sleep(host->card)) + if (mmc_can_sleep(host->card)) err = mmc_sleep(host); else if (!mmc_host_is_spi(host)) err = mmc_deselect_cards(host); From 3c0c23354a0c200f2f52c4be8de85f8949daa0c4 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 4 Dec 2014 23:05:50 +0200 Subject: [PATCH 174/472] mmc: core: add wakeup functionality to sdio cards This patch initializes wakeup source if the detected card is a sdio card and enables the wakeup capability. Platform drivers would have to invoke: * pm_wakeup_event on this card device to signal a wakeup * corresponding pm_relax have to be invoked Change-Id: Ic8d5c98073e8ed3f676eb42fc0ce1f13a11cb40f Signed-off-by: Asutosh Das [merez@codeaurora.org: fix conflicts due to different PM in 3.14] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/bus.c | 6 ++++++ drivers/mmc/core/sdio_irq.c | 18 ++++++++++++++++++ include/linux/mmc/host.h | 7 +++++++ 3 files changed, 31 insertions(+) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 15e94c25b06e..4a542d6bf509 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -361,6 +361,12 @@ int mmc_add_card(struct mmc_card *card) card->dev.of_node = mmc_of_find_child_device(card->host, 0); + if (mmc_card_sdio(card)) { + ret = device_init_wakeup(&card->dev, true); + if (ret) + pr_err("%s: %s: failed to init wakeup: %d\n", + mmc_hostname(card->host), __func__, ret); + } ret = device_add(&card->dev); if (ret) return ret; diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 09cc67d028f0..b95f942aea0e 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -104,6 +104,7 @@ static int sdio_irq_thread(void *_host) struct sched_param param = { .sched_priority = 1 }; unsigned long period, idle_period; int ret; + bool ws; sched_setscheduler(current, SCHED_FIFO, ¶m); @@ -137,6 +138,17 @@ static int sdio_irq_thread(void *_host) ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); if (ret) break; + ws = false; + /* + * prevent suspend if it has started when scheduled; + * 100 msec (approx. value) should be enough for the system to + * resume and attend to the card's request + */ + if ((host->dev_status == DEV_SUSPENDING) || + (host->dev_status == DEV_SUSPENDED)) { + pm_wakeup_event(&host->card->dev, 100); + ws = true; + } ret = process_sdio_pending_irqs(host); host->sdio_irq_pending = false; mmc_release_host(host); @@ -173,6 +185,12 @@ static int sdio_irq_thread(void *_host) host->ops->enable_sdio_irq(host, 1); mmc_host_clk_release(host); } + /* + * function drivers would have processed the event from card + * unless suspended, hence release wake source + */ + if (ws && (host->dev_status == DEV_RESUMED)) + pm_relax(&host->card->dev); if (!kthread_should_stop()) schedule_timeout(period); set_current_state(TASK_RUNNING); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 8b459bdcc797..82cb8b901e8b 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -218,6 +218,12 @@ struct mmc_supply { struct regulator *vqmmc; /* Optional Vccq supply */ }; +enum dev_state { + DEV_SUSPENDING = 1, + DEV_SUSPENDED, + DEV_RESUMED, +}; + struct mmc_host { struct device *parent; struct device class_dev; @@ -447,6 +453,7 @@ struct mmc_host { struct delayed_work work; enum mmc_load state; } clk_scaling; + enum dev_state dev_status; unsigned long private[0] ____cacheline_aligned; }; From 47c3f0cd0a333be13074ca8c15ea0ddd5ae47816 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Thu, 1 Nov 2012 13:26:21 +0530 Subject: [PATCH 175/472] mmc: card: Skip secure erase on MoviNAND; causes unrecoverable corruption. For several MoviNAND eMMC parts, there are known issues with secure erase and secure trim. For these specific MoviNAND devices, we skip these operations. Specifically, there is a bug in the eMMC firmware that causes unrecoverable corruption when the MMC is erased with MMC_CAP_ERASE enabled. References: http://forum.xda-developers.com/showthread.php?t=1644364 https://plus.google.com/111398485184813224730/posts/21pTYfTsCkB#111398485184813224730/posts/21pTYfTsCkB Change-Id: I9946828b9c9063da312f95483fcc47e26585489a Signed-off-by: Ian Chen Reviewed-by: Namjae Jeon Acked-by: Jaehoon Chung Reviewed-by: Linus Walleij Cc: stable [3.0+] Signed-off-by: Chris Ball Signed-off-by: Pratibhasagar V Patch-mainline: v3.6 Git-commit: 3550ccdb9d8d350e526b809bf3dd92b550a74fe1 Signed-off-by: Stephen Boyd --- drivers/mmc/card/block.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index c3abd5c6ce52..2b0fefc1a7bf 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3098,6 +3098,28 @@ static const struct mmc_fixup blk_fixups[] = MMC_FIXUP("SEM04G", 0x45, CID_OEMID_ANY, add_quirk_mmc, MMC_QUIRK_INAND_DATA_TIMEOUT), + /* + * On these Samsung MoviNAND parts, performing secure erase or + * secure trim can result in unrecoverable corruption due to a + * firmware bug. + */ + MMC_FIXUP("M8G2FA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("MAG4FA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("MBG8FA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("MCGAFA", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("VAL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("VYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("KYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + MMC_FIXUP("VZL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_SEC_ERASE_TRIM_BROKEN), + END_FIXUP }; From d3b98e3a117bca3e400f4bf20c9c6c1a2eac553b Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Fri, 6 Sep 2013 19:07:07 +0530 Subject: [PATCH 176/472] mmc: core: fix possible clock gating issue during voltage switch During voltage sequence (for UHS SD/SDIO cards), host first sends the voltage switch command (CMD11) to card and then host must stop the clock at least for 5ms but currently there is a possibility (if clkgate_delay is 0) that clock may be gated off immediately after the CMD11 response from card and then get turned on before 5ms itself. This patch ensures that clock is gated off at least for 5ms after receiving the card response for voltage switch command. Change-Id: I131b3d154adab29bef367c8ce31c2f2edd159fd2 Signed-off-by: Subhash Jadavani [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index a4858ab50015..5970e3ced815 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1753,12 +1753,15 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage, u32 ocr) pr_warn("%s: cannot verify signal voltage switch\n", mmc_hostname(host)); - mmc_host_clk_hold(host); - cmd.opcode = SD_SWITCH_VOLTAGE; cmd.arg = 0; cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + /* + * Hold the clock reference so clock doesn't get auto gated during this + * voltage switch sequence. + */ + mmc_host_clk_hold(host); err = mmc_wait_for_cmd(host, &cmd, 0); if (err) goto err_command; From 9c0c486493bb059e1de4dbd5b1c93c98ee8c9001 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 24 Apr 2013 15:22:05 +0530 Subject: [PATCH 177/472] mmc: quirks: enable clock gating for AR6004 card This patch whitelists the AR6004 card for clock-gating. The controller supports asynchronous interrupt functionality which enables to receive interrupts from the card when clocks are off. Hence, clocks can be turned off when idle. This patch whitelists the card, to let the clock-gating framework gate the clock during idle time. Change-Id: I651f86e42595cc82f99093c06ee220a1d0ec95a9 Signed-off-by: Asutosh Das --- drivers/mmc/core/quirks.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index 2a2704d9731b..562957a37704 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -55,6 +55,14 @@ #define SDIO_DEVICE_ID_MSM_QCA_AR6003_2 0x301 #endif +#ifndef SDIO_DEVICE_ID_MSM_QCA_AR6004_1 +#define SDIO_DEVICE_ID_MSM_QCA_AR6004_1 0x400 +#endif + +#ifndef SDIO_DEVICE_ID_MSM_QCA_AR6004_2 +#define SDIO_DEVICE_ID_MSM_QCA_AR6004_2 0x401 +#endif + /* * This hook just adds a quirk for all sdio devices */ @@ -83,6 +91,12 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6003_2, remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6004_1, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + + SDIO_FIXUP(SDIO_VENDOR_ID_MSM_QCA, SDIO_DEVICE_ID_MSM_QCA_AR6004_2, + remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), + SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_NONSTD_FUNC_IF), From a35f5e1cc424d7b1bd2af0c1bd105413f8617002 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Sat, 6 Dec 2014 20:01:46 +0200 Subject: [PATCH 178/472] ARM: dts: msm: Reduce the clocks for SD card slot for MSM8226 The SD cards functionality on QRD devices are failing with CRC errors when used with higher clock / bus modes. So reduce the clock speed for SD card slot. CRs-Fixed: 491789 Change-Id: I24b8bfe44cee4367c22846747f439365d8795d2e Signed-off-by: Pratibhasagar V [merez@codeaurora.org: msm8226-qrd-dtsi is not included in 3.14] --- drivers/mmc/core/sd.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index b1385634d11b..165b6f2b729e 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -27,6 +27,12 @@ #include "sd.h" #include "sd_ops.h" +#define UHS_SDR104_MIN_DTR (100 * 1000 * 1000) +#define UHS_DDR50_MIN_DTR (50 * 1000 * 1000) +#define UHS_SDR50_MIN_DTR (50 * 1000 * 1000) +#define UHS_SDR25_MIN_DTR (25 * 1000 * 1000) +#define UHS_SDR12_MIN_DTR (12.5 * 1000 * 1000) + static const unsigned int tran_exp[] = { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 @@ -425,18 +431,22 @@ static void sd_update_bus_speed_mode(struct mmc_card *card) } if ((card->host->caps & MMC_CAP_UHS_SDR104) && - (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104)) { + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR104) && + (card->host->f_max > UHS_SDR104_MIN_DTR)) { card->sd_bus_speed = UHS_SDR104_BUS_SPEED; } else if ((card->host->caps & MMC_CAP_UHS_DDR50) && - (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50)) { + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_DDR50) && + (card->host->f_max > UHS_DDR50_MIN_DTR)) { card->sd_bus_speed = UHS_DDR50_BUS_SPEED; } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50)) && (card->sw_caps.sd3_bus_mode & - SD_MODE_UHS_SDR50)) { + SD_MODE_UHS_SDR50) && + (card->host->f_max > UHS_SDR50_MIN_DTR)) { card->sd_bus_speed = UHS_SDR50_BUS_SPEED; } 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)) { + (card->sw_caps.sd3_bus_mode & SD_MODE_UHS_SDR25) && + (card->host->f_max > UHS_SDR25_MIN_DTR)) { card->sd_bus_speed = UHS_SDR25_BUS_SPEED; } else if ((card->host->caps & (MMC_CAP_UHS_SDR104 | MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR25 | From 3f3e47cfbb93f0a1f1e996e7c1e932b9ef1eea8e Mon Sep 17 00:00:00 2001 From: Raviv Shvili Date: Sat, 6 Dec 2014 20:06:27 +0200 Subject: [PATCH 179/472] mmc: core : fix arbitrary read/write to user space In the MMC card debug_fs the read and write handlers use the strlcat and sscanf, without checking the pointer given. Since the pointer is not checked it is possible to write everywhere (ring 0 or 3). In order to fix it, an access_ok function is being used to verify the buffer's pointer supplied by user is valid. CRs-fixed: 545716 Change-Id: Ia710b6af5a95974fc930ca902e8ff18afa4e17ba Signed-off-by: Raviv Shvili [merez@codeaurora.org: Fixed conflicts due to missing BKOPS statistics] Signed-off-by: Maya Erez --- drivers/mmc/core/debugfs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 734d2cce0a56..1479a96dfee4 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -424,6 +425,9 @@ static ssize_t mmc_wr_pack_stats_read(struct file *filp, char __user *ubuf, if (!card) return cnt; + if (!access_ok(VERIFY_WRITE, ubuf, cnt)) + return cnt; + if (!card->wr_pack_stats.print_in_read) return 0; @@ -564,6 +568,9 @@ static ssize_t mmc_wr_pack_stats_write(struct file *filp, if (!card) return cnt; + if (!access_ok(VERIFY_READ, ubuf, cnt)) + return cnt; + sscanf(ubuf, "%d", &value); if (value) { mmc_blk_init_packed_statistics(card); From 0c758bb6b206df527df06eb2e2fb2a784b063148 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 1 Oct 2013 16:03:16 +0530 Subject: [PATCH 180/472] mmc: core: add clock-scaling support to HS400 cards This patch adds clock scaling support to HS400 cards. Scaling down to 52MHz from HS400 involves: - switching the bus-speed mode to HS at 52MHz Scaling up to HS400 would require all of the initialization process upto HS400 mode selection. Change-Id: I8196d6666bcc0ef327659253df53a17792fa51f7 Signed-off-by: Asutosh Das Signed-off-by: Krishna Konda [venkatg@codeaurora.org: fix mmc_select_hs args and MAX_DTRs as used in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 14 +++++++++++++- drivers/mmc/core/mmc.c | 29 ++++++++++++++++++++++++++++- include/linux/mmc/card.h | 1 + 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 5970e3ced815..0b0f1821f5fb 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2689,13 +2689,19 @@ EXPORT_SYMBOL_GPL(mmc_reset_clk_scale_stats); unsigned long mmc_get_max_frequency(struct mmc_host *host) { unsigned long freq; + unsigned char timing; if (host->ops && host->ops->get_max_frequency) { freq = host->ops->get_max_frequency(host); goto out; } - switch (host->ios.timing) { + if (mmc_card_hs400(host->card)) + timing = MMC_TIMING_MMC_HS400; + else + timing = host->ios.timing; + + switch (timing) { case MMC_TIMING_UHS_SDR50: freq = UHS_SDR50_MAX_DTR; break; @@ -2708,6 +2714,9 @@ unsigned long mmc_get_max_frequency(struct mmc_host *host) case MMC_TIMING_UHS_DDR50: freq = UHS_DDR50_MAX_DTR; break; + case MMC_TIMING_MMC_HS400: + freq = MMC_HS200_MAX_DTR; + break; default: mmc_host_clk_hold(host); freq = host->ios.clock; @@ -2748,6 +2757,9 @@ static unsigned long mmc_get_min_frequency(struct mmc_host *host) case MMC_TIMING_MMC_HS200: freq = MMC_HIGH_52_MAX_DTR; break; + case MMC_TIMING_MMC_HS400: + freq = MMC_HIGH_52_MAX_DTR; + break; case MMC_TIMING_UHS_DDR50: freq = UHS_DDR50_MAX_DTR / 2; break; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index e5731432963d..200a9442508c 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -624,6 +624,7 @@ static int mmc_read_ext_csd(struct mmc_card *card) return err; } + memcpy(&card->cached_ext_csd, ext_csd, sizeof(card->ext_csd)); err = mmc_decode_ext_csd(card, ext_csd); kfree(ext_csd); return err; @@ -1299,6 +1300,26 @@ err: return err; } +int mmc_set_clock_bus_speed(struct mmc_card *card, unsigned long freq) +{ + int err; + + if (freq < MMC_HS200_MAX_DTR) { + /* + * Lower the clock and adjust the timing to be able + * to switch to HighSpeed mode + */ + mmc_set_timing(card->host, MMC_TIMING_LEGACY); + mmc_set_clock(card->host, MMC_HIGH_26_MAX_DTR); + + err = mmc_select_hs(card); + } else { + err = mmc_select_hs400(card); + } + + return err; +} + /** * mmc_change_bus_speed() - Change MMC card bus frequency at runtime * @host: pointer to mmc host structure @@ -1340,7 +1361,13 @@ static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) if (*freq < host->f_min) *freq = host->f_min; - mmc_set_clock(host, (unsigned int) (*freq)); + if (mmc_card_hs400(card)) { + err = mmc_set_clock_bus_speed(card, *freq); + if (err) + goto out; + } else { + mmc_set_clock(host, (unsigned int) (*freq)); + } if (mmc_card_hs200(card) && card->host->ops->execute_tuning) { /* diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index c18556450f08..258efa6c2edc 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -345,6 +345,7 @@ struct mmc_card { struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ struct notifier_block reboot_notify; bool issue_long_pon; + u8 cached_ext_csd; }; /* From 5355845e05c8cffdacd04702210c860fe6cf650d Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 7 Oct 2013 14:53:32 +0530 Subject: [PATCH 181/472] mmc: core: Check for NULL pointer access in ioctl Added checks to check if card is NULL before accessing it. CRs-Fixed: 548450 Change-Id: Idc005b8420a78b3566164102fbeaa243a8e73c7c Signed-off-by: Asutosh Das [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 5 ++--- drivers/mmc/core/core.c | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 2b0fefc1a7bf..0642ce0a1a44 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -868,9 +868,8 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, int err = 0, ioc_err = 0; idata = mmc_blk_ioctl_copy_from_user(ic_ptr); - if (IS_ERR(idata)) + if (IS_ERR_OR_NULL(idata)) return PTR_ERR(idata); - md = mmc_blk_get(bdev->bd_disk); if (!md) { err = -EINVAL; @@ -878,7 +877,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, } card = md->queue.card; - if (IS_ERR(card)) { + if (IS_ERR_OR_NULL(card)) { err = PTR_ERR(card); goto cmd_done; } diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0b0f1821f5fb..26ae35e9d023 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -876,6 +876,10 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) { unsigned int mult; + if (!card) { + WARN_ON(1); + return; + } /* * SDIO cards only define an upper 1 s limit on access. */ From 96bf883665a1f84b543765ead0355cd273b961c9 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 23 Oct 2013 09:05:21 +0530 Subject: [PATCH 182/472] mmc: block: Fix error path of mmc_blk_alloc_req() In case of any error within mmc_blk_alloc_req(), the bitmaps that keep track of devices are not getting cleared. This may result in failure to detect a card in case it reaches maximum devices limitation. Fix it by clearing those bitmaps appropriately. CRs-fixed: 563264 Change-Id: I0e23c45856355565534146f5fabb957fd4b1d007 Signed-off-by: Sahitya Tummala --- drivers/mmc/card/block.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 0642ce0a1a44..9eb7061038af 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2810,8 +2810,11 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, err_putdisk: put_disk(md->disk); err_kfree: + if (!subname) + __clear_bit(md->name_idx, name_use); kfree(md); out: + __clear_bit(devidx, dev_use); return ERR_PTR(ret); } From 02bf86d2b1559651b6f86771167f00b0037ca06a Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Thu, 5 Dec 2013 19:35:43 +0530 Subject: [PATCH 183/472] mmc: core: fix buffer overflow during memcpy of ext_csd Fix buffer overflow while caching the mmc ext_csd content. Also, to avoid duplicate allocation keep the allocated ext_csd till the card is removed. CRs-Fixed: 583929 Change-Id: I5d69e37f6fd1f5249479d454c353be050df40b6d Signed-off-by: Sujit Reddy Thumma [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/bus.c | 1 + drivers/mmc/core/mmc.c | 2 +- include/linux/mmc/card.h | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 4a542d6bf509..af884a552d5e 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -399,6 +399,7 @@ void mmc_remove_card(struct mmc_card *card) } kfree(card->wr_pack_stats.packing_events); + kfree(card->cached_ext_csd); put_device(&card->dev); } diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 200a9442508c..ec117aa422c0 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -624,7 +624,7 @@ static int mmc_read_ext_csd(struct mmc_card *card) return err; } - memcpy(&card->cached_ext_csd, ext_csd, sizeof(card->ext_csd)); + card->cached_ext_csd = ext_csd; err = mmc_decode_ext_csd(card, ext_csd); kfree(ext_csd); return err; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 258efa6c2edc..809ba6f07147 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -345,7 +345,7 @@ struct mmc_card { struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ struct notifier_block reboot_notify; bool issue_long_pon; - u8 cached_ext_csd; + u8 *cached_ext_csd; }; /* From e357ffba06233e026eba30d2348d4b524f2b4f9e Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 31 Jul 2013 16:32:49 +0530 Subject: [PATCH 184/472] mmc: queue: scale down the max_segs if memory allocation fails In low memory conditions at runtime, allocation of max_segs may fail. Retry with a scaled down max_segs until allocation succeeds. CRs-fixed: 583267 Change-Id: I072724afa44854dacc58654e6329531c1bb11120 Signed-off-by: Subhash Jadavani Signed-off-by: Asutosh Das --- drivers/mmc/card/queue.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 4756193e9a0f..904872e3fad2 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -286,22 +286,43 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, #endif if (!mqrq_cur->bounce_buf && !mqrq_prev->bounce_buf) { + unsigned int max_segs = host->max_segs; + blk_queue_bounce_limit(mq->queue, limit); blk_queue_max_hw_sectors(mq->queue, min(host->max_blk_count, host->max_req_size / 512)); - blk_queue_max_segments(mq->queue, host->max_segs); blk_queue_max_segment_size(mq->queue, host->max_seg_size); +retry: + blk_queue_max_segments(mq->queue, host->max_segs); mqrq_cur->sg = mmc_alloc_sg(host->max_segs, &ret); - if (ret) + if (ret == -ENOMEM) + goto cur_sg_alloc_failed; + else if (ret) goto cleanup_queue; - mqrq_prev->sg = mmc_alloc_sg(host->max_segs, &ret); - if (ret) + if (ret == -ENOMEM) + goto prev_sg_alloc_failed; + else if (ret) goto cleanup_queue; + + goto success; + +prev_sg_alloc_failed: + kfree(mqrq_cur->sg); + mqrq_cur->sg = NULL; +cur_sg_alloc_failed: + host->max_segs /= 2; + if (host->max_segs) { + goto retry; + } else { + host->max_segs = max_segs; + goto cleanup_queue; + } } +success: sema_init(&mq->thread_sem, 1); mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s", From 2f6c967231f2a4f6ee64bbefd372444efc210c1b Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 9 Dec 2014 10:15:53 +0200 Subject: [PATCH 185/472] mmc: block: check for NULL pointer before dereferencing mmc block data can be NULL. Hence, check for NULL before dereferencing md. CRs-Fixed: 562259 Change-Id: I0182c216ec73347cdd2ea464f593839fffd242a9 Signed-off-by: Asutosh Das [merez@codeaurora.org: fix conflicts due to removal of BKOPS statistics] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 9eb7061038af..27c3cb27d395 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -211,9 +211,13 @@ static ssize_t power_ro_lock_show(struct device *dev, { int ret; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); - struct mmc_card *card = md->queue.card; + struct mmc_card *card; int locked = 0; + if (!md) + return -EINVAL; + + card = md->queue.card; 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) @@ -241,6 +245,8 @@ static ssize_t power_ro_lock_store(struct device *dev, return count; md = mmc_blk_get(dev_to_disk(dev)); + if (!md) + return -EINVAL; card = md->queue.card; mmc_get_card(card); @@ -278,6 +284,9 @@ static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, int ret; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); + if (!md) + return -EINVAL; + ret = snprintf(buf, PAGE_SIZE, "%d\n", get_disk_ro(dev_to_disk(dev)) ^ md->read_only); @@ -292,6 +301,10 @@ static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, char *end; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); unsigned long set = simple_strtoul(buf, &end, 0); + + if (!md) + return -EINVAL; + if (end == buf) { ret = -EINVAL; goto out; @@ -312,6 +325,8 @@ num_wr_reqs_to_start_packing_show(struct device *dev, int num_wr_reqs_to_start_packing; int ret; + if (!md) + return -EINVAL; num_wr_reqs_to_start_packing = md->queue.num_wr_reqs_to_start_packing; ret = snprintf(buf, PAGE_SIZE, "%d\n", num_wr_reqs_to_start_packing); @@ -327,9 +342,13 @@ num_wr_reqs_to_start_packing_store(struct device *dev, { int value; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); - struct mmc_card *card = md->queue.card; + struct mmc_card *card; int ret = count; + if (!md) + return -EINVAL; + + card = md->queue.card; if (!card) { ret = -EINVAL; goto exit; @@ -363,6 +382,8 @@ no_pack_for_random_show(struct device *dev, struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); int ret; + if (!md) + return -EINVAL; ret = snprintf(buf, PAGE_SIZE, "%d\n", md->queue.no_pack_for_random); mmc_blk_put(md); @@ -376,9 +397,13 @@ no_pack_for_random_store(struct device *dev, { int value; struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev)); - struct mmc_card *card = md->queue.card; + struct mmc_card *card; int ret = count; + if (!md) + return -EINVAL; + + card = md->queue.card; if (!card) { ret = -EINVAL; goto exit; @@ -735,7 +760,7 @@ static int mmc_blk_ioctl_rpmb_cmd(struct block_device *bdev, /* make sure this is a rpmb partition */ if ((!md) || (!(md->area_type & MMC_BLK_DATA_AREA_RPMB))) { err = -EINVAL; - goto cmd_done; + return err; } idata = mmc_blk_ioctl_rpmb_copy_from_user(ic_ptr); From 9fc518192eecb53110c08726b85cc616d9264cf8 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 28 Oct 2013 15:18:53 -0700 Subject: [PATCH 186/472] mmc: core: get drive types supported by eMMC cards Get the various drive types other than the default supported by the card. Change-Id: I122971e4fb4a3ab98f0078ceafca3380e9c0e2d1 Signed-off-by: Krishna Konda [subhashj@codeaurora.org: fixed merge conflicts and fixed compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index ec117aa422c0..50d1eb11eaeb 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -387,6 +387,8 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.raw_card_type = ext_csd[EXT_CSD_CARD_TYPE]; mmc_select_card_type(card); + card->ext_csd.raw_driver_strength = ext_csd[EXT_CSD_DRIVER_STRENGTH]; + card->ext_csd.raw_s_a_timeout = ext_csd[EXT_CSD_S_A_TIMEOUT]; card->ext_csd.raw_erase_timeout_mult = ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT]; From 90102c17f9caaaf8ad8ec241df0a434aa3d8c643 Mon Sep 17 00:00:00 2001 From: Pratibhasagar V Date: Wed, 9 Apr 2014 12:52:46 +0530 Subject: [PATCH 187/472] mmc: core: Exit clock scaling prior to removing the card After removing the SD/MMC 'card' from the driver model we are cancelling the pending clock scaling work which accesses card->dev. This could cause NULL pointer issue as the card is already removed. CRs-Fixed: 640344 Change-Id: I8c5ee817e3f116dedf0bf4fb51eb6b70d52467b7 Signed-off-by: Pratibhasagar V --- drivers/mmc/core/mmc.c | 3 ++- drivers/mmc/core/sd.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 50d1eb11eaeb..351fc78d0d7c 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1908,11 +1908,12 @@ static void mmc_remove(struct mmc_host *host) BUG_ON(!host->card); unregister_reboot_notifier(&host->card->reboot_notify); + + mmc_exit_clk_scaling(host); mmc_remove_card(host->card); mmc_claim_host(host); host->card = NULL; - mmc_exit_clk_scaling(host); mmc_release_host(host); } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 165b6f2b729e..092b447ae901 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1119,11 +1119,11 @@ static void mmc_sd_remove(struct mmc_host *host) BUG_ON(!host); BUG_ON(!host->card); + mmc_exit_clk_scaling(host); mmc_remove_card(host->card); mmc_claim_host(host); host->card = NULL; - mmc_exit_clk_scaling(host); mmc_release_host(host); } From 3332d5a9e4fe04b14c3008d432a031c22a38bd2c Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 9 Dec 2014 12:55:38 +0200 Subject: [PATCH 188/472] mmc: core: Fix clock frequency transitions during invalid states eMMC and SD card specifications restrict the usage of a class of commands while commands in other class are in progress. For example, during erase operations the SD/eMMC spec. allows only CMD35, CMD36, CMD38. If clock scaling is enabled and decide to scale up the clocks it may be possible that CMD19/21 tuning commands are sent in between erase commands, which is illegal as per specification. Fix such illegal transactions to the card and also make clock scaling statistics accountable only for read/write commands instead of time consuming commands, like CMD38 erase, where transactions are independent of bus frequency. Change-Id: Iffba175787837e7f95bde8970f19d0f0f9d7d67d Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fix conflicts as mmc_update_clk_scaling is missing on 3.14] Signed-off-by: Maya Erez --- drivers/mmc/core/core.c | 51 ++++++++++++++++++++++++++++++++++------ include/linux/mmc/host.h | 2 ++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 26ae35e9d023..188ace67504b 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -127,6 +127,41 @@ static inline void mmc_should_fail_request(struct mmc_host *host, #endif /* CONFIG_FAIL_MMC_REQUEST */ +static inline void +mmc_clk_scaling_update_state(struct mmc_host *host, struct mmc_request *mrq) +{ + if (mrq) { + switch (mrq->cmd->opcode) { + case MMC_READ_SINGLE_BLOCK: + case MMC_READ_MULTIPLE_BLOCK: + case MMC_WRITE_BLOCK: + case MMC_WRITE_MULTIPLE_BLOCK: + host->clk_scaling.invalid_state = false; + break; + default: + host->clk_scaling.invalid_state = true; + break; + } + } else { + /* + * force clock scaling transitions, + * if other conditions are met + */ + host->clk_scaling.invalid_state = false; + } + + return; +} + +static inline void mmc_update_clk_scaling(struct mmc_host *host) +{ + if (host->clk_scaling.enable && !host->clk_scaling.invalid_state) { + host->clk_scaling.busy_time_us += + ktime_to_us(ktime_sub(ktime_get(), + host->clk_scaling.start_busy)); + host->clk_scaling.start_busy = ktime_get(); + } +} /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -142,10 +177,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) #ifdef CONFIG_MMC_PERF_PROFILING ktime_t diff; #endif - if (host->card && host->clk_scaling.enable) - host->clk_scaling.busy_time_us += - ktime_to_us(ktime_sub(ktime_get(), - host->clk_scaling.start_busy)); + if (host->card) + mmc_update_clk_scaling(host); /* Flag re-tuning needed on CRC errors */ if ((cmd->opcode != MMC_SEND_TUNING_BLOCK && @@ -340,8 +373,11 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) * frequency will be done after current thread * releases host. */ - mmc_clk_scaling(host, false); - host->clk_scaling.start_busy = ktime_get(); + mmc_clk_scaling_update_state(host, mrq); + if (!host->clk_scaling.invalid_state) { + mmc_clk_scaling(host, false); + host->clk_scaling.start_busy = ktime_get(); + } } __mmc_start_request(host, mrq); @@ -2811,7 +2847,8 @@ static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) * this mode. */ if (!card || (mmc_card_mmc(card) && - card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB)) + card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB) + || host->clk_scaling.invalid_state) goto out; if (mmc_send_status(card, &status)) { diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 82cb8b901e8b..ff861fd2517b 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -450,6 +450,8 @@ struct mmc_host { bool enable; bool initialized; bool in_progress; + /* freq. transitions are not allowed in invalid state */ + bool invalid_state; struct delayed_work work; enum mmc_load state; } clk_scaling; From c250bf69fda3120c4b47e41aa61cbad349b7df7a Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 30 May 2014 09:22:35 +0530 Subject: [PATCH 189/472] mmc: core: Fix NULL pointer dereference issue with mmc_blk_reset() If the mmc_hw_reset() fails, then host->card might be NULL in some cases. Hence, check for reset errors and report it to the caller so that the current request can be aborted and also check for host->card before accessing it so as to prevent NULL pointer dereference issue. Change-Id: Iba0f0be314474e607a40383bc0b28eef66a31d63 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 27c3cb27d395..32b413b61cda 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1403,8 +1403,15 @@ static int mmc_blk_reset(struct mmc_blk_data *md, struct mmc_host *host, md->reset_done |= type; err = mmc_hw_reset(host); + if (err && err != -EOPNOTSUPP) { + /* We failed to reset so we need to abort the request */ + pr_err("%s: %s: failed to reset %d\n", mmc_hostname(host), + __func__, err); + return -ENODEV; + } + /* Ensure we switch back to the correct partition */ - if (err != -EOPNOTSUPP) { + if (host->card) { struct mmc_blk_data *main_md = dev_get_drvdata(&host->card->dev); int part_err; From 248d1fcebed5f136804f86cff4501ed0461e84ee Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 9 Dec 2014 20:40:16 +0200 Subject: [PATCH 190/472] mmc: core: Fix null pointer dereference due to race conditions Fix race condition between mmcqd thread and the mmc_queue_suspend updating a shared variable mq->flags, which can lead to potential null pointer dereference as following- Unable to handle kernel NULL pointer dereference at virtual address 00000020 pgd = c0004000 [00000020] *pgd=00000000 mmcqd/0: 186] Internal error: Oops: 5 [#1] PREEMPT SMP ARM CPU: 0 Tainted: G W (3.4.0-1251694-eng #1) PC is at mmc_blk_err_check+0x20c/0x3b8 LR is at mmc_start_req+0x198/0x718 cpu0 | cpu1 x |= 1 | x |= 2 final value of x can be x = 1 or x = 2 Change-Id: Ie0fff6d6dba5aebb3584cba9fb98de24515c4cd8 Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fix conflicts due to missing stop transmission and changes in new request implementation in 3.14] Signed-off-by: Maya Erez [venkatg@codeaurora.org: Fix conflicts due to changes in 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 7 ++++--- drivers/mmc/card/queue.c | 14 ++++++-------- drivers/mmc/card/queue.h | 6 +++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 32b413b61cda..56cb49d8d256 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -2510,7 +2511,7 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) areq = mmc_start_req(card->host, areq, (int *) &status); if (!areq) { if (status == MMC_BLK_NEW_REQUEST) - mq->flags |= MMC_QUEUE_NEW_REQUEST; + set_bit(MMC_QUEUE_NEW_REQUEST, &mq->flags); return 0; } @@ -2679,7 +2680,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) mmc_blk_write_packing_control(mq, req); - mq->flags &= ~MMC_QUEUE_NEW_REQUEST; + clear_bit(MMC_QUEUE_NEW_REQUEST, &mq->flags); if (cmd_flags & REQ_DISCARD) { /* complete ongoing async transfer before issuing discard */ if (card->host->areq) @@ -2703,7 +2704,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) } out: - if ((!req && !(mq->flags & MMC_QUEUE_NEW_REQUEST)) || + if ((!req && !(test_bit(MMC_QUEUE_NEW_REQUEST, &mq->flags))) || (cmd_flags & MMC_REQ_SPECIAL_MASK)) /* * Release host when there are no more requests diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 904872e3fad2..695b0ef06b39 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -82,8 +83,8 @@ static int mmc_queue_thread(void *d) cmd_flags = req ? req->cmd_flags : 0; mq->issue_fn(mq, req); cond_resched(); - if (mq->flags & MMC_QUEUE_NEW_REQUEST) { - mq->flags &= ~MMC_QUEUE_NEW_REQUEST; + if (test_bit(MMC_QUEUE_NEW_REQUEST, &mq->flags)) { + clear_bit(MMC_QUEUE_NEW_REQUEST, &mq->flags); continue; /* fetch again */ } @@ -454,9 +455,7 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) unsigned long flags; int rc = 0; - if (!(mq->flags & MMC_QUEUE_SUSPENDED)) { - mq->flags |= MMC_QUEUE_SUSPENDED; - + if (!(test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags))) { spin_lock_irqsave(q->queue_lock, flags); blk_stop_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); @@ -467,7 +466,7 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) * Failed to take the lock so better to abort the * suspend because mmcqd thread is processing requests. */ - mq->flags &= ~MMC_QUEUE_SUSPENDED; + clear_bit(MMC_QUEUE_SUSPENDED, &mq->flags); spin_lock_irqsave(q->queue_lock, flags); blk_start_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); @@ -489,8 +488,7 @@ void mmc_queue_resume(struct mmc_queue *mq) struct request_queue *q = mq->queue; unsigned long flags; - if (mq->flags & MMC_QUEUE_SUSPENDED) { - mq->flags &= ~MMC_QUEUE_SUSPENDED; + if (test_and_clear_bit(MMC_QUEUE_SUSPENDED, &mq->flags)) { up(&mq->thread_sem); diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 2e23b6d849ae..bcb6827c0960 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -48,9 +48,9 @@ struct mmc_queue { struct mmc_card *card; struct task_struct *thread; struct semaphore thread_sem; - unsigned int flags; -#define MMC_QUEUE_SUSPENDED (1 << 0) -#define MMC_QUEUE_NEW_REQUEST (1 << 1) + unsigned long flags; +#define MMC_QUEUE_SUSPENDED 0 +#define MMC_QUEUE_NEW_REQUEST 1 int (*issue_fn)(struct mmc_queue *, struct request *); void *data; From c75b73aff388cd297fa0e85f89eb575acf16873c Mon Sep 17 00:00:00 2001 From: Sujit Reddy Thumma Date: Tue, 9 Dec 2014 20:44:41 +0200 Subject: [PATCH 191/472] mmc: core: Fix null pointer dereference due to illegal mmc request Fix a race condition that can lead to null pointer dereference while the MMC transfers are going on. 1) mmc_request_done() -> mmc_wait_for_data_done -> step1: update is_done_rcv step2: wake_up sleeping thread (mmcqd) waiting for is_done_rcv 2) mmcqd -> mmc_wait_for_data_req_done step4: wait for is_done_rcv or is_new_req step5: is_new_req set from block layer context and mmcqd is woken up step6: let's say step1 is done, so complete the current request step7: fetch new request and issue to host layer step8: fetch one more request and wait for previous request to complete In the above execution contexts, if step4-step8 happens between step1 and step2 a null pointer dereference is observed - [ 29.483302] Unable to handle kernel NULL pointer dereference at virtual address 00000488 [ 29.490366] pgd = c0004000 [ 29.493054] [00000488] *pgd=00000000 [ 29.518937] PC is at do_raw_spin_lock+0x8/0x13c [ 29.523445] LR is at _raw_spin_lock_irqsave+0x20/0x28 [ 30.108789] [] (do_raw_spin_lock+0x8/0x13c) from [ 30.118418] [] (_raw_spin_lock_irqsave+0x20/0x28) from [ 30.127445] [] (__wake_up+0x20/0x50) from [ 30.136124] [] (mmc_request_done+0x30c/0x368) from [ 30.145932] [] (sdhci_tasklet_finish+0x130/0x13c) from Change-Id: I9a21431b5fd9bb9bbcb5c18a9895096fe845e64b Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fixed conflicts due to missing stop transmission] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 188ace67504b..9e60bd2d021c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -463,10 +463,13 @@ EXPORT_SYMBOL(mmc_start_bkops); */ static void mmc_wait_data_done(struct mmc_request *mrq) { + unsigned long flags; struct mmc_context_info *context_info = &mrq->host->context_info; + spin_lock_irqsave(&context_info->lock, flags); context_info->is_done_rcv = true; wake_up_interruptible(&context_info->wait); + spin_unlock_irqrestore(&context_info->lock, flags); } static void mmc_wait_done(struct mmc_request *mrq) @@ -532,6 +535,7 @@ static int mmc_wait_for_data_req_done(struct mmc_host *host, struct mmc_command *cmd; struct mmc_context_info *context_info = &host->context_info; int err; + bool is_done_rcv = false; unsigned long flags; while (1) { @@ -539,9 +543,10 @@ static int mmc_wait_for_data_req_done(struct mmc_host *host, (context_info->is_done_rcv || context_info->is_new_req)); spin_lock_irqsave(&context_info->lock, flags); + is_done_rcv = context_info->is_done_rcv; context_info->is_waiting_last_req = false; spin_unlock_irqrestore(&context_info->lock, flags); - if (context_info->is_done_rcv) { + if (is_done_rcv) { context_info->is_done_rcv = false; context_info->is_new_req = false; cmd = mrq->cmd; From c6cc59c4358c423be099b5517319c5da5ef0f752 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 9 Dec 2014 23:23:25 +0200 Subject: [PATCH 192/472] mmc: core: Add retry mechanism for MMC resume failure Enhance the error handling/recovery path during eMMC resume by adding retry mechanism and by adding additional error messages to failure cases. This may help some of the bad parts which fail to resume sporadically. Change-Id: I895068edb487e6a44205e0769342b2ec2c89c876 Signed-off-by: Sahitya Tummala [merez@codeaurora.org: fix conflicts due to addition of __mmc_resume in 3.14 and different bus speed implementation] Signed-off-by: Maya Erez [venkatg@codeaurora.org: Fix args for mmc_power_up and mmc_select_voltage to use with 3.14 kernel] Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 115 ++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 20 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 351fc78d0d7c..7cd054d391fb 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -595,6 +595,7 @@ out: static int mmc_read_ext_csd(struct mmc_card *card) { + struct mmc_host *host = card->host; u8 *ext_csd; int err; @@ -603,6 +604,9 @@ static int mmc_read_ext_csd(struct mmc_card *card) err = mmc_get_ext_csd(card, &ext_csd); if (err) { + pr_err("%s: %s: mmc_get_ext_csd() fails %d\n", + mmc_hostname(host), __func__, err); + /* If the host or the card can't do the switch, * fail more gracefully. */ if ((err != -EINVAL) @@ -1495,16 +1499,22 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* The extra bit indicates that we support high capacity */ err = mmc_send_op_cond(host, ocr | (1 << 30), &rocr); - if (err) + if (err) { + pr_err("%s: %s: mmc_send_op_cond() fails %d\n", + mmc_hostname(host), __func__, err); goto err; + } /* * For SPI, enable CRC as appropriate. */ if (mmc_host_is_spi(host)) { err = mmc_spi_set_crc(host, use_spi_crc); - if (err) + if (err) { + pr_err("%s: %s: mmc_spi_set_crc() fails %d\n", + mmc_hostname(host), __func__, err); goto err; + } } /* @@ -1514,12 +1524,17 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_send_cid(host, cid); else err = mmc_all_send_cid(host, cid); - if (err) + if (err) { + pr_err("%s: %s: mmc_send_cid() fails %d\n", + mmc_hostname(host), __func__, err); goto err; + } if (oldcard) { if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { err = -ENOENT; + pr_err("%s: %s: CID memcmp failed %d\n", + mmc_hostname(host), __func__, err); goto err; } @@ -1531,6 +1546,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, card = mmc_alloc_card(host, &mmc_type); if (IS_ERR(card)) { err = PTR_ERR(card); + pr_err("%s: %s: no memory to allocate for card %d\n", + mmc_hostname(host), __func__, err); goto err; } @@ -1553,8 +1570,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, */ if (!mmc_host_is_spi(host)) { err = mmc_set_relative_addr(card); - if (err) + if (err) { + pr_err("%s: %s: mmc_set_relative_addr() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); } @@ -1564,15 +1584,24 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err) + if (err) { + pr_err("%s: %s: mmc_send_csd() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } err = mmc_decode_csd(card); - if (err) + if (err) { + pr_err("%s: %s: mmc_decode_csd() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } err = mmc_decode_cid(card); - if (err) + if (err) { + pr_err("%s: %s: mmc_decode_cid() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } } /* @@ -1587,15 +1616,21 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, */ if (!mmc_host_is_spi(host)) { err = mmc_select_card(card); - if (err) + if (err) { + pr_err("%s: %s: mmc_select_card() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } } if (!oldcard) { /* Read extended CSD. */ err = mmc_read_ext_csd(card); - if (err) + if (err) { + pr_err("%s: %s: mmc_read_ext_csd() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } /* If doing byte addressing, check if required to do sector * addressing. Handle the case of <2GB cards needing sector @@ -1622,8 +1657,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, EXT_CSD_ERASE_GROUP_DEF, 1, card->ext_csd.generic_cmd6_time); - if (err && err != -EBADMSG) + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for ERASE_GRP_DEF fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } if (err) { err = 0; @@ -1653,8 +1691,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_PART_CONFIG, card->ext_csd.part_config, card->ext_csd.part_time); - if (err && err != -EBADMSG) + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for PART_CONFIG fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } card->part_curr = card->ext_csd.part_config & EXT_CSD_PART_CONFIG_ACC_MASK; } @@ -1667,8 +1708,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, EXT_CSD_POWER_OFF_NOTIFICATION, EXT_CSD_POWER_ON, card->ext_csd.generic_cmd6_time); - if (err && err != -EBADMSG) + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for POWER_ON PON fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } /* * The err can be -EBADMSG or 0, @@ -1682,8 +1726,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Select timing interface */ err = mmc_select_timing(card); - if (err) + if (err) { + pr_err("%s: %s: mmc_select_timing() fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } if (mmc_card_hs200(card)) { err = mmc_hs200_tuning(card); @@ -1715,8 +1762,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HPI_MGMT, 1, card->ext_csd.generic_cmd6_time); - if (err && err != -EBADMSG) + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for HPI_MGMT fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } if (err) { pr_warn("%s: Enabling HPI failed\n", mmc_hostname(card->host)); @@ -1733,8 +1783,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CACHE_CTRL, 1, card->ext_csd.generic_cmd6_time); - if (err && err != -EBADMSG) + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for CACHE_CTRL fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } /* * Only if no error, cache is turned on successfully. @@ -1760,8 +1813,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, EXT_CSD_EXP_EVENTS_CTRL, EXT_CSD_PACKED_EVENT_EN, card->ext_csd.generic_cmd6_time); - if (err && err != -EBADMSG) + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for EXP_EVENTS_CTRL fails %d\n", + mmc_hostname(host), __func__, err); goto free_card; + } if (err) { pr_warn("%s: Enabling packed event failed\n", mmc_hostname(card->host)); @@ -1785,17 +1841,21 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (card->ext_csd.max_packed_writes + 1) * sizeof(*card->wr_pack_stats.packing_events), GFP_KERNEL); - if (!card->wr_pack_stats.packing_events) + if (!card->wr_pack_stats.packing_events) { + pr_err("%s: %s: no memory for packing events\n", + mmc_hostname(host), __func__); goto free_card; + } } } return 0; free_card: - host->card = NULL; - if (!oldcard) + if (!oldcard) { + host->card = NULL; mmc_remove_card(card); + } err: return err; } @@ -2019,6 +2079,7 @@ static int mmc_suspend(struct mmc_host *host) static int _mmc_resume(struct mmc_host *host) { int err = 0; + int retries; BUG_ON(!host); BUG_ON(!host->card); @@ -2031,7 +2092,21 @@ static int _mmc_resume(struct mmc_host *host) } mmc_power_up(host, host->card->ocr); - err = mmc_init_card(host, host->card->ocr, host->card); + retries = 3; + while (retries) { + err = mmc_init_card(host, host->card->ocr, host->card); + if (err) { + pr_err("%s: MMC card re-init failed rc = %d (retries = %d)\n", + mmc_hostname(host), err, retries); + retries--; + mmc_power_off(host); + usleep_range(5000, 5500); + mmc_power_up(host, host->card->ocr); + mmc_select_voltage(host, host->card->ocr); + continue; + } + break; + } mmc_card_clr_suspended(host->card); mmc_release_host(host); From 4bc12fc78cd0c2fdd3e7d8ffdd4ec93c306a41af Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Tue, 9 Dec 2014 23:31:55 +0200 Subject: [PATCH 193/472] mmc: block: Fix an invalid use of req->cmd_flag in DISCARD operation When handling a DISCARD operation, the MMC request data structure may be freed in memory. Therefore, it can't be used to retrieve the cmd_flags for checking if MMC_REQ_NOREINSERT_MASK is set: (!(mq->mqrq_cur->req->cmd_flags & MMC_REQ_NOREINSERT_MASK)))). To prevent the issue we should use the local variable of cmd_flags. Change-Id: Idef53d5bd66fa6f1faaf79644c8efb5177c75e89 Signed-off-by: Maya Erez Signed-off-by: Konstantin Dorfman [merez@codeaurora.org: fix conflicts due to removal of stop transmission] Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 56cb49d8d256..b587f301dc94 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2685,7 +2685,8 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) /* complete ongoing async transfer before issuing discard */ if (card->host->areq) mmc_blk_issue_rw_rq(mq, NULL); - if (req->cmd_flags & REQ_SECURE) + if (cmd_flags & REQ_SECURE && + !(card->quirks & MMC_QUIRK_SEC_ERASE_TRIM_BROKEN)) ret = mmc_blk_issue_secdiscard_rq(mq, req); else ret = mmc_blk_issue_discard_rq(mq, req); From fe333f447f6ab9f94898f6182e055ae0e72fe740 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Tue, 9 Dec 2014 23:34:41 +0200 Subject: [PATCH 194/472] mmc: core: Avoid infinite retries of failed mmc command With some bad SD cards, it is possible that the error recovery procedure goes into a state where it retries the failed command infinitely leading to CPU hog. Fix inifinite retries when the bad SD card isn't responding to a command even when the SD card reset mechanism is successful. CRs-Fixed: 671153 Change-Id: Ic6db66b571aa425aec32c82d52789c68fe0cb0e9 Signed-off-by: Sujit Reddy Thumma [merez@codeaurora.org: fix conflicts due to removal of sanitize from block.c in 3.14] Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index b587f301dc94..28048f872850 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -72,6 +72,7 @@ MODULE_ALIAS("mmc:block"); #define PACKED_CMD_WR 0x02 #define PACKED_TRIGGER_MAX_ELEMENTS 5000 +#define MMC_BLK_MAX_RETRIES 5 /* max # of retries before aborting a command */ #define MMC_BLK_UPDATE_STOP_REASON(stats, reason) \ do { \ if (stats->enabled) \ @@ -2559,11 +2560,12 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) break; case MMC_BLK_RETRY: retune_retry_done = brq->retune_retry_done; - if (retry++ < 5) + if (retry++ < MMC_BLK_MAX_RETRIES) break; /* Fall through */ case MMC_BLK_ABORT: - if (!mmc_blk_reset(md, card->host, type)) + if (!mmc_blk_reset(md, card->host, type) && + (retry++ < (MMC_BLK_MAX_RETRIES + 1))) break; goto cmd_abort; case MMC_BLK_DATA_ERR: { From 34e4cf13f729da24d0ff25393acea1f5fdb5e375 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 9 Jan 2015 15:09:11 -0800 Subject: [PATCH 195/472] mmc: core: Update HS400 timing mode before performing tuning There is no need to prepare anything additional before tuning for HS400 other than updating the HS400 timing mode, so just do that. Change-Id: Ib7a72a4bb9b901e32413acf440c925c4cd50a73d Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 7cd054d391fb..d1f46fa6343d 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1461,8 +1461,7 @@ static int mmc_hs200_tuning(struct mmc_card *card) */ if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400 && host->ios.bus_width == MMC_BUS_WIDTH_8) - if (host->ops->prepare_hs400_tuning) - host->ops->prepare_hs400_tuning(host, &host->ios); + mmc_set_timing(host, MMC_TIMING_MMC_HS400); return mmc_execute_tuning(card); } From 58821ee5f6552ebfcf22e8a8ee00cf9da7aad947 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 9 Jan 2015 15:11:57 -0800 Subject: [PATCH 196/472] mmc: core: Add workaround for hosts that need addtional tuning for HS400 Add a capability to identify hosts that need additional tuning for HS400 and perform a post tuning process that maybe needed for proper HS400 functionality. Change-Id: I3895aabddce4dbecb208e3c522957e656f37e30d Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 11 +++++++++++ include/linux/mmc/host.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index d1f46fa6343d..c2963f43d61c 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1139,6 +1139,17 @@ static int mmc_select_hs400(struct mmc_card *card) goto out_err; } + if ((host->caps2 & MMC_CAP2_HS400_POST_TUNING) && host->ops->execute_tuning) { + mmc_host_clk_hold(host); + err = host->ops->execute_tuning(host, + MMC_SEND_TUNING_BLOCK_HS200); + mmc_host_clk_release(host); + + if (err) + pr_warn("%s: tuning execution failed\n", + mmc_hostname(host)); + } + return 0; out_err: diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index ff861fd2517b..fca8e4a04c3b 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -318,6 +318,8 @@ struct mmc_host { #define MMC_CAP2_CLK_SCALE (1 << 20) /* Allow dynamic clk scaling */ /* Allows Asynchronous SDIO irq while card is in 4-bit mode */ #define MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE (1 << 21) +/* Some hosts need additional tuning */ +#define MMC_CAP2_HS400_POST_TUNING (1 << 22) #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ mmc_pm_flag_t pm_caps; /* supported pm features */ From ecdef62197414d8d6c294d4d5730ad1460da7154 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 9 Jan 2015 17:26:27 -0800 Subject: [PATCH 197/472] mmc: core: declare local stack arrays as const The array values are readonly and initialized during declaration. Change-Id: I3abbf15a441300af9828ac907ea67975302d5352 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/core/mmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index c2963f43d61c..93b0b596dd20 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -896,11 +896,11 @@ static void mmc_set_bus_speed(struct mmc_card *card) */ static int mmc_select_bus_width(struct mmc_card *card) { - static unsigned ext_csd_bits[] = { + static const unsigned ext_csd_bits[] = { EXT_CSD_BUS_WIDTH_8, EXT_CSD_BUS_WIDTH_4, }; - static unsigned bus_widths[] = { + static const unsigned bus_widths[] = { MMC_BUS_WIDTH_8, MMC_BUS_WIDTH_4, }; From a14ad583988ef20b6504ce889ba18ed9dc363603 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 16 Dec 2014 15:07:12 -0800 Subject: [PATCH 198/472] include: sdhci: Fix QUIRK2 defines Fix all the QUIRK2 define conflicts due to 3.14 kernel merge Change-Id: Id7095884755e1390ab928110bf585cb462317bea Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index c98cd2f29182..01b26839a660 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -444,8 +444,17 @@ struct sdhci_host { */ #define SDHCI_QUIRK2_SLOW_INT_CLR (1<<18) +/* + * If the base clock can be scalable, then there should be no further + * clock dividing as the input clock itself will be scaled down to + * required frequency. + */ #define SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK (1<<19) - +/* + * Dont use the max_discard_to in sdhci driver so that the maximum discard + * unit gets picked by the mmc queue. Otherwise, it takes a long time for + * secure discard kind of operations to complete. + */ #define SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE (1<<20) /* From e224d1c722c113973b3bbc22d215bd59f927104e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 12 Jan 2015 19:52:35 -0800 Subject: [PATCH 199/472] mmc: sdhci: Fix spinlock handling in sdhci_do_set_ios() sdhci_update_clock() has been removed and host ops set_clock() is called directly in various places. Release the host spinlock before calling host ops set_clock(). Change-Id: Ia12917286a7d791e6a6ad826c0b7edd71c8694ae Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a47105ae1554..e9d6472d7ccf 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1832,8 +1832,11 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); /* Re-enable SD Clock */ - if (ios->clock) + if (ios->clock) { + spin_unlock_irqrestore(&host->lock, flags); host->ops->set_clock(host, host->clock); + spin_lock_irqsave(&host->lock, flags); + } } /* Reset SD Clock Enable */ @@ -1860,8 +1863,11 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } /* Re-enable SD Clock */ - if (ios->clock) + if (ios->clock) { + spin_unlock_irqrestore(&host->lock, flags); host->ops->set_clock(host, host->clock); + spin_lock_irqsave(&host->lock, flags); + } } else sdhci_writeb(host, ctrl, SDHCI_HOST_CONTROL); From 78e333d973f31848f0470e04b6f88411d24c9a5a Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 16 Dec 2014 17:31:00 -0800 Subject: [PATCH 200/472] mmc: sdhci-msm: Add missing register defines Add missing register defines that were removed as part of dropping stop transmission support. Rearrange the defines as part of 3.14 kernel upgrade. Change-Id: Ie7c38721b4f96d066260e7aeb01043a5a909ed01 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 45 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 8ef9478a549e..26195f4cd780 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2,7 +2,7 @@ * drivers/mmc/host/sdhci-msm.c - Qualcomm Technologies, Inc. MSM SDHCI Platform * driver source file * - * Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -40,16 +40,33 @@ #include "sdhci-pltfm.h" +#define CORE_POWER 0x0 +#define CORE_SW_RST (1 << 7) + #define SDHCI_VER_100 0x2B +#define CORE_MCI_DATA_CNT 0x30 +#define CORE_MCI_STATUS 0x34 +#define CORE_MCI_FIFO_CNT 0x44 + +#define CORE_MCI_VERSION 0x050 +#define CORE_VERSION_STEP_MASK 0x0000FFFF +#define CORE_VERSION_MINOR_MASK 0x0FFF0000 +#define CORE_VERSION_MINOR_SHIFT 16 +#define CORE_VERSION_MAJOR_MASK 0xF0000000 +#define CORE_VERSION_MAJOR_SHIFT 28 +#define CORE_VERSION_TARGET_MASK 0x000000FF + +#define CORE_GENERICS 0x70 +#define SWITCHABLE_SIGNALLING_VOL (1 << 29) + #define CORE_HC_MODE 0x78 #define HC_MODE_EN 0x1 #define FF_CLK_SW_RST_DIS (1 << 13) -#define CORE_GENERICS 0x70 -#define SWITCHABLE_SIGNALLING_VOL (1 << 29) - -#define CORE_POWER 0x0 -#define CORE_SW_RST (1 << 7) +#define CORE_TESTBUS_CONFIG 0x0CC +#define CORE_TESTBUS_SEL2_BIT 4 +#define CORE_TESTBUS_ENA (1 << 3) +#define CORE_TESTBUS_SEL2 (1 << CORE_TESTBUS_SEL2_BIT) #define CORE_PWRCTL_STATUS 0xDC #define CORE_PWRCTL_MASK 0xE0 @@ -93,6 +110,9 @@ #define CORE_HC_SELECT_IN_HS400 (6 << 19) #define CORE_HC_SELECT_IN_MASK (7 << 19) +#define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 +#define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 + #define CORE_VENDOR_SPEC_CAPABILITIES0 0x11C #define CORE_8_BIT_SUPPORT (1 << 18) #define CORE_3_3V_SUPPORT (1 << 24) @@ -100,8 +120,7 @@ #define CORE_1_8V_SUPPORT (1 << 26) #define CORE_SYS_BUS_SUPPORT_64_BIT BIT(28) -#define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 -#define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 +#define CORE_SDCC_DEBUG_REG 0x124 #define CORE_CSR_CDC_CTLR_CFG0 0x130 #define CORE_SW_TRIG_FULL_CALIB (1 << 16) @@ -138,14 +157,6 @@ #define CORE_DDR_CONFIG 0x1B8 #define DDR_CONFIG_POR_VAL 0x80040853 - -#define CORE_MCI_DATA_CNT 0x30 -#define CORE_MCI_STATUS 0x34 -#define CORE_MCI_FIFO_CNT 0x44 - -#define CORE_TESTBUS_SEL2_BIT 4 -#define CORE_TESTBUS_SEL2 (1 << CORE_TESTBUS_SEL2_BIT) - /* 512 descriptors */ #define SDHCI_MSM_MAX_SEGMENTS (1 << 9) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ @@ -154,8 +165,6 @@ #define INVALID_TUNING_PHASE -1 -#define CORE_VERSION_TARGET_MASK 0x000000FF - #define NUM_TUNING_PHASES 16 #define MAX_DRV_TYPES_SUPPORTED_HS200 3 From ffd18b69b79b9293ecce0067884ec66ecb2fa425 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 16 Dec 2014 18:09:12 -0800 Subject: [PATCH 201/472] mmc: sdhci-msm: Update the args for sdhci_pltfm_init The function signature changed for sdhci_pltfm_init as part of 3.14 kernel upgrade. Make changes to reflect that. Change-Id: I6ac6fbd9703253d31a14c07399423b1c8a0b3301 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 26195f4cd780..95564e119b6b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2840,7 +2840,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) } msm_host->sdhci_msm_pdata.ops = &sdhci_msm_ops; - host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata); + host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0); if (IS_ERR(host)) { ret = PTR_ERR(host); goto out; From 4db29d8d463545acb5dfa8df5515df2af115148d Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 16 Dec 2014 18:26:25 -0800 Subject: [PATCH 202/472] mmc: sdhci-msm: Adjust host capabilities As part of 3.14 kernel upgrade some capabilities have been removed, added and renamed. Make those adjusments in the sdhci-msm platform driver. Change-Id: Icb14a3e997875e3ab094ef51a2307972d0788a89 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 95564e119b6b..fea88d15f2f6 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3082,12 +3082,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR; msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; - msm_host->mmc->caps2 |= (MMC_CAP2_BOOTPART_NOACC | - MMC_CAP2_DETECT_ON_ERR); - msm_host->mmc->caps2 |= MMC_CAP2_CACHE_CTRL; - msm_host->mmc->caps2 |= MMC_CAP2_POWEROFF_NOTIFY; + msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; + msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; + msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; if (msm_host->pdata->nonremovable) From 99efbad3f449e5dfe422068d8854dfe59ba807e0 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 9 Jan 2015 11:04:34 -0800 Subject: [PATCH 203/472] mmc: sdhci-msm: use the generic sdhci_set_bus_width() The generic bus width setup has been converted to a library function. Update the sdhci_ops function table to use the generic sdhci_set_bus_width() library function. Change-Id: I0fb1345cab1055ed2f5f2e36debf5ea273241628 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index fea88d15f2f6..f2b7d820ccb1 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2766,6 +2766,7 @@ static struct sdhci_ops sdhci_msm_ops = { .dump_vendor_regs = sdhci_msm_dump_vendor_regs, .config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd, .enable_controller_clock = sdhci_msm_enable_controller_clock, + .set_bus_width = sdhci_set_bus_width, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, From e71e9559d4327b8b48035bcf6947d646b44e46b8 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 9 Jan 2015 11:09:44 -0800 Subject: [PATCH 204/472] mmc: sdhci-msm: use the generic sdhci_reset() The reset functionality has been converted to a library function. Update the sdhci_ops function table to use the generic sdhci_reset() library function. Change-Id: I6c535cd5a641e23399bad284e9a1f89ac8811e62 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f2b7d820ccb1..3f242fa5307d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2767,6 +2767,7 @@ static struct sdhci_ops sdhci_msm_ops = { .config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd, .enable_controller_clock = sdhci_msm_enable_controller_clock, .set_bus_width = sdhci_set_bus_width, + .reset = sdhci_reset, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, From 3ef53ed8d2dd08999120f97c77ba0f8e2ec40047 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 9 Jan 2015 12:19:16 -0800 Subject: [PATCH 205/472] mmc: sdhci-msm: Update mmc DDR timing mode A new timing mode has been introduced to differentiate SD/MMC DDR timing modes. Use the MMC specific DDR timing mode in addition to the SD specific one when checking for timing modes. Change-Id: Id151c0fd0dc8eccdec8f842a5ee13af415b7ffe6 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3f242fa5307d..b3237a710b3c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2521,6 +2521,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) sup_clock = sdhci_msm_get_sup_clk_rate(host, clock); if ((curr_ios.timing == MMC_TIMING_UHS_DDR50) || + (curr_ios.timing == MMC_TIMING_MMC_DDR52) || (curr_ios.timing == MMC_TIMING_MMC_HS400)) { /* * The SDHC requires internal clock frequency to be double the @@ -2645,9 +2646,9 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, ctrl_2 = sdhci_readw(host, SDHCI_HOST_CONTROL2); /* Select Bus Speed Mode for host */ ctrl_2 &= ~SDHCI_CTRL_UHS_MASK; - if (uhs == MMC_TIMING_MMC_HS400) - ctrl_2 |= SDHCI_CTRL_UHS_SDR104; - else if (uhs == MMC_TIMING_MMC_HS200) + if ((uhs == MMC_TIMING_MMC_HS400) || + (uhs == MMC_TIMING_MMC_HS200) || + (uhs == MMC_TIMING_UHS_SDR104)) ctrl_2 |= SDHCI_CTRL_UHS_SDR104; else if (uhs == MMC_TIMING_UHS_SDR12) ctrl_2 |= SDHCI_CTRL_UHS_SDR12; @@ -2655,9 +2656,8 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, ctrl_2 |= SDHCI_CTRL_UHS_SDR25; else if (uhs == MMC_TIMING_UHS_SDR50) ctrl_2 |= SDHCI_CTRL_UHS_SDR50; - else if (uhs == MMC_TIMING_UHS_SDR104) - ctrl_2 |= SDHCI_CTRL_UHS_SDR104; - else if (uhs == MMC_TIMING_UHS_DDR50) + else if ((uhs == MMC_TIMING_UHS_DDR50) || + (uhs == MMC_TIMING_MMC_DDR52)) ctrl_2 |= SDHCI_CTRL_UHS_DDR50; /* * When clock frquency is less than 100MHz, the feedback clock must be From 8379862de2736e96861ff2fadbd1e21e335df1b0 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 21 Jan 2015 15:51:37 -0800 Subject: [PATCH 206/472] mmc: sdhci-msm: Disable pm functionalities Disable the following pm functionalities till they are functional: * clk scaling (need to verify full functionality) * clk gating (crashes when ungating the clock) Change-Id: I0962d46d85415e77b978e1f7dd9f2f49446655bb Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b3237a710b3c..ebc160a17ce6 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1769,9 +1769,11 @@ static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable) * after SDHCI_MSM_MMC_CLK_GATE_DELAY and thus no * additional delay is required to remove the bus vote. */ +#ifdef MMC_CLKGATE if (host->mmc->clkgate_delay) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); else +#endif sdhci_msm_bus_queue_work(host); } } @@ -2506,7 +2508,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) curr_pwrsave = !!(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & CORE_CLK_PWRSAVE); if ((clock > 400000) && - !curr_pwrsave && mmc_host_may_gate_card(host->mmc->card)) + !curr_pwrsave) writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | CORE_CLK_PWRSAVE, host->ioaddr + CORE_VENDOR_SPEC); @@ -2514,7 +2516,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) * Disable pwrsave for a newly added card if doesn't allow clock * gating. */ - else if (curr_pwrsave && !mmc_host_may_gate_card(host->mmc->card)) + else if (curr_pwrsave) writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & ~CORE_CLK_PWRSAVE, host->ioaddr + CORE_VENDOR_SPEC); @@ -3074,10 +3076,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Enable pwr irq interrupts */ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); - +#ifdef MMC_CLKGATE /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */ msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY; - +#endif /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; @@ -3086,7 +3088,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; - msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; From 8b70321d56d4c4ca94b6a165ea22d45387d47c61 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 23 Jan 2015 12:58:00 -0800 Subject: [PATCH 207/472] mmc: sdhci: Fix unclocked access during MMC_POWER_UP During MMC_POWER_UP the clocks are turned off when enabling power to the card. Make sure the controller clocks are enabled for normal register access. Change-Id: Iad6fc4dfc6e44d0a609b66432b0985d589c3e5b9 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index e9d6472d7ccf..3b167c6939ff 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1719,6 +1719,27 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) !(host->quirks2 & SDHCI_QUIRK2_PRESET_VALUE_BROKEN)) sdhci_enable_preset_value(host, false); + spin_lock_irqsave(&host->lock, flags); + if (ios->clock || ios->clock != host->clock) { + spin_unlock_irqrestore(&host->lock, flags); + host->ops->set_clock(host, ios->clock); + spin_lock_irqsave(&host->lock, flags); + host->clock = ios->clock; + + if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK && + host->clock) { + host->timeout_clk = host->mmc->actual_clock ? + host->mmc->actual_clock / 1000 : + host->clock / 1000; + host->mmc->max_busy_timeout = + host->ops->get_max_timeout_count ? + host->ops->get_max_timeout_count(host) : + 1 << 27; + host->mmc->max_busy_timeout /= host->timeout_clk; + } + } + spin_unlock_irqrestore(&host->lock, flags); + /* * The controller clocks may be off during power-up and we may end up * enabling card clock before giving power to the card. Hence, during @@ -1739,23 +1760,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) } spin_lock_irqsave(&host->lock, flags); - if (!ios->clock || ios->clock != host->clock) { + if (!host->clock) { spin_unlock_irqrestore(&host->lock, flags); - host->ops->set_clock(host, ios->clock); - spin_lock_irqsave(&host->lock, flags); - host->clock = ios->clock; - - if (host->quirks & SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK && - host->clock) { - host->timeout_clk = host->mmc->actual_clock ? - host->mmc->actual_clock / 1000 : - host->clock / 1000; - host->mmc->max_busy_timeout = - host->ops->get_max_timeout_count ? - host->ops->get_max_timeout_count(host) : - 1 << 27; - host->mmc->max_busy_timeout /= host->timeout_clk; - } + return; } spin_unlock_irqrestore(&host->lock, flags); From 3426f39922bb0dec96a07a270cea0937f0096579 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 3 Feb 2015 16:10:07 -0800 Subject: [PATCH 208/472] mmc: sdhci-msm: Update DLL reset sequence The latest version of the SDCC core requires a change in the reset sequence for DLL tuning. Make necessary changes as needed. Change-Id: I69e972c08e89efebff9822de6d0e59692784652e Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ebc160a17ce6..96a16ac05a9c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -153,6 +153,8 @@ #define CORE_DLL_CONFIG_2 0x1B4 #define CORE_DDR_CAL_EN (1 << 0) +#define CORE_FLL_CYCLE_CNT (1 << 18) +#define CORE_DLL_CLOCK_DISABLE (1 << 21) #define CORE_DDR_CONFIG 0x1B8 #define DDR_CONFIG_POR_VAL 0x80040853 @@ -162,6 +164,7 @@ #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ #define CORE_FREQ_100MHZ (100 * 1000 * 1000) +#define TCXO_FREQ 19200000 #define INVALID_TUNING_PHASE -1 @@ -313,6 +316,7 @@ struct sdhci_msm_host { struct device_attribute auto_cmd21_attr; atomic_t controller_clock; bool use_cdclp533; + bool use_updated_dll_reset; }; enum vdd_io_level { @@ -643,6 +647,8 @@ static inline void msm_cm_dll_set_freq(struct sdhci_host *host) /* Initialize the DLL (Programmable Delay Line ) */ static int msm_init_cm_dll(struct sdhci_host *host) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; struct mmc_host *mmc = host->mmc; int rc = 0; unsigned long flags; @@ -667,6 +673,17 @@ static int msm_init_cm_dll(struct sdhci_host *host) curr_pwrsave = false; } + if (msm_host->use_updated_dll_reset) { + /* Disable the DLL clock */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) + & ~CORE_CK_OUT_EN), + host->ioaddr + CORE_DLL_CONFIG); + + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG_2) + | CORE_DLL_CLOCK_DISABLE), + host->ioaddr + CORE_DLL_CONFIG_2); + } + /* Write 1 to DLL_RST bit of DLL_CONFIG register */ writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) | CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); @@ -676,6 +693,22 @@ static int msm_init_cm_dll(struct sdhci_host *host) | CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); msm_cm_dll_set_freq(host); + if (msm_host->use_updated_dll_reset) { + u32 mclk_freq = 0; + + if ((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG_2) + & CORE_FLL_CYCLE_CNT)) + mclk_freq = (u32) ((host->clock / TCXO_FREQ) * 8); + else + mclk_freq = (u32) ((host->clock / TCXO_FREQ) * 4); + + writel_relaxed(((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG_2) + & ~(0xFF << 10)) | (mclk_freq << 10)), + host->ioaddr + CORE_DLL_CONFIG_2); + /* wait for 5us before enabling DLL clock */ + udelay(5); + } + /* Write 0 to DLL_RST bit of DLL_CONFIG register */ writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & ~CORE_DLL_RST), host->ioaddr + CORE_DLL_CONFIG); @@ -684,6 +717,14 @@ static int msm_init_cm_dll(struct sdhci_host *host) writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) & ~CORE_DLL_PDN), host->ioaddr + CORE_DLL_CONFIG); + if (msm_host->use_updated_dll_reset) { + msm_cm_dll_set_freq(host); + /* Enable the DLL clock */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG_2) + & ~CORE_DLL_CLOCK_DISABLE), + host->ioaddr + CORE_DLL_CONFIG_2); + } + /* Set DLL_EN bit to 1. */ writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG) | CORE_DLL_EN), host->ioaddr + CORE_DLL_CONFIG); @@ -2814,6 +2855,13 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, if ((major == 1) && (minor < 0x34)) msm_host->use_cdclp533 = true; + /* + * SDCC 5 controller with major version 1, minor version 0x42 and later + * will require additional steps when resetting DLL. + */ + if ((major == 1) && (minor >= 0x42)) + msm_host->use_updated_dll_reset = true; + /* * Mask 64-bit support for controller with 32-bit address bus so that * smaller descriptor size will be used and improve memory consumption. From 011fb838d71aa00cc18a3254524885286e7489c6 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 6 Feb 2015 13:27:25 -0800 Subject: [PATCH 209/472] mmc: sdhci-msm: Disable packed commands Disable packed commands on msm-3.14 kernel till failures are addressed. Change-Id: Id7cd4ed1235d4697dafb4f0876ee6598f4de75eb Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 96a16ac05a9c..a1f8aa5fb27f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3132,8 +3132,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; msm_host->mmc->caps2 |= msm_host->pdata->caps2; - msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR; - msm_host->mmc->caps2 |= MMC_CAP2_PACKED_WR_CONTROL; msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; From 4f32644877e95bbd76894683fced2ef601a76eae Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 10 Feb 2015 13:10:12 +0200 Subject: [PATCH 210/472] mmc: quirks: add support for quirks based on EXT_CSD_REV This change allows the usage of quirks based on the register value of EXT_CSD_REV. It was seen for several eMMC cards that same issues, such as data corruption while using cache, were relevant for all eMMC cards having the same EXT_CSD_REV value. This change allows us to distinguish between cards based on this register. Change-Id: I1663891c367a59b520bc505641c6c4ddad56fd1a Signed-off-by: Talel Shenhar --- drivers/mmc/card/block.c | 5 +++++ drivers/mmc/core/quirks.c | 2 ++ include/linux/mmc/card.h | 39 ++++++++++++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 28048f872850..86192704e583 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3055,6 +3055,11 @@ force_ro_fail: #define CID_MANFID_SAMSUNG 0x15 #define CID_MANFID_KINGSTON 0x70 +#define CID_MANFID_SANDISK 0x2 +#define CID_MANFID_TOSHIBA 0x11 +#define CID_MANFID_MICRON 0x13 +#define CID_MANFID_SAMSUNG 0x15 + static const struct mmc_fixup blk_fixups[] = { MMC_FIXUP("SEM02G", CID_MANFID_SANDISK, 0x100, add_quirk, diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index 562957a37704..1fb9fe1838c7 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -129,6 +129,8 @@ void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table) (f->name == CID_NAME_ANY || !strncmp(f->name, card->cid.prod_name, sizeof(card->cid.prod_name))) && + (f->ext_csd_rev == EXT_CSD_REV_ANY || + f->ext_csd_rev == card->ext_csd.rev) && (f->cis_vendor == card->cis.vendor || f->cis_vendor == (u16) SDIO_ANY_ID) && (f->cis_device == card->cis.device || diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 809ba6f07147..a9c756549c2f 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -386,19 +386,41 @@ struct mmc_fixup { /* SDIO-specfic fields. You can use SDIO_ANY_ID here of course */ u16 cis_vendor, cis_device; + /* MMC-specific field, You can use EXT_CSD_REV_ANY here of course */ + unsigned int ext_csd_rev; + void (*vendor_fixup)(struct mmc_card *card, int data); int data; }; +#define CID_MANFID_SANDISK 0x2 +#define CID_MANFID_TOSHIBA 0x11 +#define CID_MANFID_MICRON 0x13 +#define CID_MANFID_SAMSUNG 0x15 + #define CID_MANFID_ANY (-1u) #define CID_OEMID_ANY ((unsigned short) -1) #define CID_NAME_ANY (NULL) +#define EXT_CSD_REV_ANY (-1u) #define END_FIXUP { NULL } +/* extended CSD mapping to mmc version */ +enum mmc_version_ext_csd_rev { + MMC_V4_0, + MMC_V4_1, + MMC_V4_2, + MMC_V4_41 = 5, + MMC_V4_5, + MMC_V4_51 = MMC_V4_5, + MMC_V5_0, + MMC_V5_01 = MMC_V5_0, + MMC_V5_1 +}; + #define _FIXUP_EXT(_name, _manfid, _oemid, _rev_start, _rev_end, \ _cis_vendor, _cis_device, \ - _fixup, _data) \ + _fixup, _data, _ext_csd_rev) \ { \ .name = (_name), \ .manfid = (_manfid), \ @@ -409,23 +431,30 @@ struct mmc_fixup { .cis_device = (_cis_device), \ .vendor_fixup = (_fixup), \ .data = (_data), \ + .ext_csd_rev = (_ext_csd_rev), \ } #define MMC_FIXUP_REV(_name, _manfid, _oemid, _rev_start, _rev_end, \ - _fixup, _data) \ + _fixup, _data, _ext_csd_rev) \ _FIXUP_EXT(_name, _manfid, \ _oemid, _rev_start, _rev_end, \ SDIO_ANY_ID, SDIO_ANY_ID, \ - _fixup, _data) \ + _fixup, _data, _ext_csd_rev) \ #define MMC_FIXUP(_name, _manfid, _oemid, _fixup, _data) \ - MMC_FIXUP_REV(_name, _manfid, _oemid, 0, -1ull, _fixup, _data) + MMC_FIXUP_REV(_name, _manfid, _oemid, 0, -1ull, _fixup, _data, \ + EXT_CSD_REV_ANY) + +#define MMC_FIXUP_EXT_CSD_REV(_name, _manfid, _oemid, _fixup, _data, \ + _ext_csd_rev) \ + MMC_FIXUP_REV(_name, _manfid, _oemid, 0, -1ull, _fixup, _data, \ + _ext_csd_rev) #define SDIO_FIXUP(_vendor, _device, _fixup, _data) \ _FIXUP_EXT(CID_NAME_ANY, CID_MANFID_ANY, \ CID_OEMID_ANY, 0, -1ull, \ _vendor, _device, \ - _fixup, _data) \ + _fixup, _data, EXT_CSD_REV_ANY) \ #define cid_rev(hwrev, fwrev, year, month) \ (((u64) hwrev) << 40 | \ From d3254ba7a0f2956dd9da87d575ad83a8b9914b15 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 11 Feb 2015 12:58:16 +0200 Subject: [PATCH 211/472] mmc: block: handle flush request timeout In case of a flush request timeout, an error is returned to the block layer. The problem with this is that the eMMC card is left in programming state and while the device is in the programming state it cannot serve any request. This commit moves the card out of the programming state, in case of timeout, by issuing HPI, thereby allowing the device to continue serving requests. Some filesystems, such as EXT4, remount the partition as read-only after receiving an error from the block layer, thus this change will allow the remounted partition to work as the card can serve read request thanks to the HPI. In case where the card doesn't even respond to HPI it cannot serve any request, thereby, this commit reset the card in such catastrophic cases. Change-Id: Idbca6ff3a420a954c61cf4fb79c9094542888d89 Signed-off-by: Talel Shenhar --- drivers/mmc/card/block.c | 16 +++++++++++++++- drivers/mmc/core/core.c | 12 +++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 86192704e583..f35cb71baede 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -129,6 +129,7 @@ struct mmc_blk_data { #define MMC_BLK_WRITE BIT(1) #define MMC_BLK_DISCARD BIT(2) #define MMC_BLK_SECDISCARD BIT(3) +#define MMC_BLK_FLUSH BIT(4) /* * Only set in main mmc_blk_data associated @@ -1565,8 +1566,21 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) int ret = 0; ret = mmc_flush_cache(card); - if (ret) + if (ret == -ENODEV) { + pr_err("%s: %s: restart mmc card", + req->rq_disk->disk_name, __func__); + if (mmc_blk_reset(md, card->host, MMC_BLK_FLUSH)) + pr_err("%s: %s: fail to restart mmc", + req->rq_disk->disk_name, __func__); + else + mmc_blk_reset_success(md, MMC_BLK_FLUSH); + } + + if (ret) { + pr_err("%s: %s: notify flush error to upper layers", + req->rq_disk->disk_name, __func__); ret = -EIO; + } blk_end_request_all(req, ret); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9e60bd2d021c..b0aacd2f78d8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3357,9 +3357,19 @@ int mmc_flush_cache(struct mmc_card *card) (card->ext_csd.cache_ctrl & 1)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_FLUSH_CACHE, 1, 0); - if (err) + if (err == -ETIMEDOUT) { + pr_err("%s: cache flush timeout\n", + mmc_hostname(card->host)); + err = mmc_interrupt_hpi(card); + if (err) { + pr_err("%s: mmc_interrupt_hpi() failed (%d)\n", + mmc_hostname(card->host), err); + err = -ENODEV; + } + } else if (err) { pr_err("%s: cache flush error %d\n", mmc_hostname(card->host), err); + } } return err; From a89dfb1bbd09b7625eca50ce02448a517c7156c6 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Thu, 5 Feb 2015 14:44:15 +0200 Subject: [PATCH 212/472] mmc: quirks: add new quirk that allows HPI disable Certain cards might get broken when HPI feature is used. This patch allows host to avoid using HPI for such buggy cards by adding new quirk. As some of the other features like BKOPs/Cache are dependent on HPI feature, those features would also get disabled if HPI is disabled. Change-Id: I93a8810e4031eafcd44b5152296e065dc3330b63 Signed-off-by: Talel Shenhar --- drivers/mmc/core/mmc.c | 22 ++++++++++++++++++++-- drivers/mmc/core/mmc_ops.c | 2 +- include/linux/mmc/card.h | 2 ++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 93b0b596dd20..087e9ea1fda0 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -490,13 +490,30 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) ext_csd[EXT_CSD_PWR_CL_DDR_200_360]; } + /* check whether the eMMC card supports HPI */ + if ((ext_csd[EXT_CSD_HPI_FEATURES] & 0x1) && + !(card->quirks & MMC_QUIRK_BROKEN_HPI)) { + card->ext_csd.hpi = 1; + if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x2) + card->ext_csd.hpi_cmd = MMC_STOP_TRANSMISSION; + else + card->ext_csd.hpi_cmd = MMC_SEND_STATUS; + /* + * Indicate the maximum timeout to close + * a command interrupted by HPI + */ + card->ext_csd.out_of_int_time = + ext_csd[EXT_CSD_OUT_OF_INTERRUPT_TIME] * 10; + } + if (card->ext_csd.rev >= 5) { /* Adjust production date as per JEDEC JESD84-B451 */ if (card->cid.year < 2010) card->cid.year += 16; /* check whether the eMMC card supports BKOPS */ - if (ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1) { + if ((ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1) && + card->ext_csd.hpi) { card->ext_csd.bkops = 1; card->ext_csd.man_bkops_en = (ext_csd[EXT_CSD_BKOPS_EN] & @@ -1788,8 +1805,9 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* * If cache size is higher than 0, this indicates * the existence of cache and it can be turned on. + * If HPI is not supported then cache shouldn't be enabled. */ - if (card->ext_csd.cache_size > 0) { + if (card->ext_csd.cache_size > 0 && card->ext_csd.hpi_en) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CACHE_CTRL, 1, card->ext_csd.generic_cmd6_time); diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 91248c14af01..6384bf34660f 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -767,7 +767,7 @@ int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status) unsigned int opcode; int err; - if (!card->ext_csd.hpi) { + if (!card->ext_csd.hpi_en) { pr_warn("%s: Card didn't support HPI command\n", mmc_hostname(card->host)); return -EINVAL; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index a9c756549c2f..a16fd1739371 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -306,6 +306,8 @@ struct mmc_card { #define MMC_QUIRK_TRIM_BROKEN (1<<12) /* Skip trim */ /* byte mode */ #define MMC_QUIRK_INAND_DATA_TIMEOUT (1<<13) /* For incorrect data timeout */ +#define MMC_QUIRK_BROKEN_HPI (1 << 14) /* For devices which gets */ + /* broken due to HPI feature */ unsigned int erase_size; /* erase size in sectors */ From 96c1215f259dc55a463dd0094a61f86798358f5a Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Sun, 8 Feb 2015 13:36:57 +0200 Subject: [PATCH 213/472] mmc: block: avoid HPI for some Kingston cards Certain cards might get broken when HPI feature is used. This patch uses the HPI quirk to avoid the usage of HPI on Kingston MMC16G cards that have EXT_CSD_REV = 5 (mmc version 4.41) Change-Id: I4572eb9e71a281b56e25e5b4864d5777b16e2bc2 Signed-off-by: Talel Shenhar --- drivers/mmc/core/mmc.c | 9 +++++++++ include/linux/mmc/card.h | 1 + 2 files changed, 10 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 087e9ea1fda0..245cd173eb45 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -125,6 +125,15 @@ static void mmc_set_erase_size(struct mmc_card *card) mmc_init_erase(card); } +static const struct mmc_fixup mmc_fixups[] = { + + /* avoid HPI for specific cards */ + MMC_FIXUP_EXT_CSD_REV("MMC16G", CID_MANFID_KINGSTON, CID_OEMID_ANY, + add_quirk, MMC_QUIRK_BROKEN_HPI, MMC_V4_41), + + END_FIXUP +}; + /* * Given a 128-bit response, decode to our card CSD structure. */ diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index a16fd1739371..4cab91c56616 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -399,6 +399,7 @@ struct mmc_fixup { #define CID_MANFID_TOSHIBA 0x11 #define CID_MANFID_MICRON 0x13 #define CID_MANFID_SAMSUNG 0x15 +#define CID_MANFID_KINGSTON 0x70 #define CID_MANFID_ANY (-1u) #define CID_OEMID_ANY ((unsigned short) -1) From 4a897cf6aa31fbb9e81dab20ac5f73515499b89d Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 4 Feb 2015 17:59:23 +0200 Subject: [PATCH 214/472] mmc: quirks: add new quirk that allows Cache disable This change allows us to prevent cache enable for certain cards that have broken cache functionality. Change-Id: Iea3f8c8f4e5498a8742fa408a19e3e169d1fa8cb Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 3 ++- drivers/mmc/core/mmc.c | 54 +++++++++++++++++++++++++++------------- include/linux/mmc/card.h | 1 + 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b0aacd2f78d8..23ee6dc61e09 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3354,7 +3354,8 @@ int mmc_flush_cache(struct mmc_card *card) if (mmc_card_mmc(card) && (card->ext_csd.cache_size > 0) && - (card->ext_csd.cache_ctrl & 1)) { + (card->ext_csd.cache_ctrl & 1) && + (!(card->quirks & MMC_QUIRK_CACHE_DISABLE))) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_FLUSH_CACHE, 1, 0); if (err == -ETIMEDOUT) { diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 245cd173eb45..c0faf2bbc711 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1816,26 +1816,46 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * the existence of cache and it can be turned on. * If HPI is not supported then cache shouldn't be enabled. */ - if (card->ext_csd.cache_size > 0 && card->ext_csd.hpi_en) { - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_CACHE_CTRL, 1, - card->ext_csd.generic_cmd6_time); - if (err && err != -EBADMSG) { - pr_err("%s: %s: mmc_switch() for CACHE_CTRL fails %d\n", + if (card->ext_csd.cache_size > 0) { + if (card->ext_csd.hpi_en && + (!(card->quirks & MMC_QUIRK_CACHE_DISABLE))) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CACHE_CTRL, 1, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) { + pr_err("%s: %s: fail on CACHE_CTRL ON %d\n", mmc_hostname(host), __func__, err); - goto free_card; - } + goto free_card; + } - /* - * Only if no error, cache is turned on successfully. - */ - if (err) { - pr_warn("%s: Cache is supported, but failed to turn on (%d)\n", - mmc_hostname(card->host), err); - card->ext_csd.cache_ctrl = 0; - err = 0; + /* + * Only if no error, cache is turned on successfully. + */ + if (err) { + pr_warn("%s: Cache is supported, but failed to turn on (%d)\n", + mmc_hostname(card->host), err); + card->ext_csd.cache_ctrl = 0; + err = 0; + } else { + card->ext_csd.cache_ctrl = 1; + } } else { - card->ext_csd.cache_ctrl = 1; + /* + * mmc standard doesn't say what is the card default + * value for EXT_CSD_CACHE_CTRL. + * Hence, cache may be enabled by default by + * card vendors. + * Thus, it is best to explicitly disable cache in case + * we want to avoid cache. + */ + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CACHE_CTRL, 0, + card->ext_csd.generic_cmd6_time); + if (err) { + pr_err("%s: %s: fail on CACHE_CTRL OFF %d\n", + mmc_hostname(host), __func__, err); + goto free_card; + } } } diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 4cab91c56616..c3e6fbfbcfab 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -308,6 +308,7 @@ struct mmc_card { #define MMC_QUIRK_INAND_DATA_TIMEOUT (1<<13) /* For incorrect data timeout */ #define MMC_QUIRK_BROKEN_HPI (1 << 14) /* For devices which gets */ /* broken due to HPI feature */ +#define MMC_QUIRK_CACHE_DISABLE (1 << 14) /* prevent cache enable */ unsigned int erase_size; /* erase size in sectors */ From 3fe677ea14bf7984d15757743383d4c2a61fc7f6 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Thu, 12 Feb 2015 10:12:39 +0200 Subject: [PATCH 215/472] mmc: card: disable cache for specific Kingston cards This change prevents enabling cache for cards with product name MMC16G. This change is workaround the problem of long timeouts for cache operations. Change-Id: Ib683809faed8afc74bf0faff8636ead210013e6f Signed-off-by: Talel Shenhar --- drivers/mmc/core/mmc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index c0faf2bbc711..5938ff5e96b0 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -131,6 +131,10 @@ static const struct mmc_fixup mmc_fixups[] = { MMC_FIXUP_EXT_CSD_REV("MMC16G", CID_MANFID_KINGSTON, CID_OEMID_ANY, add_quirk, MMC_QUIRK_BROKEN_HPI, MMC_V4_41), + /* Disable cache for specific cards */ + MMC_FIXUP("MMC16G", CID_MANFID_KINGSTON, CID_OEMID_ANY, + add_quirk_mmc, MMC_QUIRK_CACHE_DISABLE), + END_FIXUP }; From 1e5451c60daddd58d3b6f200d623debaf2ded069 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 12 Feb 2015 20:37:31 +0200 Subject: [PATCH 216/472] mmc: card: fix asymmetric claim host in mmc_blk_ioctl_rpmb_cmd On kernel-3.14 mmc_claim_host is replaced by mmc_get_card to also call pm_runtime_get_sync. mmc_blk_ioctl_rpmb_cmd has asymmetric claim and release as mmc_claim_host was used to get the lock, but mmc_put_card was used for the release. To fix this and prevent bad counter of runtime PM, mmc_claim_host should be replaced with mmc_get_card. Change-Id: I7c2218623fddfbeed0489aed330c9fe6e8bc5338 Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index f35cb71baede..31e028cfff80 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -778,7 +778,7 @@ static int mmc_blk_ioctl_rpmb_cmd(struct block_device *bdev, goto idata_free; } - mmc_claim_host(card->host); + mmc_get_card(card); err = mmc_blk_part_switch(card, md); if (err) From 0baf8a894ac77c714a6883024e92a3743b2c8e38 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 28 Jan 2015 14:44:57 +0200 Subject: [PATCH 217/472] mmc: block: use pr_err_ratelimited for command errors Switch to using pr_err_ratelimited in order to avoid flooding the logs in case of error function gets called repeatedly. Change-Id: I636dc933915127a43ad4da87a565f8f585e6df90 Signed-off-by: Talel Shenhar --- drivers/mmc/card/block.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 31e028cfff80..2c56a909092b 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1237,18 +1237,21 @@ static int mmc_blk_cmd_error(struct request *req, const char *name, int error, switch (error) { case -EILSEQ: /* response crc error, retry the r/w cmd */ - pr_err("%s: %s sending %s command, card status %#x\n", - req->rq_disk->disk_name, "response CRC error", + pr_err_ratelimited( + "%s: response CRC error sending %s command, card status %#x\n", + req->rq_disk->disk_name, name, status); return ERR_RETRY; case -ETIMEDOUT: - pr_err("%s: %s sending %s command, card status %#x\n", - req->rq_disk->disk_name, "timed out", name, status); + pr_err_ratelimited( + "%s: timed out sending %s command, card status %#x\n", + req->rq_disk->disk_name, name, status); /* If the status cmd initially failed, retry the r/w cmd */ if (!status_valid) { - pr_err("%s: status not valid, retrying timeout\n", req->rq_disk->disk_name); + pr_err_ratelimited("%s: status not valid, retrying timeout\n", + req->rq_disk->disk_name); return ERR_RETRY; } /* @@ -1257,17 +1260,22 @@ static int mmc_blk_cmd_error(struct request *req, const char *name, int error, * have corrected the state problem above. */ if (status & (R1_COM_CRC_ERROR | R1_ILLEGAL_COMMAND)) { - pr_err("%s: command error, retrying timeout\n", req->rq_disk->disk_name); + pr_err_ratelimited( + "%s: command error, retrying timeout\n", + req->rq_disk->disk_name); return ERR_RETRY; } /* Otherwise abort the command */ - pr_err("%s: not retrying timeout\n", req->rq_disk->disk_name); + pr_err_ratelimited( + "%s: not retrying timeout\n", + req->rq_disk->disk_name); return ERR_ABORT; default: /* We don't understand the error code the driver gave us */ - pr_err("%s: unknown error %d sending read/write command, card status %#x\n", + pr_err_ratelimited( + "%s: unknown error %d sending read/write command, card status %#x\n", req->rq_disk->disk_name, error, status); return ERR_ABORT; } From 07543f5f7695a5d4385e8e690aa5d748787f4e2e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 23 Feb 2015 18:55:48 -0800 Subject: [PATCH 218/472] mmc: sdhci: Fix mmc_power_off sequence During mmc_power_off in sdhci_do_set_ios clock to the card needs to be turned off as a last step after the power is turned off. The existing check turns off the clock first and thereby never turns off the power. Change-Id: I961b090c814d9182aff11da7a3b090d9a84fc299 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 3b167c6939ff..818758b0d89f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1720,7 +1720,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_enable_preset_value(host, false); spin_lock_irqsave(&host->lock, flags); - if (ios->clock || ios->clock != host->clock) { + if (ios->clock && ios->clock != host->clock) { spin_unlock_irqrestore(&host->lock, flags); host->ops->set_clock(host, ios->clock); spin_lock_irqsave(&host->lock, flags); From bc88aab40444b80817d281babe06059075937d18 Mon Sep 17 00:00:00 2001 From: Konstantein Dorfman Date: Tue, 27 Jan 2015 12:06:02 +0200 Subject: [PATCH 219/472] mmc: sdhci-msm: move pwr_irq to sdhci_msm_host Keep pwr_irq as field in sdhci_msm_host struct instead of local sdhci_msm_probe variable. It will be used later in power management callbacks. Change-Id: I7b3629b958e953bab66c32e6131b7fd577c7c9f2 Signed-off-by: Konstantin Dorfman [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index a1f8aa5fb27f..da7b9ed1187c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -294,6 +294,7 @@ struct sdhci_msm_bus_vote { struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ + int pwr_irq; /* power irq */ struct clk *clk; /* main SD/MMC bus clock */ struct clk *pclk; /* SDHC peripheral bus clock */ struct clk *bus_clk; /* SDHC bus voter clock */ @@ -2879,7 +2880,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) struct sdhci_pltfm_host *pltfm_host; struct sdhci_msm_host *msm_host; struct resource *core_memres = NULL; - int ret = 0, pwr_irq = 0, dead = 0; + int ret = 0, dead = 0; u16 host_version; u32 pwr, irq_status, irq_ctl; @@ -3107,18 +3108,18 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks2 |= SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR; /* Setup PWRCTL irq */ - pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); - if (pwr_irq < 0) { + msm_host->pwr_irq = platform_get_irq_byname(pdev, "pwr_irq"); + if (msm_host->pwr_irq < 0) { dev_err(&pdev->dev, "Failed to get pwr_irq by name (%d)\n", - pwr_irq); + msm_host->pwr_irq); goto vreg_deinit; } - ret = devm_request_threaded_irq(&pdev->dev, pwr_irq, NULL, + ret = devm_request_threaded_irq(&pdev->dev, msm_host->pwr_irq, NULL, sdhci_msm_pwr_irq, IRQF_ONESHOT, dev_name(&pdev->dev), host); if (ret) { dev_err(&pdev->dev, "Request threaded irq(%d) failed (%d)\n", - pwr_irq, ret); + msm_host->pwr_irq, ret); goto vreg_deinit; } From a5d50c0f53bc42f6e2c37301523096a48ef857c4 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Wed, 25 Feb 2015 10:09:41 +0200 Subject: [PATCH 220/472] mmc: sdhci-msm: add registration to pm core framework Add registration and initialization of platform driver to pm core for system suspend/resume and runtime suspend/resume. Platform device runtime power management uses the autosuspend pm core feature with MSM_AUTOSUSPEND_DELAY (100ms). MMC core power management is configured to aggressively suspend the card on runtime suspend (and full initialization on runtime resume). Change-Id: I45f0b2f1709361f41f7c38c57b95f692f8a0e486 Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/sdhci-msm.c | 102 ++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index da7b9ed1187c..1fb1c71e2b00 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -170,6 +171,7 @@ #define NUM_TUNING_PHASES 16 #define MAX_DRV_TYPES_SUPPORTED_HS200 3 +#define MSM_AUTOSUSPEND_DELAY_MS 100 static const u32 tuning_block_64[] = { 0x00FF0FFF, 0xCCC3CCFF, 0xFFCC3CC3, 0xEFFEFFFE, @@ -3132,6 +3134,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; + msm_host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM; msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; @@ -3184,6 +3187,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto free_cd_gpio; } + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, MSM_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&pdev->dev); + msm_host->msm_bus_vote.max_bus_bw.show = show_sdhci_max_bus_bw; msm_host->msm_bus_vote.max_bus_bw.store = store_sdhci_max_bus_bw; sysfs_attr_init(&msm_host->msm_bus_vote.max_bus_bw.attr); @@ -3216,7 +3224,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) mmc_hostname(host->mmc), __func__, ret); device_remove_file(&pdev->dev, &msm_host->auto_cmd21_attr); } - /* Successful initialization */ goto out; @@ -3224,6 +3231,7 @@ remove_max_bus_bw_file: device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); remove_host: dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); + pm_runtime_disable(&pdev->dev); sdhci_remove_host(host, dead); free_cd_gpio: if (gpio_is_valid(msm_host->pdata->status_gpio)) @@ -3269,6 +3277,7 @@ static int sdhci_msm_remove(struct platform_device *pdev) if (!gpio_is_valid(msm_host->pdata->status_gpio)) device_remove_file(&pdev->dev, &msm_host->polling); device_remove_file(&pdev->dev, &msm_host->msm_bus_vote.max_bus_bw); + pm_runtime_disable(&pdev->dev); sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); @@ -3287,6 +3296,96 @@ static int sdhci_msm_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int sdhci_msm_runtime_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + disable_irq(host->irq); + disable_irq(msm_host->pwr_irq); + + /* + * Remove the vote immediately only if clocks are off in which + * case we might have queued work to remove vote but it may not + * be completed before runtime suspend or system suspend. + */ + if (!atomic_read(&msm_host->clks_on)) { + if (msm_host->msm_bus_vote.client_handle) + sdhci_msm_bus_cancel_work_and_set_vote(host, 0); + } + + return 0; +} + +static int sdhci_msm_runtime_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + enable_irq(msm_host->pwr_irq); + enable_irq(host->irq); + + return 0; +} + +static int sdhci_msm_suspend(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (gpio_is_valid(msm_host->pdata->status_gpio)) + mmc_gpio_free_cd(msm_host->mmc); + + if (pm_runtime_suspended(dev)) { + pr_debug("%s: %s: already runtime suspended\n", + mmc_hostname(host->mmc), __func__); + goto out; + } + return sdhci_msm_runtime_suspend(dev); +out: + return 0; +} + +static int sdhci_msm_resume(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int ret = 0; + + if (gpio_is_valid(msm_host->pdata->status_gpio)) { + ret = mmc_gpio_request_cd(msm_host->mmc, + msm_host->pdata->status_gpio, 0); + if (ret) + pr_err("%s: %s: Failed to request card detection IRQ %d\n", + mmc_hostname(host->mmc), __func__, ret); + } + if (pm_runtime_suspended(dev)) { + pr_debug("%s: %s: runtime suspended, defer system resume\n", + mmc_hostname(host->mmc), __func__); + goto out; + } + + return sdhci_msm_runtime_resume(dev); +out: + return ret; +} + +static const struct dev_pm_ops sdhci_msm_pmops = { + SET_SYSTEM_SLEEP_PM_OPS(sdhci_msm_suspend, sdhci_msm_resume) + SET_RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume, + NULL) +}; + +#define SDHCI_MSM_PMOPS (&sdhci_msm_pmops) + +#else +#define SDHCI_MSM_PMOPS NULL +#endif static const struct of_device_id sdhci_msm_dt_match[] = { {.compatible = "qcom,sdhci-msm"}, }; @@ -3299,6 +3398,7 @@ static struct platform_driver sdhci_msm_driver = { .name = "sdhci_msm", .owner = THIS_MODULE, .of_match_table = sdhci_msm_dt_match, + .pm = SDHCI_MSM_PMOPS, }, }; From fbd98e0c5c1b0c01d9bfb33959ae75b0cbebfaaf Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 12 Feb 2015 11:23:32 +0200 Subject: [PATCH 221/472] mmc: core: fix disable clock scaling Disable clock scaling only when clock scaling was properly initialized. Change-Id: I7ec45e49c5ce18ea6aef0e272e79325fa8952c5b Signed-off-by: Konstantin Dorfman --- drivers/mmc/core/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 23ee6dc61e09..da5927ed5772 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3015,8 +3015,10 @@ out: */ void mmc_disable_clk_scaling(struct mmc_host *host) { - cancel_delayed_work_sync(&host->clk_scaling.work); - host->clk_scaling.enable = false; + if (host->clk_scaling.initialized) { + cancel_delayed_work_sync(&host->clk_scaling.work); + host->clk_scaling.enable = false; + } } EXPORT_SYMBOL_GPL(mmc_disable_clk_scaling); From b8be10334c1dfb5a0048afeda77cb553b585556e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 2 Mar 2015 13:22:40 -0800 Subject: [PATCH 222/472] mmc: sdhci: Fix setting clock for HS400 The platform specific set_clock must be called even if the clock rates are same but the timing modes are different as there may be some platform specific handling required for different timing modes. Change-Id: Ia7102b29a8979ad88820c60ed5874d4b2dab71a0 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 818758b0d89f..995e3ca6890f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1720,7 +1720,8 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_enable_preset_value(host, false); spin_lock_irqsave(&host->lock, flags); - if (ios->clock && ios->clock != host->clock) { + if (ios->clock && + ((ios->clock != host->clock) || (ios->timing != host->timing))) { spin_unlock_irqrestore(&host->lock, flags); host->ops->set_clock(host, ios->clock); spin_lock_irqsave(&host->lock, flags); From dee93fdbd290044066d783987cd7fb163b59a582 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Thu, 30 Oct 2014 21:13:27 -0700 Subject: [PATCH 223/472] mmc: sdhci-msm: support dual voltage pad for eMMC Only 1.8V is used on IO pad lines for eMMCs' on msm platforms. So this means for any SDHC core on msm platforms used with eMMCs' should have PAD_PWR_SWITCH bit set indicating that 1.8V is used for IO lines. So far this did not matter as all eMMCs' SDHC pads were single voltage and the PAD_PWR_SWTICH bit did not make a difference. But when a SDHC core that supports dual voltage pad is used for initializing eMMC, the BUS_ON pwr_irq generated would cause the pad to be set at a logical HIGH in the driver (as it should since the bus is being turned ON) but then the driver does not set or clear the PAD_PWR_SWITCH for eMMCs which causes the communication between the host and card to break down. So even though the IO pad is driven at 1.8V, the host is not told that it should switch to 1.8V IO from 3.0V (default). This does not matter for SD cards for when they are turned ON, they always start with 3.0V on IO pad. But for eMMC instead of not setting or clearing the PAD_PWR_SWITCH bit when the bus is turned ON, check the voltage being supplied on the IO pad and based on that set or clear the PAD_PWR_SWITCH. Change-Id: I7756731760298c6766d00a22160c6328faaaf4a7 Signed-off-by: Krishna Konda --- drivers/mmc/host/sdhci-msm.c | 44 ++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 1fb1c71e2b00..2af0bdd9686f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -101,11 +101,12 @@ #define CORE_DDR_DLL_LOCK (1 << 11) #define CORE_VENDOR_SPEC 0x10C -#define CORE_CLK_PWRSAVE (1 << 1) -#define CORE_HC_MCLK_SEL_DFLT (2 << 8) -#define CORE_HC_MCLK_SEL_HS400 (3 << 8) -#define CORE_HC_MCLK_SEL_MASK (3 << 8) -#define CORE_HC_AUTO_CMD21_EN (1 << 6) +#define CORE_CLK_PWRSAVE (1 << 1) +#define CORE_HC_MCLK_SEL_DFLT (2 << 8) +#define CORE_HC_MCLK_SEL_HS400 (3 << 8) +#define CORE_HC_MCLK_SEL_MASK (3 << 8) +#define CORE_HC_AUTO_CMD21_EN (1 << 6) +#define CORE_IO_PAD_PWR_SWITCH_EN (1 << 15) #define CORE_IO_PAD_PWR_SWITCH (1 << 16) #define CORE_HC_SELECT_IN_EN (1 << 18) #define CORE_HC_SELECT_IN_HS400 (6 << 19) @@ -320,6 +321,7 @@ struct sdhci_msm_host { atomic_t controller_clock; bool use_cdclp533; bool use_updated_dll_reset; + u32 caps_0; }; enum vdd_io_level { @@ -2180,11 +2182,12 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) */ mb(); - if (io_level & REQ_IO_HIGH) + if ((io_level & REQ_IO_HIGH) && (msm_host->caps_0 & CORE_3_0V_SUPPORT)) writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & ~CORE_IO_PAD_PWR_SWITCH), host->ioaddr + CORE_VENDOR_SPEC); - else if (io_level & REQ_IO_LOW) + else if ((io_level & REQ_IO_LOW) || + (msm_host->caps_0 & CORE_1_8V_SUPPORT)) writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | CORE_IO_PAD_PWR_SWITCH), host->ioaddr + CORE_VENDOR_SPEC); @@ -2819,7 +2822,7 @@ static struct sdhci_ops sdhci_msm_ops = { static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, struct sdhci_host *host) { - u32 version, caps; + u32 version, caps = 0; u16 minor; u8 major; @@ -2828,6 +2831,8 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, CORE_VERSION_MAJOR_SHIFT; minor = version & CORE_VERSION_TARGET_MASK; + caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES); + /* * Starting with SDCC 5 controller (core major version = 1) * controller won't advertise 3.0v, 1.8v and 8-bit features @@ -2835,20 +2840,19 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, */ if (major >= 1 && minor != 0x11 && minor != 0x12) { struct sdhci_msm_reg_data *vdd_io_reg; - caps = CORE_3_0V_SUPPORT; /* * Enable 1.8V support capability on controllers that * support dual voltage */ vdd_io_reg = msm_host->pdata->vreg_data->vdd_io_data; - if (vdd_io_reg && - (vdd_io_reg->low_vol_level != vdd_io_reg->high_vol_level)) + if (vdd_io_reg && (vdd_io_reg->high_vol_level > 2700000)) + caps |= CORE_3_0V_SUPPORT; + if (vdd_io_reg && (vdd_io_reg->low_vol_level < 1950000)) caps |= CORE_1_8V_SUPPORT; if (msm_host->pdata->mmc_bus_width == MMC_CAP_8_BIT_DATA) caps |= CORE_8_BIT_SUPPORT; - writel_relaxed( - (readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | - caps), host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); + writel_relaxed(caps, host->ioaddr + + CORE_VENDOR_SPEC_CAPABILITIES0); } /* @@ -2871,9 +2875,10 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, * In case bus addressing ever changes, controller version should be * used in order to decide whether or not to mask 64-bit support. */ - caps = readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES); caps &= ~CORE_SYS_BUS_SUPPORT_64_BIT; writel_relaxed(caps, host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); + /* keep track of the value in SDHCI_CAPABILITIES */ + msm_host->caps_0 = caps; } static int sdhci_msm_probe(struct platform_device *pdev) @@ -3051,6 +3056,14 @@ static int sdhci_msm_probe(struct platform_device *pdev) FF_CLK_SW_RST_DIS, msm_host->core_mem + CORE_HC_MODE); sdhci_set_default_hw_caps(msm_host, host); + + /* + * Set the PAD_PWR_SWTICH_EN bit so that the PAD_PWR_SWITCH bit can + * be used as required later on. + */ + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | + CORE_IO_PAD_PWR_SWITCH_EN), + host->ioaddr + CORE_VENDOR_SPEC); /* * CORE_SW_RST above may trigger power irq if previous status of PWRCTL * was either BUS_ON or IO_HIGH_V. So before we enable the power irq @@ -3066,6 +3079,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (irq_status & (CORE_PWRCTL_IO_HIGH | CORE_PWRCTL_IO_LOW)) irq_ctl |= CORE_PWRCTL_IO_SUCCESS; writel_relaxed(irq_ctl, (msm_host->core_mem + CORE_PWRCTL_CTL)); + /* * Ensure that above writes are propogated before interrupt enablement * in GIC. From 12c38797eac328a4881d719e83476da83f49d75e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 3 Mar 2015 16:14:55 -0800 Subject: [PATCH 224/472] mmc: sdhci-msm: enable 64-bit ADMA The SDHC controller capability was limited to 32-bit ADMA if the system on chip supported only 32-bit address bus width, eventhough the controller was 64-bit ADMA capable for potential memory savings. Remove this limitation on systems that support larger address bus width. Change-Id: I79b296bc4dff015dac76036c231d197748aa03cb Signed-off-by: Venkat Gopalakrishnan --- Documentation/devicetree/bindings/mmc/sdhci-msm.txt | 3 +++ drivers/mmc/host/sdhci-msm.c | 12 +++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 9315a06343f7..e42fe9d51983 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -72,6 +72,8 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag all the cores in the little cluster. The default CPU affinity mode is PM_QOS_REQ_AFFINE_IRQ to maintain backward compatibility. + - qcom,large-address-bus - specifies whether the soc is capable of + supporting larger than 32 bit address bus width. Example: @@ -104,6 +106,7 @@ Example: qcom,bus-width = <4>; qcom,nonremovable; + qcom,large-address-bus; qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v"; gpios = <&msmgpio 40 0>, /* CLK */ diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 2af0bdd9686f..e39a92051d40 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -273,6 +273,7 @@ struct sdhci_msm_pltfm_data { struct sdhci_msm_slot_reg_data *vreg_data; bool nonremovable; bool nonhotplug; + bool largeaddressbus; bool pin_cfg_sts; struct sdhci_msm_pin_data *pin_data; struct sdhci_pinctrl_data *pctrl_data; @@ -1588,6 +1589,9 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) if (of_get_property(np, "qcom,nonhotplug", NULL)) pdata->nonhotplug = true; + pdata->largeaddressbus = + of_property_read_bool(np, "qcom,large-address-bus"); + sdhci_msm_populate_affinity_type(pdata, np); return pdata; @@ -2851,8 +2855,6 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, caps |= CORE_1_8V_SUPPORT; if (msm_host->pdata->mmc_bus_width == MMC_CAP_8_BIT_DATA) caps |= CORE_8_BIT_SUPPORT; - writel_relaxed(caps, host->ioaddr + - CORE_VENDOR_SPEC_CAPABILITIES0); } /* @@ -2872,10 +2874,10 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, /* * Mask 64-bit support for controller with 32-bit address bus so that * smaller descriptor size will be used and improve memory consumption. - * In case bus addressing ever changes, controller version should be - * used in order to decide whether or not to mask 64-bit support. */ - caps &= ~CORE_SYS_BUS_SUPPORT_64_BIT; + if (!msm_host->pdata->largeaddressbus) + caps &= ~CORE_SYS_BUS_SUPPORT_64_BIT; + writel_relaxed(caps, host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); /* keep track of the value in SDHCI_CAPABILITIES */ msm_host->caps_0 = caps; From 26f1875126f874580c8ef227547cff96185d0eb7 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 12 Feb 2015 13:37:56 +0200 Subject: [PATCH 225/472] mmc: core: Add tracepoints to enhance pm debugging Instrument the mmc core layer with tracepoints to aid in debugging issues and identifying latencies in the following paths: * System suspend/resume * Runtime suspend/resume Change-Id: I1e0fa7d3f8b54c102b4055f910b58a42412748da Signed-off-by: Konstantin Dorfman [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 15 +++++++++++++ include/trace/events/mmc.h | 43 +++++++++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 5938ff5e96b0..ab1a7dc872c5 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "core.h" #include "host.h" @@ -2123,6 +2124,7 @@ out: static int mmc_suspend(struct mmc_host *host) { int err; + ktime_t start = ktime_get(); err = _mmc_suspend(host, true); if (!err) { @@ -2130,6 +2132,8 @@ static int mmc_suspend(struct mmc_host *host) pm_runtime_set_suspended(&host->card->dev); } + trace_mmc_suspend(mmc_hostname(host), err, + ktime_to_us(ktime_sub(ktime_get(), start))); return err; } @@ -2210,6 +2214,7 @@ static int mmc_shutdown(struct mmc_host *host) static int mmc_resume(struct mmc_host *host) { int err = 0; + ktime_t start = ktime_get(); if (!(host->caps & MMC_CAP_RUNTIME_RESUME)) { err = _mmc_resume(host); @@ -2218,6 +2223,9 @@ static int mmc_resume(struct mmc_host *host) } pm_runtime_enable(&host->card->dev); + trace_mmc_resume(mmc_hostname(host), err, + ktime_to_us(ktime_sub(ktime_get(), start))); + return err; } @@ -2227,6 +2235,7 @@ static int mmc_resume(struct mmc_host *host) static int mmc_runtime_suspend(struct mmc_host *host) { int err; + ktime_t start = ktime_get(); if (!(host->caps & MMC_CAP_AGGRESSIVE_PM)) return 0; @@ -2236,6 +2245,8 @@ static int mmc_runtime_suspend(struct mmc_host *host) pr_err("%s: error %d doing aggressive suspend\n", mmc_hostname(host), err); + trace_mmc_runtime_suspend(mmc_hostname(host), err, + ktime_to_us(ktime_sub(ktime_get(), start))); return err; } @@ -2245,6 +2256,7 @@ static int mmc_runtime_suspend(struct mmc_host *host) static int mmc_runtime_resume(struct mmc_host *host) { int err; + ktime_t start = ktime_get(); if (!(host->caps & (MMC_CAP_AGGRESSIVE_PM | MMC_CAP_RUNTIME_RESUME))) return 0; @@ -2256,6 +2268,9 @@ static int mmc_runtime_resume(struct mmc_host *host) mmc_init_clk_scaling(host); + trace_mmc_runtime_resume(mmc_hostname(host), err, + ktime_to_us(ktime_sub(ktime_get(), start))); + return 0; } diff --git a/include/trace/events/mmc.h b/include/trace/events/mmc.h index 9606997a8f2e..98a12711b572 100644 --- a/include/trace/events/mmc.h +++ b/include/trace/events/mmc.h @@ -1,6 +1,6 @@ /* * Copyright (C) 2013 Google, Inc. - * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -176,6 +176,47 @@ TRACE_EVENT(mmc_clk, ) ); +DECLARE_EVENT_CLASS(mmc_pm_template, + TP_PROTO(const char *dev_name, int err, s64 usecs), + + TP_ARGS(dev_name, err, usecs), + + TP_STRUCT__entry( + __field(s64, usecs) + __field(int, err) + __string(dev_name, dev_name) + ), + + TP_fast_assign( + __entry->usecs = usecs; + __entry->err = err; + __assign_str(dev_name, dev_name); + ), + + TP_printk( + "took %lld usecs, %s err %d", + __entry->usecs, + __get_str(dev_name), + __entry->err + ) +); + +DEFINE_EVENT(mmc_pm_template, mmc_runtime_suspend, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + +DEFINE_EVENT(mmc_pm_template, mmc_runtime_resume, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + +DEFINE_EVENT(mmc_pm_template, mmc_suspend, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + +DEFINE_EVENT(mmc_pm_template, mmc_resume, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + #endif /* if !defined(_TRACE_MMC_H) || defined(TRACE_HEADER_MULTI_READ) */ /* This part must be outside protection */ From 70a7e671d5fade5b8b419a45262a880e27cc6773 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Wed, 25 Feb 2015 16:23:50 +0200 Subject: [PATCH 226/472] mmc: sdhci-msm: Add tracepoints to enhance pm debugging Instrument the sdhci-msm platform driver with tracepoints to aid in debugging issues and identifying latencies in the following paths: * System suspend/resume * Runtime suspend/resume Change-Id: I4fed1c2ccba7d5d7f978f161e7985c98e869d1d8 Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/sdhci-msm.c | 20 +++++++++++++++++--- include/trace/events/mmc.h | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e39a92051d40..e7fd0aea05cf 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -38,6 +38,7 @@ #include #include #include +#include #include "sdhci-pltfm.h" @@ -3318,6 +3319,7 @@ static int sdhci_msm_runtime_suspend(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; + ktime_t start = ktime_get(); disable_irq(host->irq); disable_irq(msm_host->pwr_irq); @@ -3331,6 +3333,8 @@ static int sdhci_msm_runtime_suspend(struct device *dev) if (msm_host->msm_bus_vote.client_handle) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); } + trace_sdhci_msm_runtime_suspend(mmc_hostname(host->mmc), 0, + ktime_to_us(ktime_sub(ktime_get(), start))); return 0; } @@ -3340,10 +3344,13 @@ static int sdhci_msm_runtime_resume(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; + ktime_t start = ktime_get(); enable_irq(msm_host->pwr_irq); enable_irq(host->irq); + trace_sdhci_msm_runtime_resume(mmc_hostname(host->mmc), 0, + ktime_to_us(ktime_sub(ktime_get(), start))); return 0; } @@ -3352,6 +3359,8 @@ static int sdhci_msm_suspend(struct device *dev) struct sdhci_host *host = dev_get_drvdata(dev); struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; + int ret = 0; + ktime_t start = ktime_get(); if (gpio_is_valid(msm_host->pdata->status_gpio)) mmc_gpio_free_cd(msm_host->mmc); @@ -3361,9 +3370,11 @@ static int sdhci_msm_suspend(struct device *dev) mmc_hostname(host->mmc), __func__); goto out; } - return sdhci_msm_runtime_suspend(dev); + ret = sdhci_msm_runtime_suspend(dev); out: - return 0; + trace_sdhci_msm_suspend(mmc_hostname(host->mmc), ret, + ktime_to_us(ktime_sub(ktime_get(), start))); + return ret; } static int sdhci_msm_resume(struct device *dev) @@ -3372,6 +3383,7 @@ static int sdhci_msm_resume(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; int ret = 0; + ktime_t start = ktime_get(); if (gpio_is_valid(msm_host->pdata->status_gpio)) { ret = mmc_gpio_request_cd(msm_host->mmc, @@ -3386,8 +3398,10 @@ static int sdhci_msm_resume(struct device *dev) goto out; } - return sdhci_msm_runtime_resume(dev); + ret = sdhci_msm_runtime_resume(dev); out: + trace_sdhci_msm_resume(mmc_hostname(host->mmc), ret, + ktime_to_us(ktime_sub(ktime_get(), start))); return ret; } diff --git a/include/trace/events/mmc.h b/include/trace/events/mmc.h index 98a12711b572..30b2113ace6e 100644 --- a/include/trace/events/mmc.h +++ b/include/trace/events/mmc.h @@ -217,6 +217,21 @@ DEFINE_EVENT(mmc_pm_template, mmc_resume, TP_PROTO(const char *dev_name, int err, s64 usecs), TP_ARGS(dev_name, err, usecs)); +DEFINE_EVENT(mmc_pm_template, sdhci_msm_suspend, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + +DEFINE_EVENT(mmc_pm_template, sdhci_msm_resume, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + +DEFINE_EVENT(mmc_pm_template, sdhci_msm_runtime_suspend, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); + +DEFINE_EVENT(mmc_pm_template, sdhci_msm_runtime_resume, + TP_PROTO(const char *dev_name, int err, s64 usecs), + TP_ARGS(dev_name, err, usecs)); #endif /* if !defined(_TRACE_MMC_H) || defined(TRACE_HEADER_MULTI_READ) */ /* This part must be outside protection */ From e2dffecbe77a3edb2beabc5a2836c6934ed2ad46 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 10 Mar 2015 14:11:28 -0700 Subject: [PATCH 227/472] mmc: sd: Use locally stored ocr on paranoid init Prevent null pointer dereference when accessing ocr stored in the card structure in case mmc_sd_init_card() had failed before allocating the card structure and we are retrying the init. Change-Id: I15365804e0986e01339ccaefdffdfaf9fd319160 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/core/sd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 092b447ae901..99b521b79f1c 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1413,8 +1413,8 @@ int mmc_attach_sd(struct mmc_host *host) retries--; mmc_power_off(host); usleep_range(5000, 5500); - mmc_power_up(host, host->card->ocr); - mmc_select_voltage(host, host->card->ocr); + mmc_power_up(host, rocr); + mmc_select_voltage(host, rocr); continue; } break; From 6b5b56b28b8a13109609909a2dbd7f0e18af4b53 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 10 Mar 2015 15:51:23 -0700 Subject: [PATCH 228/472] mmc: sdhci: Call host ops set_clock when disabling clocks sdhci_set_clock() has been turned into a generic library function, all calls to change clock rate needs to call the platform specific set_clock which will call into the generic library after performing platform specific operations. Fix this when disabling clocks. Change-Id: Ib01b1984291682dad709a3e38500ad31e2d3e4bc Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 995e3ca6890f..d2262672f604 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1898,7 +1898,7 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_set_power(host, ios->power_mode, ios->vdd); } if (!ios->clock) - sdhci_set_clock(host, ios->clock); + host->ops->set_clock(host, ios->clock); mmiowb(); } From 5dddfdcab842d62248a939cd037d8444d6da5c4b Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 12 Feb 2013 16:13:05 -0800 Subject: [PATCH 229/472] include: mmc: Export sanitized mmc headers Export sanitized mmc.h and core.h for userspace. Change-Id: I3a6eadde2023d974b0ce260a77082b01d8ba0b5d Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- include/uapi/linux/mmc/Kbuild | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/uapi/linux/mmc/Kbuild b/include/uapi/linux/mmc/Kbuild index b250482ea78a..ce4e88593605 100644 --- a/include/uapi/linux/mmc/Kbuild +++ b/include/uapi/linux/mmc/Kbuild @@ -1,4 +1,6 @@ # UAPI Header export list header-y += core.h +header-y += core.h header-y += ioctl.h header-y += mmc.h +header-y += mmc.h From a5b878365aafe7b34fdbbda5f5f4176c011bf65b Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 13 May 2015 14:14:45 +0530 Subject: [PATCH 230/472] mmc: sdhci-msm: Dont enable PWRSAVE_DLL for certain sdhc hosts SDHC core with new 14lpp tech DLL should not enable PWRSAVE_DLL since such controller's internal gating cannot meet following MCLK requirement: when MCLK is gated OFF, it is not gated for less than 0.5us and MCLK must be switched on for at-least 1us before DATA starts coming. Add support for this new requirement. Change-Id: Ief03926e0b9c6ff2f6335a422c576dd489fba9d5 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e7fd0aea05cf..b9070c6d554b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -323,6 +323,7 @@ struct sdhci_msm_host { atomic_t controller_clock; bool use_cdclp533; bool use_updated_dll_reset; + bool use_14lpp_dll; u32 caps_0; }; @@ -881,6 +882,8 @@ out: static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; u32 dll_status; int ret = 0; @@ -910,10 +913,18 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) goto out; } - /* set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3 */ - writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC3) - | CORE_PWRSAVE_DLL), - host->ioaddr + CORE_VENDOR_SPEC3); + /* + * set CORE_PWRSAVE_DLL bit in CORE_VENDOR_SPEC3. + * when MCLK is gated OFF, it is not gated for less than 0.5us + * and MCLK must be switched on for at-least 1us before DATA + * starts coming. Controllers with 14lpp tech DLL cannot + * guarantee above requirement. So PWRSAVE_DLL should not be + * turned on for host controllers using this DLL. + */ + if (!msm_host->use_14lpp_dll) + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC3) + | CORE_PWRSAVE_DLL), + host->ioaddr + CORE_VENDOR_SPEC3); mb(); out: pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc), @@ -2872,6 +2883,16 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, if ((major == 1) && (minor >= 0x42)) msm_host->use_updated_dll_reset = true; + /* + * SDCC 5 controller with major version 1 and minor version 0x42 + * currently uses 14lpp tech DLL whose internal gating cannot + * guarantee MCLK timing requirement i.e. + * when MCLK is gated OFF, it is not gated for less than 0.5us + * and MCLK must be switched on for at-least 1us before DATA + * starts coming. + */ + if ((major == 1) && (minor == 0x42)) + msm_host->use_14lpp_dll = true; /* * Mask 64-bit support for controller with 32-bit address bus so that * smaller descriptor size will be used and improve memory consumption. From 3006336d10a868a18eba5da912367fd775bf2e68 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 13 May 2015 17:21:20 +0300 Subject: [PATCH 231/472] mmc: sd: fix SD card runtime suspend sequence for clock scaling This change fixes the sequence for runtime suspend. Before this change, clock scaling logic was initialized during runtime suspend instead of calling destruction API of clock scaling. This change fixes this issue by calling the right clock scaling API during SD runtime suspend/resume. Change-Id: I6044f65de55f3eb683747a09b1e20322342222c2 Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/sd.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 99b521b79f1c..8fefdc9bf153 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1195,7 +1195,8 @@ static int _mmc_sd_suspend(struct mmc_host *host) * Disable clock scaling before suspend and enable it after resume so * as to avoid clock scaling decisions kicking in during this window. */ - mmc_disable_clk_scaling(host); + if (mmc_can_scale_clk(host)) + mmc_disable_clk_scaling(host); mmc_claim_host(host); @@ -1273,6 +1274,9 @@ static int _mmc_sd_resume(struct mmc_host *host) #endif mmc_card_clr_suspended(host->card); + if (mmc_can_scale_clk(host)) + mmc_init_clk_scaling(host); + out: mmc_release_host(host); return err; @@ -1310,13 +1314,6 @@ static int mmc_sd_runtime_suspend(struct mmc_host *host) pr_err("%s: error %d doing aggressive suspend\n", mmc_hostname(host), err); - /* - * We have done full initialization of the card, - * reset the clk scale stats and current frequency. - */ - if (mmc_can_scale_clk(host)) - mmc_init_clk_scaling(host); - return err; } From 71e326e07801e6499e7e9c23b4b78b9b498e270a Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 8 May 2015 11:12:30 +0530 Subject: [PATCH 232/472] mmc: block: add req pointer to mmc request This is needed by ICE (Inline Crypto Engine) driver to get the ICE configuration data from the request. Change-Id: Ie69c64f4dc0c31290dec50d905e8b3d436c86d62 Signed-off-by: Sahitya Tummala Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 1 + include/linux/mmc/core.h | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 2c56a909092b..c0acbfa6211c 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1944,6 +1944,7 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, } mqrq->mmc_active.mrq = &brq->mrq; + mqrq->mmc_active.mrq->req = mqrq->req; mqrq->mmc_active.err_check = mmc_blk_err_check; mmc_queue_bounce_pre(mqrq); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index ba296ab25899..07f96aa5ff6b 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -108,6 +108,7 @@ struct mmc_request { struct completion completion; void (*done)(struct mmc_request *);/* completion function */ struct mmc_host *host; + struct request *req; }; struct mmc_card; From a4a793c49e19f3c4a042e99297cb2f2d8792cb2a Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 21 May 2015 08:24:03 +0530 Subject: [PATCH 233/472] mmc: sdhci-msm: Move common definitions to header file This is needed by Inline Crypto Engine (ICE) driver. Change-Id: Ibfe151098f4f0395b5b9dbdc463723b8e0265487 Signed-off-by: Sahitya Tummala Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 135 +------------------------------ drivers/mmc/host/sdhci-msm.h | 149 +++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 132 deletions(-) create mode 100644 drivers/mmc/host/sdhci-msm.h diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b9070c6d554b..fb20d751b6b7 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -31,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -40,7 +39,7 @@ #include #include -#include "sdhci-pltfm.h" +#include "sdhci-msm.h" #define CORE_POWER 0x0 #define CORE_SW_RST (1 << 7) @@ -162,6 +161,8 @@ #define CORE_DDR_CONFIG 0x1B8 #define DDR_CONFIG_POR_VAL 0x80040853 +#define MSM_MMC_DEFAULT_CPU_DMA_LATENCY 200 /* usecs */ + /* 512 descriptors */ #define SDHCI_MSM_MAX_SEGMENTS (1 << 9) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ @@ -197,136 +198,6 @@ static int disable_slots; /* root can write, others read */ module_param(disable_slots, int, S_IRUGO|S_IWUSR); -/* This structure keeps information per regulator */ -struct sdhci_msm_reg_data { - /* voltage regulator handle */ - struct regulator *reg; - /* regulator name */ - const char *name; - /* voltage level to be set */ - u32 low_vol_level; - u32 high_vol_level; - /* Load values for low power and high power mode */ - u32 lpm_uA; - u32 hpm_uA; - - /* is this regulator enabled? */ - bool is_enabled; - /* is this regulator needs to be always on? */ - bool is_always_on; - /* is low power mode setting required for this regulator? */ - bool lpm_sup; - bool set_voltage_sup; -}; - -#define MSM_MMC_DEFAULT_CPU_DMA_LATENCY 200 /* usecs */ -/* - * This structure keeps information for all the - * regulators required for a SDCC slot. - */ -struct sdhci_msm_slot_reg_data { - /* keeps VDD/VCC regulator info */ - struct sdhci_msm_reg_data *vdd_data; - /* keeps VDD IO regulator info */ - struct sdhci_msm_reg_data *vdd_io_data; -}; - -struct sdhci_msm_gpio { - u32 no; - const char *name; - bool is_enabled; -}; - -struct sdhci_msm_gpio_data { - struct sdhci_msm_gpio *gpio; - u8 size; -}; - -struct sdhci_msm_pin_data { - /* - * = 1 if controller pins are using gpios - * = 0 if controller has dedicated MSM pads - */ - u8 is_gpio; - struct sdhci_msm_gpio_data *gpio_data; -}; - -struct sdhci_pinctrl_data { - struct pinctrl *pctrl; - struct pinctrl_state *pins_active; - struct pinctrl_state *pins_sleep; -}; - -struct sdhci_msm_bus_voting_data { - struct msm_bus_scale_pdata *bus_pdata; - unsigned int *bw_vecs; - unsigned int bw_vecs_size; -}; - -struct sdhci_msm_pltfm_data { - /* Supported UHS-I Modes */ - u32 caps; - - /* More capabilities */ - u32 caps2; - - unsigned long mmc_bus_width; - struct sdhci_msm_slot_reg_data *vreg_data; - bool nonremovable; - bool nonhotplug; - bool largeaddressbus; - bool pin_cfg_sts; - struct sdhci_msm_pin_data *pin_data; - struct sdhci_pinctrl_data *pctrl_data; - u32 cpu_dma_latency_us; - int status_gpio; /* card detection GPIO that is configured as IRQ */ - struct sdhci_msm_bus_voting_data *voting_data; - u32 *sup_clk_table; - unsigned char sup_clk_cnt; - enum pm_qos_req_type cpu_affinity_type; -}; - -struct sdhci_msm_bus_vote { - uint32_t client_handle; - uint32_t curr_vote; - int min_bw_vote; - int max_bw_vote; - bool is_max_bw_needed; - struct delayed_work vote_work; - struct device_attribute max_bus_bw; -}; - -struct sdhci_msm_host { - struct platform_device *pdev; - void __iomem *core_mem; /* MSM SDCC mapped address */ - int pwr_irq; /* power irq */ - struct clk *clk; /* main SD/MMC bus clock */ - struct clk *pclk; /* SDHC peripheral bus clock */ - struct clk *bus_clk; /* SDHC bus voter clock */ - struct clk *ff_clk; /* CDC calibration fixed feedback clock */ - struct clk *sleep_clk; /* CDC calibration sleep clock */ - atomic_t clks_on; /* Set if clocks are enabled */ - struct sdhci_msm_pltfm_data *pdata; - struct mmc_host *mmc; - struct sdhci_pltfm_data sdhci_msm_pdata; - u32 curr_pwr_state; - u32 curr_io_level; - struct completion pwr_irq_completion; - struct sdhci_msm_bus_vote msm_bus_vote; - struct device_attribute polling; - u32 clk_rate; /* Keeps track of current clock rate that is set */ - bool tuning_done; - bool calibration_done; - u8 saved_tuning_phase; - bool en_auto_cmd21; - struct device_attribute auto_cmd21_attr; - atomic_t controller_clock; - bool use_cdclp533; - bool use_updated_dll_reset; - bool use_14lpp_dll; - u32 caps_0; -}; - enum vdd_io_level { /* set vdd_io_data->low_vol_level */ VDD_IO_LOW, diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h new file mode 100644 index 000000000000..d2333c6f5d9b --- /dev/null +++ b/drivers/mmc/host/sdhci-msm.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SDHCI_MSM_H__ +#define __SDHCI_MSM_H__ + +#include +#include "sdhci-pltfm.h" + +/* This structure keeps information per regulator */ +struct sdhci_msm_reg_data { + /* voltage regulator handle */ + struct regulator *reg; + /* regulator name */ + const char *name; + /* voltage level to be set */ + u32 low_vol_level; + u32 high_vol_level; + /* Load values for low power and high power mode */ + u32 lpm_uA; + u32 hpm_uA; + + /* is this regulator enabled? */ + bool is_enabled; + /* is this regulator needs to be always on? */ + bool is_always_on; + /* is low power mode setting required for this regulator? */ + bool lpm_sup; + bool set_voltage_sup; +}; + +/* + * This structure keeps information for all the + * regulators required for a SDCC slot. + */ +struct sdhci_msm_slot_reg_data { + /* keeps VDD/VCC regulator info */ + struct sdhci_msm_reg_data *vdd_data; + /* keeps VDD IO regulator info */ + struct sdhci_msm_reg_data *vdd_io_data; +}; + +struct sdhci_msm_gpio { + u32 no; + const char *name; + bool is_enabled; +}; + +struct sdhci_msm_gpio_data { + struct sdhci_msm_gpio *gpio; + u8 size; +}; + +struct sdhci_msm_pin_data { + /* + * = 1 if controller pins are using gpios + * = 0 if controller has dedicated MSM pads + */ + u8 is_gpio; + struct sdhci_msm_gpio_data *gpio_data; +}; + +struct sdhci_pinctrl_data { + struct pinctrl *pctrl; + struct pinctrl_state *pins_active; + struct pinctrl_state *pins_sleep; +}; + +struct sdhci_msm_bus_voting_data { + struct msm_bus_scale_pdata *bus_pdata; + unsigned int *bw_vecs; + unsigned int bw_vecs_size; +}; + +struct sdhci_msm_pltfm_data { + /* Supported UHS-I Modes */ + u32 caps; + + /* More capabilities */ + u32 caps2; + + unsigned long mmc_bus_width; + struct sdhci_msm_slot_reg_data *vreg_data; + bool nonremovable; + bool nonhotplug; + bool largeaddressbus; + bool pin_cfg_sts; + struct sdhci_msm_pin_data *pin_data; + struct sdhci_pinctrl_data *pctrl_data; + u32 cpu_dma_latency_us; + int status_gpio; /* card detection GPIO that is configured as IRQ */ + struct sdhci_msm_bus_voting_data *voting_data; + u32 *sup_clk_table; + unsigned char sup_clk_cnt; + enum pm_qos_req_type cpu_affinity_type; +}; + +struct sdhci_msm_bus_vote { + uint32_t client_handle; + uint32_t curr_vote; + int min_bw_vote; + int max_bw_vote; + bool is_max_bw_needed; + struct delayed_work vote_work; + struct device_attribute max_bus_bw; +}; + +struct sdhci_msm_host { + struct platform_device *pdev; + void __iomem *core_mem; /* MSM SDCC mapped address */ + int pwr_irq; /* power irq */ + struct clk *clk; /* main SD/MMC bus clock */ + struct clk *pclk; /* SDHC peripheral bus clock */ + struct clk *bus_clk; /* SDHC bus voter clock */ + struct clk *ff_clk; /* CDC calibration fixed feedback clock */ + struct clk *sleep_clk; /* CDC calibration sleep clock */ + atomic_t clks_on; /* Set if clocks are enabled */ + struct sdhci_msm_pltfm_data *pdata; + struct mmc_host *mmc; + struct sdhci_pltfm_data sdhci_msm_pdata; + u32 curr_pwr_state; + u32 curr_io_level; + struct completion pwr_irq_completion; + struct sdhci_msm_bus_vote msm_bus_vote; + struct device_attribute polling; + u32 clk_rate; /* Keeps track of current clock rate that is set */ + bool tuning_done; + bool calibration_done; + u8 saved_tuning_phase; + bool en_auto_cmd21; + struct device_attribute auto_cmd21_attr; + atomic_t controller_clock; + bool use_cdclp533; + bool use_updated_dll_reset; + bool use_14lpp_dll; + u32 caps_0; +}; +#endif /* __SDHCI_MSM_H__ */ From f4a166292806342ea74935954a493650ec2cface Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 21 Mar 2014 11:14:51 +0530 Subject: [PATCH 234/472] mmc: core: Add support to read command queue parameters eMMC cards with EXT_CSD version >= 7, optionally support command queuing feature as defined by JEDEC eMMC5.1. Add support for probing command queue feature for such type of cards. Change-Id: I9454d0d6997ccbd1778a147354859467f4a9a7d3 Signed-off-by: Sujit Reddy Thumma Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 18 ++++++++++++++++++ include/linux/mmc/card.h | 2 ++ include/linux/mmc/mmc.h | 3 +++ 3 files changed, 23 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index ab1a7dc872c5..d08629b4c385 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -612,6 +612,24 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.data_sector_size = 512; } + if (card->ext_csd.rev >= 7) { + card->ext_csd.cmdq_support = ext_csd[EXT_CSD_CMDQ_SUPPORT]; + if (card->ext_csd.cmdq_support) { + /* + * Queue Depth = N + 1, + * see JEDEC JESD84-B51 section 7.4.19 + */ + card->ext_csd.cmdq_depth = + ext_csd[EXT_CSD_CMDQ_DEPTH] + 1; + pr_info("%s: CMDQ supported: depth: %d\n", + mmc_hostname(card->host), + card->ext_csd.cmdq_depth); + } + } else { + card->ext_csd.cmdq_support = 0; + card->ext_csd.cmdq_depth = 0; + } + /* eMMC v5 or later */ if (card->ext_csd.rev >= 7) { memcpy(card->ext_csd.fwrev, &ext_csd[EXT_CSD_FIRMWARE_VERSION], diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index c3e6fbfbcfab..59ef4e717beb 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -119,6 +119,8 @@ struct mmc_ext_csd { u8 raw_pwr_cl_ddr_200_360; /* 253 */ u8 raw_bkops_status; /* 246 */ u8 raw_sectors[4]; /* 212 - 4 bytes */ + u8 cmdq_depth; /* 307 */ + u8 cmdq_support; /* 308 */ unsigned int feature_support; #define MMC_DISCARD_FEATURE BIT(0) /* CMD38 feature */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 6f5b437311c2..a85e7e3f9bb6 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -273,6 +273,9 @@ struct _mmc_csd { #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ #define EXT_CSD_PWR_CL_DDR_200_360 253 /* RO */ #define EXT_CSD_FIRMWARE_VERSION 254 /* RO, 8 bytes */ +#define EXT_CSD_CMDQ_DEPTH 307 /* RO */ +#define EXT_CSD_CMDQ_SUPPORT 308 /* RO */ + #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_SUPPORTED_MODE 493 /* RO */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ From 326454ec69c4b91e0412acf93cbe56a3ccae75ad Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 2 Jun 2015 13:36:35 +0300 Subject: [PATCH 235/472] mmc: sdhci-msm: Fix clk gating on MSM8996-v2/v3 revisions MSM8996-V2 and MSM8996-V3 has different sdcc minor number compared to MSM8996-v1. Include these minor numbers as well for fixing clk-gating logic. Change-Id: Ie9c797589151bcfc17c7ddee33dd93d9ea683822 Signed-off-by: Ritesh Harjani Signed-off-by: Talel Shenhar --- drivers/mmc/host/sdhci-msm.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index fb20d751b6b7..8e40cac883cd 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2755,14 +2755,15 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, msm_host->use_updated_dll_reset = true; /* - * SDCC 5 controller with major version 1 and minor version 0x42 - * currently uses 14lpp tech DLL whose internal gating cannot - * guarantee MCLK timing requirement i.e. + * SDCC 5 controller with major version 1 and minor version 0x42, + * 0x46 and 0x49 currently uses 14lpp tech DLL whose internal + * gating cannot guarantee MCLK timing requirement i.e. * when MCLK is gated OFF, it is not gated for less than 0.5us * and MCLK must be switched on for at-least 1us before DATA * starts coming. */ - if ((major == 1) && (minor == 0x42)) + if ((major == 1) && ((minor == 0x42) || (minor == 0x46) || + (minor == 0x49))) msm_host->use_14lpp_dll = true; /* * Mask 64-bit support for controller with 32-bit address bus so that From ed9551c160f8c072a3db5d9a91810b599b86497a Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Tue, 10 Mar 2015 16:00:56 +0200 Subject: [PATCH 236/472] mmc: add support for scheduling mmcqd on idle CPU In order to boost mmc performance on various platforms, add support for configuring whether set_wake_up_idle() should be called on the mmc queue thread (mmcqd). The decision will be set in each individual platform's dts file. CRs-Fixed: 787554 Change-Id: I3989d3f5b8228785e6d3bc49c7eb01ebf2fa2f38 Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- Documentation/devicetree/bindings/mmc/sdhci-msm.txt | 3 +++ drivers/mmc/card/queue.c | 3 +++ drivers/mmc/host/sdhci-msm.c | 10 ++++++++-- include/linux/mmc/host.h | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index e42fe9d51983..b9dcecdf2cf8 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -75,6 +75,9 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag - qcom,large-address-bus - specifies whether the soc is capable of supporting larger than 32 bit address bus width. + - qcom,wakeup-on-idle: if configured, the mmcqd thread will call + set_wake_up_idle(), thereby voting for it to be called on idle CPUs. + Example: aliases { diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 695b0ef06b39..943c979e7907 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -59,6 +59,7 @@ static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; struct request_queue *q = mq->queue; + struct mmc_card *card = mq->card; struct sched_param scheduler_params = {0}; scheduler_params.sched_priority = 1; @@ -66,6 +67,8 @@ static int mmc_queue_thread(void *d) sched_setscheduler(current, SCHED_FIFO, &scheduler_params); current->flags |= PF_MEMALLOC; + if (card->host->wakeup_on_idle) + set_wake_up_idle(true); down(&mq->thread_sem); do { diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 8e40cac883cd..748587de19ec 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1367,7 +1367,9 @@ static void sdhci_msm_populate_affinity_type(struct sdhci_msm_pltfm_data *pdata, #endif /* Parse platform data */ -static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) +static +struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, + struct sdhci_msm_host *msm_host) { struct sdhci_msm_pltfm_data *pdata = NULL; struct device_node *np = dev->of_node; @@ -1477,6 +1479,9 @@ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev) sdhci_msm_populate_affinity_type(pdata, np); + if (of_property_read_bool(np, "qcom,wakeup-on-idle")) + msm_host->mmc->wakeup_on_idle = true; + return pdata; out: return NULL; @@ -2822,7 +2827,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto pltfm_free; } - msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev); + msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev, + msm_host); if (!msm_host->pdata) { dev_err(&pdev->dev, "DT parsing error\n"); goto pltfm_free; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index fca8e4a04c3b..2ba8ba249e00 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -458,6 +458,7 @@ struct mmc_host { enum mmc_load state; } clk_scaling; enum dev_state dev_status; + bool wakeup_on_idle; unsigned long private[0] ____cacheline_aligned; }; From cff87b3cda7f31e08f6c539661e3236889831a9e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 24 Feb 2015 13:09:09 -0800 Subject: [PATCH 237/472] Revert "mmc: sdhci-msm: Disable pm functionalities" This reverts commit 2fabf4b6aea9f66fb340f556f3cad1d0d99c8e0f. Reverting the temporarily disabled clk gating feature. Clk scaling still remains disabled. Change-Id: If984ff1f393968c0d76eb6eb8a98c83fb5a7bc92 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 748587de19ec..e44c5c4c1043 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1707,11 +1707,9 @@ static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable) * after SDHCI_MSM_MMC_CLK_GATE_DELAY and thus no * additional delay is required to remove the bus vote. */ -#ifdef MMC_CLKGATE if (host->mmc->clkgate_delay) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); else -#endif sdhci_msm_bus_queue_work(host); } } @@ -2447,7 +2445,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) curr_pwrsave = !!(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & CORE_CLK_PWRSAVE); if ((clock > 400000) && - !curr_pwrsave) + !curr_pwrsave && mmc_host_may_gate_card(host->mmc->card)) writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) | CORE_CLK_PWRSAVE, host->ioaddr + CORE_VENDOR_SPEC); @@ -2455,7 +2453,7 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) * Disable pwrsave for a newly added card if doesn't allow clock * gating. */ - else if (curr_pwrsave) + else if (curr_pwrsave && !mmc_host_may_gate_card(host->mmc->card)) writel_relaxed(readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC) & ~CORE_CLK_PWRSAVE, host->ioaddr + CORE_VENDOR_SPEC); @@ -3043,10 +3041,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Enable pwr irq interrupts */ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); -#ifdef MMC_CLKGATE + /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */ msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY; -#endif + /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; From ae479fa29fc8cc9fd3ab36dde46a0638da2a4fcc Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Mon, 18 May 2015 12:12:48 +0300 Subject: [PATCH 238/472] mmc: core: devfreq: migrate to devfreq based clock scaling This change adds the use of devfreq to MMC. Both eMMC and SD card will use it. For some workloads, such as video playback, it isn't necessary for these cards to run at high speed. Running at lower frequency, for example 52MHz, in such cases can still meet the deadlines for data transfers. Scaling down the clock frequency dynamically has power savings not only because the bus is running at lower frequency but also has an advantage of scaling down the system core voltage, if supported. Provide an ondemand clock scaling support similar to the cpufreq ondemand governor having two thresholds, up_threshold and down_threshold to decide whether to increase the frequency or scale it down respectively. The sampling interval is in the order of milliseconds. If sampling interval is too low, frequent switching of frequencies can lead to high power consumption and if sampling interval is too high, the clock scaling logic would take long time to realize that the underlying hardware (controller and card) is busy and scale up the clocks. Change-Id: I58ddbd93648ded82b304411956e035fb353cd97e Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed trivial merge conflicts & compilation errors] Signed-off-by: Subhash Jadavani --- .../devicetree/bindings/mmc/sdhci-msm.txt | 10 + drivers/mmc/core/core.c | 938 ++++++++++-------- drivers/mmc/core/core.h | 6 +- drivers/mmc/core/debugfs.c | 48 + drivers/mmc/core/host.c | 68 +- drivers/mmc/core/mmc.c | 13 +- drivers/mmc/core/sd.c | 3 + drivers/mmc/host/sdhci-msm.c | 10 + include/linux/mmc/card.h | 4 + include/linux/mmc/host.h | 65 +- 10 files changed, 699 insertions(+), 466 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index b9dcecdf2cf8..a95ff2d27c94 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -34,6 +34,14 @@ Optional Properties: "HS200_1p2v" - indicates that host can support HS200 at 1.2v. "DDR_1p8v" - indicates that host can support DDR mode at 1.8v. "DDR_1p2v" - indicates that host can support DDR mode at 1.2v. + - qcom,devfreq,freq-table - specifies supported frequencies for clock scaling. + Clock scaling logic shall toggle between these frequencies based + on card load. In case the defined frequencies are over or below + the supported card frequencies, they will be overridden + during card init. In case this entry is not supplied, + the driver will construct one based on the card + supported max and min frequencies. + The frequencies must be ordered from lowest to highest. In the following, can be vdd (flash core voltage) or vdd-io (I/O voltage). - qcom,-always-on - specifies whether supply should be kept "on" always. @@ -102,6 +110,8 @@ Example: qcom,vdd-io-voltage-level = <1800000 2950000>; qcom,vdd-io-current-level = <6 22000>; + qcom,devfreq,freq-table = <52000000 200000000>; + pinctrl-names = "active", "sleep"; pinctrl-0 = <&sdc1_clk_on &sdc1_cmd_on &sdc1_data_on>; pinctrl-1 = <&sdc1_clk_off &sdc1_cmd_on &sdc1_data_on>; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index da5927ed5772..0340980c0b77 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -53,8 +54,6 @@ /* If the device is not responding */ #define MMC_CORE_TIMEOUT_MS (10 * 60 * 1000) /* 10 minute timeout */ -static void mmc_clk_scaling(struct mmc_host *host, bool from_wq); - /* * Background operations can take a long time, depending on the housekeeping * operations the card has to perform. @@ -127,41 +126,539 @@ static inline void mmc_should_fail_request(struct mmc_host *host, #endif /* CONFIG_FAIL_MMC_REQUEST */ -static inline void -mmc_clk_scaling_update_state(struct mmc_host *host, struct mmc_request *mrq) +static bool mmc_is_data_request(struct mmc_request *mmc_request) { - if (mrq) { - switch (mrq->cmd->opcode) { - case MMC_READ_SINGLE_BLOCK: - case MMC_READ_MULTIPLE_BLOCK: - case MMC_WRITE_BLOCK: - case MMC_WRITE_MULTIPLE_BLOCK: - host->clk_scaling.invalid_state = false; - break; - default: - host->clk_scaling.invalid_state = true; - break; + switch (mmc_request->cmd->opcode) { + case MMC_READ_SINGLE_BLOCK: + case MMC_READ_MULTIPLE_BLOCK: + case MMC_WRITE_BLOCK: + case MMC_WRITE_MULTIPLE_BLOCK: + return true; + default: + return false; + } +} + +static void mmc_clk_scaling_start_busy(struct mmc_host *host, bool lock_needed) +{ + unsigned long flags; + struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; + + if (!clk_scaling->enable) + return; + + if (lock_needed) + spin_lock_irqsave(&clk_scaling->lock, flags); + + clk_scaling->start_busy = ktime_get(); + clk_scaling->is_busy_started = true; + + if (lock_needed) + spin_unlock_irqrestore(&clk_scaling->lock, flags); +} + +static void mmc_clk_scaling_stop_busy(struct mmc_host *host, bool lock_needed) +{ + unsigned long flags; + struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; + + if (!clk_scaling->enable) + return; + + if (lock_needed) + spin_lock_irqsave(&clk_scaling->lock, flags); + + if (!clk_scaling->is_busy_started) { + WARN_ON(1); + goto out; + } + + clk_scaling->total_busy_time_us += + ktime_to_us(ktime_sub(ktime_get(), + clk_scaling->start_busy)); + pr_debug("%s: accumulated busy time is %lu usec\n", + mmc_hostname(host), clk_scaling->total_busy_time_us); + clk_scaling->is_busy_started = false; + +out: + if (lock_needed) + spin_unlock_irqrestore(&clk_scaling->lock, flags); +} + +/** + * mmc_disable_devfreq_clk_scaling() - Disable clock scaling + * @host: pointer to mmc host structure + * + * Disables clock scaling aggresively + */ +void mmc_disable_clk_scaling(struct mmc_host *host) +{ + if (!host) { + pr_err("bad host parameter\n"); + WARN_ON(1); + return; + } + pr_debug("%s: disabling clock scaling\n", mmc_hostname(host)); + mmc_exit_clk_scaling(host); + +} +EXPORT_SYMBOL(mmc_disable_clk_scaling); + +/** + * mmc_can_scale_clk() - Check clock scaling capability + * @host: pointer to mmc host structure + */ +bool mmc_can_scale_clk(struct mmc_host *host) +{ + if (!host) { + pr_err("bad host parameter\n"); + WARN_ON(1); + return false; + } + + return host->caps2 & MMC_CAP2_CLK_SCALE; +} +EXPORT_SYMBOL(mmc_can_scale_clk); + +static int mmc_devfreq_get_dev_status(struct device *dev, + struct devfreq_dev_status *status) +{ + unsigned long flags; + struct mmc_host *host = container_of(dev, struct mmc_host, class_dev); + struct mmc_devfeq_clk_scaling *clk_scaling; + + if (!host) { + pr_err("bad host parameter\n"); + WARN_ON(1); + return -EINVAL; + } + + clk_scaling = &host->clk_scaling; + + if (!clk_scaling->enable) + return 0; + + spin_lock_irqsave(&clk_scaling->lock, flags); + + /* accumulate the busy time of ongoing work */ + memset(status, 0, sizeof(*status)); + if (clk_scaling->is_busy_started) { + mmc_clk_scaling_stop_busy(host, false); + mmc_clk_scaling_start_busy(host, false); + } + + status->busy_time = clk_scaling->total_busy_time_us; + status->total_time = ktime_to_us(ktime_sub(ktime_get(), + clk_scaling->measure_interval_start)); + clk_scaling->total_busy_time_us = 0; + status->current_frequency = clk_scaling->curr_freq; + clk_scaling->measure_interval_start = ktime_get(); + + pr_debug("%s: status: load = %lu%% - total_time=%lu busy_time = %lu, clk=%lu\n", + mmc_hostname(host), + (status->busy_time*100)/status->total_time, + status->total_time, status->busy_time, + status->current_frequency); + + spin_unlock_irqrestore(&clk_scaling->lock, flags); + + return 0; +} + +static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) +{ + struct mmc_card *card = host->card; + u32 status; + + /* + * If the current partition type is RPMB, clock switching may not + * work properly as sending tuning command (CMD21) is illegal in + * this mode. + */ + if (!card || (mmc_card_mmc(card) && + card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB)) + return false; + + if (mmc_send_status(card, &status)) { + pr_err("%s: Get card status fail\n", mmc_hostname(card->host)); + return false; + } + + return R1_CURRENT_STATE(status) == R1_STATE_TRAN; +} + +int mmc_clk_update_freq(struct mmc_host *host, + unsigned long freq, enum mmc_load state) +{ + int err = 0; + + if (!host) { + pr_err("bad host parameter\n"); + WARN_ON(1); + return -EINVAL; + } + + if (host->ops->notify_load) { + err = host->ops->notify_load(host, state); + if (err) { + pr_err("%s: %s: fail on notify_load\n", + mmc_hostname(host), __func__); + goto out; } - } else { - /* - * force clock scaling transitions, - * if other conditions are met - */ - host->clk_scaling.invalid_state = false; } - return; -} + if (freq != host->clk_scaling.curr_freq) { + if (!mmc_is_vaild_state_for_clk_scaling(host)) { + pr_debug("%s: invalid state for clock scaling - skipping", + mmc_hostname(host)); + err = -EAGAIN; + goto error; + } -static inline void mmc_update_clk_scaling(struct mmc_host *host) + err = host->bus_ops->change_bus_speed(host, &freq); + if (!err) + host->clk_scaling.curr_freq = freq; + else + pr_err("%s: %s: failed (%d) at freq=%lu\n", + mmc_hostname(host), __func__, err, freq); + } +error: + if (err) { + /* restore previous state */ + if (host->ops->notify_load) + if (host->ops->notify_load(host, + host->clk_scaling.state)) + pr_err("%s: %s: fail on notify_load restore\n", + mmc_hostname(host), __func__); + } +out: + return err; +} +EXPORT_SYMBOL(mmc_clk_update_freq); + +static int mmc_devfreq_set_target(struct device *dev, + unsigned long *freq, u32 devfreq_flags) { - if (host->clk_scaling.enable && !host->clk_scaling.invalid_state) { - host->clk_scaling.busy_time_us += - ktime_to_us(ktime_sub(ktime_get(), - host->clk_scaling.start_busy)); - host->clk_scaling.start_busy = ktime_get(); + struct mmc_host *host = container_of(dev, struct mmc_host, class_dev); + struct mmc_devfeq_clk_scaling *clk_scaling; + int err = 0; + int abort; + unsigned long flags; + + if (!(host && freq)) { + pr_err("%s: unexpected host/freq parameter\n", __func__); + err = -EINVAL; + goto out; } + clk_scaling = &host->clk_scaling; + + if (!clk_scaling->enable) + goto out; + + pr_debug("%s: target freq = %lu (%s)\n", mmc_hostname(host), + *freq, current->comm); + + if ((clk_scaling->curr_freq == *freq) || + clk_scaling->skip_clk_scale_freq_update) + goto out; + + /* No need to scale the clocks if they are gated */ + if (!host->ios.clock) + goto out; + + spin_lock_irqsave(&clk_scaling->lock, flags); + if (clk_scaling->clk_scaling_in_progress) { + pr_debug("%s: clocks scaling is already in-progress by mmc thread\n", + mmc_hostname(host)); + spin_unlock_irqrestore(&clk_scaling->lock, flags); + goto out; + } + clk_scaling->need_freq_change = true; + clk_scaling->target_freq = *freq; + clk_scaling->state = *freq < clk_scaling->curr_freq ? + MMC_LOAD_LOW : MMC_LOAD_HIGH; + spin_unlock_irqrestore(&clk_scaling->lock, flags); + + abort = __mmc_claim_host(host, &clk_scaling->devfreq_abort); + if (abort) + goto out; + + /* + * In case we were able to claim host there is no need to + * defer the frequency change. It will be done now + */ + clk_scaling->need_freq_change = false; + + mmc_host_clk_hold(host); + err = mmc_clk_update_freq(host, *freq, clk_scaling->state); + if (err && err != -EAGAIN) + pr_err("%s: clock scale to %lu failed with error %d\n", + mmc_hostname(host), *freq, err); + else + pr_debug("%s: clock change to %lu finished successfully (%s)\n", + mmc_hostname(host), *freq, current->comm); + + + mmc_host_clk_release(host); + mmc_release_host(host); +out: + return err; } + +static void mmc_deferred_scaling(struct mmc_host *host) +{ + unsigned long flags; + unsigned long target_freq; + int err; + + if (!host->clk_scaling.enable) + return; + + spin_lock_irqsave(&host->clk_scaling.lock, flags); + + if (host->clk_scaling.clk_scaling_in_progress || + !(host->clk_scaling.need_freq_change)) { + spin_unlock_irqrestore(&host->clk_scaling.lock, flags); + return; + } + + + atomic_inc(&host->clk_scaling.devfreq_abort); + target_freq = host->clk_scaling.target_freq; + host->clk_scaling.clk_scaling_in_progress = true; + host->clk_scaling.need_freq_change = false; + spin_unlock_irqrestore(&host->clk_scaling.lock, flags); + pr_debug("%s: doing deferred frequency change (%lu) (%s)\n", + mmc_hostname(host), + target_freq, current->comm); + + err = mmc_clk_update_freq(host, target_freq, + host->clk_scaling.state); + if (err && err != -EAGAIN) + pr_err("%s: failed on deferred scale clocks (%d)\n", + mmc_hostname(host), err); + else + pr_debug("%s: clocks were successfully scaled to %lu (%s)\n", + mmc_hostname(host), + target_freq, current->comm); + host->clk_scaling.clk_scaling_in_progress = false; + atomic_dec(&host->clk_scaling.devfreq_abort); +} + +static int mmc_devfreq_create_freq_table(struct mmc_host *host) +{ + int i; + struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; + + pr_debug("%s: supported: lowest=%lu, highest=%lu\n", + mmc_hostname(host), + host->card->clk_scaling_lowest, + host->card->clk_scaling_highest); + + if (!clk_scaling->freq_table) { + pr_debug("%s: no frequency table defined - setting default\n", + mmc_hostname(host)); + clk_scaling->freq_table = kzalloc( + 2*sizeof(*(clk_scaling->freq_table)), GFP_KERNEL); + if (!clk_scaling->freq_table) + return -ENOMEM; + clk_scaling->freq_table[0] = host->card->clk_scaling_lowest; + clk_scaling->freq_table[1] = host->card->clk_scaling_highest; + clk_scaling->freq_table_sz = 2; + goto out; + } + + if (host->card->clk_scaling_lowest > + clk_scaling->freq_table[0]) + pr_debug("%s: frequency table undershot possible freq\n", + mmc_hostname(host)); + + for (i = 0; i < clk_scaling->freq_table_sz; i++) { + if (clk_scaling->freq_table[i] <= + host->card->clk_scaling_highest) + continue; + clk_scaling->freq_table[i] = + host->card->clk_scaling_highest; + clk_scaling->freq_table_sz = i + 1; + pr_debug("%s: frequency table overshot possible freq (%d)\n", + mmc_hostname(host), clk_scaling->freq_table[i]); + break; + } + +out: + clk_scaling->devfreq_profile.freq_table = clk_scaling->freq_table; + clk_scaling->devfreq_profile.max_state = clk_scaling->freq_table_sz; + + for (i = 0; i < clk_scaling->freq_table_sz; i++) + pr_debug("%s: freq[%d] = %u\n", + mmc_hostname(host), i, clk_scaling->freq_table[i]); + + return 0; +} + +/** + * mmc_init_devfreq_clk_scaling() - Initialize clock scaling + * @host: pointer to mmc host structure + * + * Initialize clock scaling for supported hosts. It is assumed that the caller + * ensure clock is running at maximum possible frequency before calling this + * function. Shall use struct devfreq_simple_ondemand_data to configure + * governor. + */ +int mmc_init_clk_scaling(struct mmc_host *host) +{ + int err; + + if (!host || !host->card) { + pr_err("%s: unexpected host/card parameters\n", + __func__); + return -EINVAL; + } + + if (!mmc_can_scale_clk(host) || + !host->bus_ops->change_bus_speed) { + pr_debug("%s: clock scaling is not supported\n", + mmc_hostname(host)); + return 0; + } + + pr_debug("registering %s dev (%p) to devfreq", + mmc_hostname(host), + mmc_classdev(host)); + + if (host->clk_scaling.devfreq) { + pr_err("%s: dev is already registered for dev %p\n", + mmc_hostname(host), + mmc_dev(host)); + return -EPERM; + } + spin_lock_init(&host->clk_scaling.lock); + atomic_set(&host->clk_scaling.devfreq_abort, 0); + host->clk_scaling.curr_freq = host->ios.clock; + host->clk_scaling.clk_scaling_in_progress = false; + host->clk_scaling.need_freq_change = false; + host->clk_scaling.is_busy_started = false; + + host->clk_scaling.devfreq_profile.polling_ms = + host->clk_scaling.polling_delay_ms; + host->clk_scaling.devfreq_profile.get_dev_status = + mmc_devfreq_get_dev_status; + host->clk_scaling.devfreq_profile.target = mmc_devfreq_set_target; + host->clk_scaling.devfreq_profile.initial_freq = host->ios.clock; + + host->clk_scaling.ondemand_gov_data.simple_scaling = true; + host->clk_scaling.ondemand_gov_data.upthreshold = + host->clk_scaling.upthreshold; + host->clk_scaling.ondemand_gov_data.downdifferential = + host->clk_scaling.downthreshold; + + err = mmc_devfreq_create_freq_table(host); + if (err) { + pr_err("%s: fail to create devfreq frequency table\n", + mmc_hostname(host)); + return err; + } + + pr_debug("%s: adding devfreq with: upthreshold=%u downthreshold=%u polling=%u\n", + mmc_hostname(host), + host->clk_scaling.ondemand_gov_data.upthreshold, + host->clk_scaling.ondemand_gov_data.downdifferential, + host->clk_scaling.devfreq_profile.polling_ms); + host->clk_scaling.devfreq = devfreq_add_device( + mmc_classdev(host), + &host->clk_scaling.devfreq_profile, + "simple_ondemand", + &host->clk_scaling.ondemand_gov_data); + if (!host->clk_scaling.devfreq) { + pr_err("%s: unable to register with devfreq\n", + mmc_hostname(host)); + return -EPERM; + } + + pr_debug("%s: clk scaling is enabled for device %s (%p) with devfreq %p (clock = %uHz)\n", + mmc_hostname(host), + dev_name(mmc_classdev(host)), + mmc_classdev(host), + host->clk_scaling.devfreq, + host->ios.clock); + + host->clk_scaling.enable = true; + + return err; +} +EXPORT_SYMBOL(mmc_init_clk_scaling); + +/** + * mmc_exit_devfreq_clk_scaling() - Disable clock scaling + * @host: pointer to mmc host structure + * + * Disable clock scaling permanently. + */ +int mmc_exit_clk_scaling(struct mmc_host *host) +{ + int err; + + if (!host) { + pr_err("bad host parameter\n"); + WARN_ON(1); + return -EINVAL; + } + + if (!mmc_can_scale_clk(host)) + return 0; + + if (!host->clk_scaling.devfreq) { + pr_err("%s: no devfreq is assosiated with this device\n", + mmc_hostname(host)); + return -EPERM; + } + + host->clk_scaling.enable = false; + atomic_inc(&host->clk_scaling.devfreq_abort); + err = devfreq_suspend_device(host->clk_scaling.devfreq); + if (err) { + pr_err("%s: failed to suspend devfreq\n", mmc_hostname(host)); + return err; + } + pr_debug("%s: devfreq suspended\n", mmc_hostname(host)); + + err = devfreq_remove_device(host->clk_scaling.devfreq); + if (err) { + pr_err("%s: remove devfreq failed\n", mmc_hostname(host)); + return err; + } + + host->clk_scaling.devfreq = NULL; + atomic_set(&host->clk_scaling.devfreq_abort, 1); + mmc_reset_clk_scale_stats(host); + pr_debug("%s: devfreq was removed\n", mmc_hostname(host)); + + return 0; +} +EXPORT_SYMBOL(mmc_exit_clk_scaling); + + +/** + * mmc_reset_clk_scale_stats() - reset clock scaling statistics + * @host: pointer to mmc host structure + */ +void mmc_reset_clk_scale_stats(struct mmc_host *host) +{ + unsigned long flags; + + if (!host) { + pr_err("bad host parameter\n"); + WARN_ON(1); + return; + } + + spin_lock_irqsave(&host->clk_scaling.lock, flags); + host->clk_scaling.total_busy_time_us = 0; + spin_unlock_irqrestore(&host->clk_scaling.lock, flags); + +} +EXPORT_SYMBOL(mmc_reset_clk_scale_stats); + /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -177,8 +674,9 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) #ifdef CONFIG_MMC_PERF_PROFILING ktime_t diff; #endif - if (host->card) - mmc_update_clk_scaling(host); + + if (host->clk_scaling.is_busy_started) + mmc_clk_scaling_stop_busy(host, true); /* Flag re-tuning needed on CRC errors */ if ((cmd->opcode != MMC_SEND_TUNING_BLOCK && @@ -365,20 +863,9 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mmc_host_clk_hold(host); led_trigger_event(host->led, LED_FULL); - if (host->card && host->clk_scaling.enable) { - /* - * Check if we need to scale the clocks. Clocks - * will be scaled up immediately if necessary - * conditions are satisfied. Scaling down the - * frequency will be done after current thread - * releases host. - */ - mmc_clk_scaling_update_state(host, mrq); - if (!host->clk_scaling.invalid_state) { - mmc_clk_scaling(host, false); - host->clk_scaling.start_busy = ktime_get(); - } - } + mmc_deferred_scaling(host); + if (mmc_is_data_request(mrq)) + mmc_clk_scaling_start_busy(host, true); __mmc_start_request(host, mrq); @@ -2710,367 +3197,6 @@ int mmc_hw_reset(struct mmc_host *host) } EXPORT_SYMBOL(mmc_hw_reset); -/** - * mmc_reset_clk_scale_stats() - reset clock scaling statistics - * @host: pointer to mmc host structure - */ -void mmc_reset_clk_scale_stats(struct mmc_host *host) -{ - host->clk_scaling.busy_time_us = 0; - host->clk_scaling.window_time = jiffies; -} -EXPORT_SYMBOL_GPL(mmc_reset_clk_scale_stats); - -/** - * mmc_get_max_frequency() - get max. frequency supported - * @host: pointer to mmc host structure - * - * Returns max. frequency supported by card/host. If the - * timing mode is SDR50/SDR104/HS200/DDR50 return appropriate - * max. frequency in these modes else, use the current frequency. - * Also, allow host drivers to overwrite the frequency in case - * they support "get_max_frequency" host ops. - */ -unsigned long mmc_get_max_frequency(struct mmc_host *host) -{ - unsigned long freq; - unsigned char timing; - - if (host->ops && host->ops->get_max_frequency) { - freq = host->ops->get_max_frequency(host); - goto out; - } - - if (mmc_card_hs400(host->card)) - timing = MMC_TIMING_MMC_HS400; - else - timing = host->ios.timing; - - switch (timing) { - case MMC_TIMING_UHS_SDR50: - freq = UHS_SDR50_MAX_DTR; - break; - case MMC_TIMING_UHS_SDR104: - freq = UHS_SDR104_MAX_DTR; - break; - case MMC_TIMING_MMC_HS200: - freq = MMC_HS200_MAX_DTR; - break; - case MMC_TIMING_UHS_DDR50: - freq = UHS_DDR50_MAX_DTR; - break; - case MMC_TIMING_MMC_HS400: - freq = MMC_HS200_MAX_DTR; - break; - default: - mmc_host_clk_hold(host); - freq = host->ios.clock; - mmc_host_clk_release(host); - break; - } - -out: - return freq; -} -EXPORT_SYMBOL_GPL(mmc_get_max_frequency); - -/** - * mmc_get_min_frequency() - get min. frequency supported - * @host: pointer to mmc host structure - * - * Returns min. frequency supported by card/host which doesn't impair - * performance for most usecases. If the timing mode is SDR50/SDR104/HS200 - * return 50MHz value. If timing mode is DDR50 return 25MHz so that - * throughput would be equivalent to SDR50/SDR104 in 50MHz. Also, allow - * host drivers to overwrite the frequency in case they support - * "get_min_frequency" host ops. - */ -static unsigned long mmc_get_min_frequency(struct mmc_host *host) -{ - unsigned long freq; - - if (host->ops && host->ops->get_min_frequency) { - freq = host->ops->get_min_frequency(host); - goto out; - } - - switch (host->ios.timing) { - case MMC_TIMING_UHS_SDR50: - case MMC_TIMING_UHS_SDR104: - freq = UHS_SDR25_MAX_DTR; - break; - case MMC_TIMING_MMC_HS200: - freq = MMC_HIGH_52_MAX_DTR; - break; - case MMC_TIMING_MMC_HS400: - freq = MMC_HIGH_52_MAX_DTR; - break; - case MMC_TIMING_UHS_DDR50: - freq = UHS_DDR50_MAX_DTR / 2; - break; - default: - mmc_host_clk_hold(host); - freq = host->ios.clock; - mmc_host_clk_release(host); - break; - } - -out: - return freq; -} - -/* - * Scale down clocks to minimum frequency supported. - * The delayed work re-arms itself in case it cannot - * claim the host. - */ -static void mmc_clk_scale_work(struct work_struct *work) -{ - struct mmc_host *host = container_of(work, struct mmc_host, - clk_scaling.work.work); - - if (!host->card || !host->bus_ops || - !host->bus_ops->change_bus_speed || - !host->clk_scaling.enable || !host->ios.clock) - goto out; - - mmc_clk_scaling(host, true); - mmc_release_host(host); -out: - return; -} - -static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) -{ - struct mmc_card *card = host->card; - u32 status; - bool ret = false; - - /* - * If the current partition type is RPMB, clock switching may not - * work properly as sending tuning command (CMD21) is illegal in - * this mode. - */ - if (!card || (mmc_card_mmc(card) && - card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB) - || host->clk_scaling.invalid_state) - goto out; - - if (mmc_send_status(card, &status)) { - pr_err("%s: Get card status fail\n", mmc_hostname(card->host)); - goto out; - } - - switch (R1_CURRENT_STATE(status)) { - case R1_STATE_TRAN: - ret = true; - break; - default: - break; - } -out: - return ret; -} - -static int mmc_clk_update_freq(struct mmc_host *host, - unsigned long freq, enum mmc_load state) -{ - int err = 0; - - if (host->ops->notify_load) { - err = host->ops->notify_load(host, state); - if (err) - goto out; - } - - if (freq != host->clk_scaling.curr_freq) { - if (!mmc_is_vaild_state_for_clk_scaling(host)) { - err = -EAGAIN; - goto error; - } - - err = host->bus_ops->change_bus_speed(host, &freq); - if (!err) - host->clk_scaling.curr_freq = freq; - else - pr_err("%s: %s: failed (%d) at freq=%lu\n", - mmc_hostname(host), __func__, err, freq); - } -error: - if (err) { - /* restore previous state */ - if (host->ops->notify_load) - host->ops->notify_load(host, host->clk_scaling.state); - } -out: - return err; -} - -/** - * mmc_clk_scaling() - clock scaling decision algorithm - * @host: pointer to mmc host structure - * @from_wq: variable that specifies the context in which - * mmc_clk_scaling() is called. - * - * Calculate load percentage based on host busy time - * and total sampling interval and decide clock scaling - * based on scale up/down thresholds. - * If load is greater than up threshold increase the - * frequency to maximum as supported by host. Else, - * if load is less than down threshold, scale down the - * frequency to minimum supported by the host. Otherwise, - * retain current frequency and do nothing. - */ -static void mmc_clk_scaling(struct mmc_host *host, bool from_wq) -{ - int err = 0; - struct mmc_card *card = host->card; - unsigned long total_time_ms = 0; - unsigned long busy_time_ms = 0; - unsigned long freq; - unsigned int up_threshold = host->clk_scaling.up_threshold; - unsigned int down_threshold = host->clk_scaling.down_threshold; - bool queue_scale_down_work = false; - enum mmc_load state; - - if (!card || !host->bus_ops || !host->bus_ops->change_bus_speed) { - pr_err("%s: %s: invalid entry\n", mmc_hostname(host), __func__); - goto out; - } - - /* Check if the clocks are already gated. */ - if (!host->ios.clock) - goto out; - - if (time_is_after_jiffies(host->clk_scaling.window_time + - msecs_to_jiffies(host->clk_scaling.polling_delay_ms))) - goto out; - - /* handle time wrap */ - total_time_ms = jiffies_to_msecs((long)jiffies - - (long)host->clk_scaling.window_time); - - /* Check if we re-enter during clock switching */ - if (unlikely(host->clk_scaling.in_progress)) - goto out; - - host->clk_scaling.in_progress = true; - - busy_time_ms = host->clk_scaling.busy_time_us / USEC_PER_MSEC; - - freq = host->clk_scaling.curr_freq; - state = host->clk_scaling.state; - - /* - * Note that the max. and min. frequency should be based - * on the timing modes that the card and host handshake - * during initialization. - */ - if ((busy_time_ms * 100 > total_time_ms * up_threshold)) { - freq = mmc_get_max_frequency(host); - state = MMC_LOAD_HIGH; - } else if ((busy_time_ms * 100 < total_time_ms * down_threshold)) { - if (!from_wq) - queue_scale_down_work = true; - freq = mmc_get_min_frequency(host); - state = MMC_LOAD_LOW; - } - - if (state != host->clk_scaling.state) { - if (!queue_scale_down_work) { - if (!from_wq) - cancel_delayed_work_sync( - &host->clk_scaling.work); - err = mmc_clk_update_freq(host, freq, state); - if (!err) - host->clk_scaling.state = state; - else if (err == -EAGAIN) - goto no_reset_stats; - } else { - /* - * We hold claim host while queueing the scale down - * work, so delay atleast one timer tick to release - * host and re-claim while scaling down the clocks. - */ - queue_delayed_work(system_wq, - &host->clk_scaling.work, 1); - goto no_reset_stats; - } - } - - mmc_reset_clk_scale_stats(host); -no_reset_stats: - host->clk_scaling.in_progress = false; -out: - return; -} - -/** - * mmc_disable_clk_scaling() - Disable clock scaling - * @host: pointer to mmc host structure - * - * Disables clock scaling temporarily by setting enable - * property to false. To disable completely, one also - * need to set 'initialized' variable to false. - */ -void mmc_disable_clk_scaling(struct mmc_host *host) -{ - if (host->clk_scaling.initialized) { - cancel_delayed_work_sync(&host->clk_scaling.work); - host->clk_scaling.enable = false; - } -} -EXPORT_SYMBOL_GPL(mmc_disable_clk_scaling); - -/** - * mmc_can_scale_clk() - Check if clock scaling is initialized - * @host: pointer to mmc host structure - */ -bool mmc_can_scale_clk(struct mmc_host *host) -{ - return host->clk_scaling.initialized; -} -EXPORT_SYMBOL_GPL(mmc_can_scale_clk); - -/** - * mmc_init_clk_scaling() - Initialize clock scaling - * @host: pointer to mmc host structure - * - * Initialize clock scaling for supported hosts. - * It is assumed that the caller ensure clock is - * running at maximum possible frequency before - * calling this function. - */ -void mmc_init_clk_scaling(struct mmc_host *host) -{ - if (!host->card || !(host->caps2 & MMC_CAP2_CLK_SCALE)) - return; - - INIT_DELAYED_WORK(&host->clk_scaling.work, mmc_clk_scale_work); - host->clk_scaling.curr_freq = mmc_get_max_frequency(host); - if (host->ops->notify_load) - host->ops->notify_load(host, MMC_LOAD_HIGH); - host->clk_scaling.state = MMC_LOAD_HIGH; - mmc_reset_clk_scale_stats(host); - host->clk_scaling.enable = true; - host->clk_scaling.initialized = true; - pr_debug("%s: clk scaling enabled\n", mmc_hostname(host)); -} -EXPORT_SYMBOL_GPL(mmc_init_clk_scaling); - -/** - * mmc_exit_clk_scaling() - Disable clock scaling - * @host: pointer to mmc host structure - * - * Disable clock scaling permanently. - */ -void mmc_exit_clk_scaling(struct mmc_host *host) -{ - cancel_delayed_work_sync(&host->clk_scaling.work); - memset(&host->clk_scaling, 0, sizeof(host->clk_scaling)); -} -EXPORT_SYMBOL_GPL(mmc_exit_clk_scaling); - static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index 9bf804ee35a2..c4ab8ce9fd90 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -41,6 +41,8 @@ void mmc_init_erase(struct mmc_card *card); void mmc_set_chip_select(struct mmc_host *host, int mode); void mmc_set_clock(struct mmc_host *host, unsigned int hz); +int mmc_clk_update_freq(struct mmc_host *host, + unsigned long freq, enum mmc_load state); void mmc_gate_clock(struct mmc_host *host); void mmc_ungate_clock(struct mmc_host *host); void mmc_set_ungated(struct mmc_host *host); @@ -94,8 +96,8 @@ void mmc_init_context_info(struct mmc_host *host); extern void mmc_disable_clk_scaling(struct mmc_host *host); extern bool mmc_can_scale_clk(struct mmc_host *host); -extern void mmc_init_clk_scaling(struct mmc_host *host); -extern void mmc_exit_clk_scaling(struct mmc_host *host); +extern int mmc_init_clk_scaling(struct mmc_host *host); +extern int mmc_exit_clk_scaling(struct mmc_host *host); extern void mmc_reset_clk_scale_stats(struct mmc_host *host); extern unsigned long mmc_get_max_frequency(struct mmc_host *host); diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 1479a96dfee4..9f75060d3b0c 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -234,6 +234,45 @@ static int mmc_clock_opt_set(void *data, u64 val) DEFINE_SIMPLE_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set, "%llu\n"); +#include + +static int mmc_scale_get(void *data, u64 *val) +{ + struct mmc_host *host = data; + + *val = host->clk_scaling.curr_freq; + + return 0; +} + +static int mmc_scale_set(void *data, u64 val) +{ + int err = 0; + struct mmc_host *host = data; + + mmc_claim_host(host); + mmc_host_clk_hold(host); + + /* change frequency from sysfs manually */ + err = mmc_clk_update_freq(host, val, host->clk_scaling.state); + if (err == -EAGAIN) + err = 0; + else if (err) + pr_err("%s: clock scale to %llu failed with error %d\n", + mmc_hostname(host), val, err); + else + pr_debug("%s: clock change to %llu finished successfully (%s)\n", + mmc_hostname(host), val, current->comm); + + mmc_host_clk_release(host); + mmc_release_host(host); + + return err; +} + +DEFINE_SIMPLE_ATTRIBUTE(mmc_scale_fops, mmc_scale_get, mmc_scale_set, + "%llu\n"); + static int mmc_max_clock_get(void *data, u64 *val) { struct mmc_host *host = data; @@ -300,6 +339,15 @@ void mmc_add_host_debugfs(struct mmc_host *host) &mmc_max_clock_fops)) goto err_node; + if (!debugfs_create_file("scale", S_IRUSR | S_IWUSR, root, host, + &mmc_scale_fops)) + goto err_node; + + if (!debugfs_create_bool("skip_clk_scale_freq_update", + S_IRUSR | S_IWUSR, root, + &host->clk_scaling.skip_clk_scale_freq_update)) + goto err_node; + #ifdef CONFIG_MMC_CLKGATE if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR), root, &host->clk_delay)) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b0e8a7b69774..2871f21803bd 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -34,6 +34,9 @@ #include "pwrseq.h" #define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev) +#define MMC_DEVFRQ_DEFAULT_UP_THRESHOLD 35 +#define MMC_DEVFRQ_DEFAULT_DOWN_THRESHOLD 5 +#define MMC_DEVFRQ_DEFAULT_POLLING_MSEC 100 static DEFINE_IDR(mmc_host_idr); static DEFINE_SPINLOCK(mmc_host_lock); @@ -638,45 +641,27 @@ static ssize_t store_enable(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 value, freq; - int retval = -EINVAL; + unsigned long value; - if (!host) - goto out; + if (!host || kstrtoul(buf, 0, &value)) + return -EINVAL; mmc_claim_host(host); - if (!host->card || kstrtoul(buf, 0, &value)) - goto err; - - if (value && !mmc_can_scale_clk(host)) { - host->caps2 |= MMC_CAP2_CLK_SCALE; - mmc_init_clk_scaling(host); - - if (!mmc_can_scale_clk(host)) { - host->caps2 &= ~MMC_CAP2_CLK_SCALE; - goto err; - } - } else if (!value && mmc_can_scale_clk(host)) { + if (!value && host->clk_scaling.enable) { + /*turnning off clock scaling*/ + mmc_exit_clk_scaling(host); host->caps2 &= ~MMC_CAP2_CLK_SCALE; - mmc_disable_clk_scaling(host); - - /* Set to max. frequency, since we are disabling */ - if (host->bus_ops && host->bus_ops->change_bus_speed) { - freq = mmc_get_max_frequency(host); - if (host->bus_ops->change_bus_speed(host, &freq)) - goto err; - } - if (host->ops->notify_load && - host->ops->notify_load(host, MMC_LOAD_HIGH)) - goto err; - host->clk_scaling.state = MMC_LOAD_HIGH; - host->clk_scaling.initialized = false; + } else if (value) { + /* starting clock scaling, will restart in case started */ + host->caps2 |= MMC_CAP2_CLK_SCALE; + if (host->clk_scaling.enable) + mmc_exit_clk_scaling(host); + mmc_init_clk_scaling(host); } - retval = count; -err: + mmc_release_host(host); -out: - return retval; + + return count; } static ssize_t show_up_threshold(struct device *dev, @@ -687,7 +672,7 @@ static ssize_t show_up_threshold(struct device *dev, if (!host) return -EINVAL; - return snprintf(buf, PAGE_SIZE, "%d\n", host->clk_scaling.up_threshold); + return snprintf(buf, PAGE_SIZE, "%d\n", host->clk_scaling.upthreshold); } #define MAX_PERCENTAGE 100 @@ -700,7 +685,7 @@ static ssize_t store_up_threshold(struct device *dev, if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE)) return -EINVAL; - host->clk_scaling.up_threshold = value; + host->clk_scaling.upthreshold = value; pr_debug("%s: clkscale_up_thresh set to %lu\n", mmc_hostname(host), value); @@ -716,7 +701,7 @@ static ssize_t show_down_threshold(struct device *dev, return -EINVAL; return snprintf(buf, PAGE_SIZE, "%d\n", - host->clk_scaling.down_threshold); + host->clk_scaling.downthreshold); } static ssize_t store_down_threshold(struct device *dev, @@ -728,7 +713,7 @@ static ssize_t store_down_threshold(struct device *dev, if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE)) return -EINVAL; - host->clk_scaling.down_threshold = value; + host->clk_scaling.downthreshold = value; pr_debug("%s: clkscale_down_thresh set to %lu\n", mmc_hostname(host), value); @@ -867,15 +852,16 @@ int mmc_add_host(struct mmc_host *host) led_trigger_register_simple(dev_name(&host->class_dev), &host->led); + host->clk_scaling.upthreshold = MMC_DEVFRQ_DEFAULT_UP_THRESHOLD; + host->clk_scaling.downthreshold = MMC_DEVFRQ_DEFAULT_DOWN_THRESHOLD; + host->clk_scaling.polling_delay_ms = MMC_DEVFRQ_DEFAULT_POLLING_MSEC; + host->clk_scaling.skip_clk_scale_freq_update = false; + #ifdef CONFIG_DEBUG_FS mmc_add_host_debugfs(host); #endif mmc_host_clk_sysfs_init(host); - host->clk_scaling.up_threshold = 35; - host->clk_scaling.down_threshold = 5; - host->clk_scaling.polling_delay_ms = 100; - err = sysfs_create_group(&host->class_dev.kobj, &clk_scaling_attr_grp); if (err) pr_err("%s: failed to create clk scale sysfs group with err %d\n", diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index d08629b4c385..dda6600a25d9 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1809,6 +1809,16 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + card->clk_scaling_lowest = host->f_min; + if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS400) | + (card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS200)) + card->clk_scaling_highest = card->ext_csd.hs200_max_dtr; + else if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS) | + (card->mmc_avail_type | EXT_CSD_CARD_TYPE_DDR_52)) + card->clk_scaling_highest = card->ext_csd.hs_max_dtr; + else + card->clk_scaling_highest = card->csd.max_dtr; + /* * Choose the power class with selected bus interface */ @@ -2110,7 +2120,8 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) * Disable clock scaling before suspend and enable it after resume so * as to avoid clock scaling decisions kicking in during this window. */ - mmc_disable_clk_scaling(host); + if (mmc_can_scale_clk(host)) + mmc_disable_clk_scaling(host); if (mmc_card_doing_bkops(host->card)) { err = mmc_stop_bkops(host->card); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 8fefdc9bf153..c7aaa9bbb1be 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1101,6 +1101,9 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, } } + card->clk_scaling_highest = mmc_sd_get_max_clock(card); + card->clk_scaling_lowest = host->f_min; + host->card = card; return 0; diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e44c5c4c1043..c60b2a9ddcd6 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1405,6 +1405,16 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, pdata->cpu_dma_latency_us = cpu_dma_latency; else pdata->cpu_dma_latency_us = MSM_MMC_DEFAULT_CPU_DMA_LATENCY; + + if (sdhci_msm_dt_get_array(dev, "qcom,devfreq,freq-table", + &msm_host->mmc->clk_scaling.freq_table, + &msm_host->mmc->clk_scaling.freq_table_sz, 0)) + pr_debug("%s: no clock scaling frequencies were supplied\n", + dev_name(dev)); + else if (!msm_host->mmc->clk_scaling.freq_table || + !msm_host->mmc->clk_scaling.freq_table_sz) + dev_err(dev, "bad dts clock scaling frequencies\n"); + if (sdhci_msm_dt_get_array(dev, "qcom,clk-rates", &clk_table, &clk_table_len, 0)) { dev_err(dev, "failed parsing supported clock rates\n"); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 59ef4e717beb..ae943ccb331e 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -275,6 +275,10 @@ struct mmc_card { struct mmc_host *host; /* the host this device belongs to */ struct device dev; /* the device */ u32 ocr; /* the current OCR setting */ + unsigned long clk_scaling_lowest; /* lowest scaleable + * frequency */ + unsigned long clk_scaling_highest; /* highest scaleable + * frequency */ unsigned int rca; /* relative card address of device */ unsigned int type; /* card type */ #define MMC_TYPE_MMC 0 /* MMC card */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 2ba8ba249e00..5afbac264ddd 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -224,9 +225,57 @@ enum dev_state { DEV_RESUMED, }; +/** + * struct mmc_devfeq_clk_scaling - main context for MMC clock scaling logic + * + * @lock: spinlock to protect statistics + * @devfreq: struct that represent mmc-host as a client for devfreq + * @devfreq_profile: MMC device profile, mostly polling interval and callbacks + * @ondemand_gov_data: struct supplied to ondemmand governor (thresholds) + * @state: load state, can be HIGH or LOW. used to notify mmc_host_ops callback + * @start_busy: timestamped armed once a data request is started + * @measure_interval_start: timestamped armed once a measure interval started + * @devfreq_abort: flag to sync between different contexts relevant to devfreq + * @skip_clk_scale_freq_update: flag that enable/disable frequency change + * @freq_table_sz: table size of frequencies supplied to devfreq + * @freq_table: frequencies table supplied to devfreq + * @curr_freq: current frequency + * @polling_delay_ms: polling interval for status collection used by devfreq + * @upthreshold: up-threshold supplied to ondemand governor + * @downthreshold: down-threshold supplied to ondemand governor + * @need_freq_change: flag indicating if a frequency change is required + * @clk_scaling_in_progress: flag indicating if there's ongoing frequency change + * @is_busy_started: flag indicating if a request is handled by the HW + * @enable: flag indicating if the clock scaling logic is enabled for this host + */ +struct mmc_devfeq_clk_scaling { + spinlock_t lock; + struct devfreq *devfreq; + struct devfreq_dev_profile devfreq_profile; + struct devfreq_simple_ondemand_data ondemand_gov_data; + enum mmc_load state; + ktime_t start_busy; + ktime_t measure_interval_start; + atomic_t devfreq_abort; + bool skip_clk_scale_freq_update; + int freq_table_sz; + u32 *freq_table; + unsigned long total_busy_time_us; + unsigned long target_freq; + unsigned long curr_freq; + unsigned long polling_delay_ms; + unsigned int upthreshold; + unsigned int downthreshold; + bool need_freq_change; + bool clk_scaling_in_progress; + bool is_busy_started; + bool enable; +}; + struct mmc_host { struct device *parent; struct device class_dev; + struct mmc_devfeq_clk_scaling clk_scaling; int index; const struct mmc_host_ops *ops; struct mmc_pwrseq *pwrseq; @@ -441,22 +490,6 @@ struct mmc_host { } perf; bool perf_enable; #endif - struct { - unsigned long busy_time_us; - unsigned long window_time; - unsigned long curr_freq; - unsigned long polling_delay_ms; - unsigned int up_threshold; - unsigned int down_threshold; - ktime_t start_busy; - bool enable; - bool initialized; - bool in_progress; - /* freq. transitions are not allowed in invalid state */ - bool invalid_state; - struct delayed_work work; - enum mmc_load state; - } clk_scaling; enum dev_state dev_status; bool wakeup_on_idle; unsigned long private[0] ____cacheline_aligned; From a968943d3557b2b3ef444fe1059c948ddb26b8cd Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 27 May 2015 14:20:34 +0300 Subject: [PATCH 239/472] mmc: fix MMC clock scaling to meet upstream HS400 implementation On the 3.10 kernel branch we had an implementation supporting HS400 that was different than that in the Linux community code base. As part of the transition to kernel 3.14, the community's implementation was used. However, as this implementation does not properly support up/down clock scaling - this patch adds the missing functionality. Change-Id: I096132bc715909b1ff2ac84448ec0adb32ca06ba Signed-off-by: Talel Shenhar --- drivers/mmc/core/core.c | 15 +++ drivers/mmc/core/mmc.c | 263 +++++++++++++++++++++++++--------------- drivers/mmc/core/sd.c | 11 -- 3 files changed, 179 insertions(+), 110 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0340980c0b77..5f2145cbb7ad 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -298,6 +298,21 @@ int mmc_clk_update_freq(struct mmc_host *host, return -EINVAL; } + /* make sure the card supports the frequency we want */ + if (unlikely(freq > host->card->clk_scaling_highest)) { + freq = host->card->clk_scaling_highest; + pr_warn("%s: %s: frequency was overridden to %lu\n", + mmc_hostname(host), __func__, + host->card->clk_scaling_highest); + } + + if (unlikely(freq < host->card->clk_scaling_lowest)) { + freq = host->card->clk_scaling_lowest; + pr_warn("%s: %s: frequency was overridden to %lu\n", + mmc_hostname(host), __func__, + host->card->clk_scaling_lowest); + } + if (host->ops->notify_load) { err = host->ops->notify_load(host, state); if (err) { diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index dda6600a25d9..16f43e4b1d99 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1366,98 +1366,6 @@ err: return err; } -int mmc_set_clock_bus_speed(struct mmc_card *card, unsigned long freq) -{ - int err; - - if (freq < MMC_HS200_MAX_DTR) { - /* - * Lower the clock and adjust the timing to be able - * to switch to HighSpeed mode - */ - mmc_set_timing(card->host, MMC_TIMING_LEGACY); - mmc_set_clock(card->host, MMC_HIGH_26_MAX_DTR); - - err = mmc_select_hs(card); - } else { - err = mmc_select_hs400(card); - } - - return err; -} - -/** - * mmc_change_bus_speed() - Change MMC card bus frequency at runtime - * @host: pointer to mmc host structure - * @freq: pointer to desired frequency to be set - * - * Change the MMC card bus frequency at runtime after the card is - * initialized. Callers are expected to make sure of the card's - * state (DATA/RCV/TRANSFER) beforing changing the frequency at runtime. - * - * If the frequency to change is greater than max. supported by card, - * *freq is changed to max. supported by card and if it is less than min. - * supported by host, *freq is changed to min. supported by host. - */ -static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) -{ - int err = 0; - struct mmc_card *card; - - mmc_claim_host(host); - /* - * Assign card pointer after claiming host to avoid race - * conditions that may arise during removal of the card. - */ - card = host->card; - - if (!card || !freq) { - err = -EINVAL; - goto out; - } - - if (mmc_card_hs(card) || mmc_card_hs200(card) - || mmc_card_ddr52(card)) { - if (*freq > card->ext_csd.hs_max_dtr) - *freq = card->ext_csd.hs_max_dtr; - } else if (*freq > card->csd.max_dtr) { - *freq = card->csd.max_dtr; - } - - if (*freq < host->f_min) - *freq = host->f_min; - - if (mmc_card_hs400(card)) { - err = mmc_set_clock_bus_speed(card, *freq); - if (err) - goto out; - } else { - mmc_set_clock(host, (unsigned int) (*freq)); - } - - if (mmc_card_hs200(card) && card->host->ops->execute_tuning) { - /* - * We try to probe host driver for tuning for any - * frequency, it is host driver responsibility to - * perform actual tuning only when required. - */ - mmc_host_clk_hold(card->host); - err = card->host->ops->execute_tuning(card->host, - MMC_SEND_TUNING_BLOCK_HS200); - mmc_host_clk_release(card->host); - - if (err) { - pr_warn("%s: %s: tuning execution failed %d. Restoring to previous clock %lu\n", - mmc_hostname(card->host), __func__, err, - host->clk_scaling.curr_freq); - mmc_set_clock(host, host->clk_scaling.curr_freq); - } - } -out: - mmc_release_host(host); - return err; -} - static int mmc_reboot_notify(struct notifier_block *notify_block, unsigned long event, void *unused) { @@ -1526,6 +1434,163 @@ static int mmc_hs200_tuning(struct mmc_card *card) return mmc_execute_tuning(card); } +/* + * Scale down from HS400 to HS in order to allow frequency change. + * This is needed for cards that doesn't support changing frequency in HS400 + */ +static int mmc_scale_low(struct mmc_host *host, unsigned long freq) +{ + int err = 0; + + mmc_set_timing(host, MMC_TIMING_LEGACY); + mmc_set_clock(host, MMC_HIGH_26_MAX_DTR); + + err = mmc_select_hs(host->card); + if (err) { + pr_err("%s: %s: selecting HS (52Mhz) failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + err = mmc_select_bus_width(host->card); + if (err < 0) { + pr_err("%s: %s: select_bus_width failed(%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + mmc_set_clock(host, freq); + + return 0; +} + +/* + * Scale UP from HS to HS200/H400 + */ +static int mmc_scale_high(struct mmc_host *host) +{ + int err = 0; + + if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200)) { + pr_err("%s: %s: card does not support HS200\n", + mmc_hostname(host), __func__); + WARN_ON(1); + return -EPERM; + } + + err = mmc_select_hs200(host->card); + if (err) { + pr_err("%s: %s: selecting HS200 failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + mmc_set_bus_speed(host->card); + + err = mmc_hs200_tuning(host->card); + if (err) { + pr_err("%s: %s: hs200 tuning failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400)) { + pr_debug("%s: card does not support HS400\n", + mmc_hostname(host)); + return 0; + } + + err = mmc_select_hs400(host->card); + if (err) { + pr_err("%s: %s: select hs400 failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + return 0; +} + +static int mmc_set_clock_bus_speed(struct mmc_card *card, unsigned long freq) +{ + int err = 0; + + if (freq == MMC_HS200_MAX_DTR) + err = mmc_scale_high(card->host); + else + err = mmc_scale_low(card->host, freq); + + return err; +} + +static inline unsigned long mmc_ddr_freq_accommodation(unsigned long freq) +{ + if (freq == MMC_HIGH_DDR_MAX_DTR) + return freq; + + return freq/2; +} + +/** + * mmc_change_bus_speed() - Change MMC card bus frequency at runtime + * @host: pointer to mmc host structure + * @freq: pointer to desired frequency to be set + * + * Change the MMC card bus frequency at runtime after the card is + * initialized. Callers are expected to make sure of the card's + * state (DATA/RCV/TRANSFER) before changing the frequency at runtime. + * + * If the frequency to change is greater than max. supported by card, + * *freq is changed to max. supported by card. If it is less than min. + * supported by host, *freq is changed to min. supported by host. + * Host is assumed to be calimed while calling this funciton. + */ +static int mmc_change_bus_speed(struct mmc_host *host, unsigned long *freq) +{ + int err = 0; + struct mmc_card *card; + unsigned long actual_freq; + + card = host->card; + + if (!card || !freq) { + err = -EINVAL; + goto out; + } + actual_freq = *freq; + + WARN_ON(!host->claimed); + + /* + * For scaling up/down HS400 we'll need special handling, + * for other timings we can simply do clock frequency change + */ + if (mmc_card_hs400(card) || + (*freq == MMC_HS200_MAX_DTR)) { + err = mmc_set_clock_bus_speed(card, *freq); + if (err) { + pr_err("%s: %s: failed (%d)to set bus and clock speed (freq=%lu)\n", + mmc_hostname(host), __func__, err, *freq); + goto out; + } + } else if (mmc_card_hs200(host->card)) { + mmc_set_clock(host, *freq); + err = mmc_hs200_tuning(host->card); + if (err) { + pr_warn("%s: %s: tuning execution failed %d\n", + mmc_hostname(card->host), + __func__, err); + mmc_set_clock(host, host->clk_scaling.curr_freq); + } + } else { + if (mmc_card_ddr52(host->card)) + actual_freq = mmc_ddr_freq_accommodation(*freq); + mmc_set_clock(host, actual_freq); + } + +out: + return err; +} + /* * Handle the detection and initialisation of a card. * @@ -1810,10 +1875,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } card->clk_scaling_lowest = host->f_min; - if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS400) | + if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS400) || (card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS200)) card->clk_scaling_highest = card->ext_csd.hs200_max_dtr; - else if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS) | + else if ((card->mmc_avail_type | EXT_CSD_CARD_TYPE_HS) || (card->mmc_avail_type | EXT_CSD_CARD_TYPE_DDR_52)) card->clk_scaling_highest = card->ext_csd.hs_max_dtr; else @@ -2111,11 +2176,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) BUG_ON(!host); BUG_ON(!host->card); - mmc_claim_host(host); - - if (mmc_card_suspended(host->card)) - goto out; - /* * Disable clock scaling before suspend and enable it after resume so * as to avoid clock scaling decisions kicking in during this window. @@ -2123,6 +2183,11 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (mmc_can_scale_clk(host)) mmc_disable_clk_scaling(host); + mmc_claim_host(host); + + if (mmc_card_suspended(host->card)) + goto out; + if (mmc_card_doing_bkops(host->card)) { err = mmc_stop_bkops(host->card); if (err) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index c7aaa9bbb1be..44bc036bf2d0 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -610,17 +610,6 @@ static int mmc_sd_change_bus_speed(struct mmc_host *host, unsigned long *freq) goto out; } - if (mmc_card_uhs(card)) { - if (*freq > card->sw_caps.uhs_max_dtr) - *freq = card->sw_caps.uhs_max_dtr; - } else { - if (*freq > mmc_sd_get_max_clock(card)) - *freq = mmc_sd_get_max_clock(card); - } - - if (*freq < host->f_min) - *freq = host->f_min; - mmc_set_clock(host, (unsigned int) (*freq)); if (!mmc_host_is_spi(card->host) && mmc_card_uhs(card) From 5e2fbf9a0da8d4feddfda97701f6804cecf55d69 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Fri, 29 May 2015 11:26:34 +0530 Subject: [PATCH 240/472] mmc: core: Fix crash in mmc clk-scaling kernel crashes with below signature on trying to acquire uninitialized clk-scaling spinlock. Fix it by checking that clk scaling is initialized before acquiring this lock. BUG: spinlock bad magic on CPU#0, kworker/0:1/39 Backtrace: __delay+0x1c __const_udelay+0x24 msm_trigger_wdog_bite+0xbc spin_bug+0x8c do_raw_spin_lock+0x34 _raw_spin_lock_irqsave+0x28 mmc_reset_clk_scale_stats+0x40 mmc_host_clk_hold+0x5c mmc_start_request+0x1ec mmc_wait_for_req+0x98 mmc_wait_for_cmd+0x70 __mmc_switch+0xd0 mmc_switch+0x18 mmc_flush_cache+0x4c _mmc_suspend.isra.8+0x6c mmc_runtime_suspend+0x38 mmc_runtime_suspend+0x18 __rpm_callback+0x40 rpm_callback+0x44 rpm_suspend+0x2a4 pm_runtime_work+0x78 process_one_work+0x264 worker_thread+0x1f8 kthread+0xdc ret_from_fork+0x10 Change-Id: I8347e2d5ad1816f549ef8ab2607546203aa4a21d Signed-off-by: Ritesh Harjani --- drivers/mmc/core/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 5f2145cbb7ad..10063df35db1 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -666,7 +666,8 @@ void mmc_reset_clk_scale_stats(struct mmc_host *host) WARN_ON(1); return; } - + if (!host->clk_scaling.enable) + return; spin_lock_irqsave(&host->clk_scaling.lock, flags); host->clk_scaling.total_busy_time_us = 0; spin_unlock_irqrestore(&host->clk_scaling.lock, flags); From f32bccfb7f2c15fbad638e905520c89e5c9aa4e5 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 21 May 2015 08:28:19 +0530 Subject: [PATCH 241/472] mmc: sdhci-msm-ice: Add Inline Crypto Engine (ICE) support eMMC controller may have an Inline Crypto Engine (ICE) attached, which can be used to encrypt/decrypt data going to/from eMMC. This patch adds a new client driver sdhci-msm-ice.c which interacts with ICE driver present in (drivers/crypto/msm/) and thus provides an interface to the low-level SDHCI driver to do the data encryption/decryption. Change-Id: I6ac78072563f77c481425a5ec149ec46a9b0a80d Signed-off-by: Krishna Konda Signed-off-by: Sahitya Tummala Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/Kconfig | 11 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/sdhci-msm-ice.c | 355 +++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-msm-ice.h | 133 ++++++++++++ drivers/mmc/host/sdhci-msm.h | 12 ++ 5 files changed, 512 insertions(+) create mode 100644 drivers/mmc/host/sdhci-msm-ice.c create mode 100644 drivers/mmc/host/sdhci-msm-ice.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 2e1df582db74..0325e914899d 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -137,6 +137,17 @@ config MMC_SDHCI_OF_AT91 help This selects the Atmel SDMMC driver +config MMC_SDHCI_MSM_ICE + bool "Qualcomm Technologies, Inc Inline Crypto Engine for SDHCI core" + depends on MMC_SDHCI_MSM && CRYPTO_DEV_QCOM_ICE + help + This selects the QTI specific additions to support Inline Crypto + Engine (ICE). ICE accelerates the crypto operations and maintains + the high SDHCI performance. + + Select this if you have ICE supported for SDHCI on QTI chipset. + If unsure, say N. + config MMC_SDHCI_OF_ESDHC tristate "SDHCI OF support for the Freescale eSDHC controller" depends on MMC_SDHCI_PLTFM diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 56d31deb82f7..ee65b49df3ff 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -73,6 +73,7 @@ obj-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o obj-$(CONFIG_MMC_SDHCI_BCM_KONA) += sdhci-bcm-kona.o obj-$(CONFIG_MMC_SDHCI_BCM2835) += sdhci-bcm2835.o obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o +obj-$(CONFIG_MMC_SDHCI_MSM_ICE) += sdhci-msm-ice.o obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o diff --git a/drivers/mmc/host/sdhci-msm-ice.c b/drivers/mmc/host/sdhci-msm-ice.c new file mode 100644 index 000000000000..2ba882ccbb93 --- /dev/null +++ b/drivers/mmc/host/sdhci-msm-ice.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "sdhci-msm-ice.h" + +static void sdhci_msm_ice_success_cb(void *host_ctrl, + enum ice_event_completion evt) +{ + struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)host_ctrl; + + if ((msm_host->ice.state == SDHCI_MSM_ICE_STATE_DISABLED && + evt == ICE_INIT_COMPLETION) || (msm_host->ice.state == + SDHCI_MSM_ICE_STATE_SUSPENDED && evt == ICE_RESUME_COMPLETION)) + msm_host->ice.state = SDHCI_MSM_ICE_STATE_ACTIVE; + + complete(&msm_host->ice.async_done); +} + +static void sdhci_msm_ice_error_cb(void *host_ctrl, enum ice_error_code evt) +{ + struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)host_ctrl; + + dev_err(&msm_host->pdev->dev, "%s: Error in ice operation %d", + __func__, evt); + + if (msm_host->ice.state == SDHCI_MSM_ICE_STATE_ACTIVE) + msm_host->ice.state = SDHCI_MSM_ICE_STATE_DISABLED; + + complete(&msm_host->ice.async_done); +} + +static struct platform_device *sdhci_msm_ice_get_pdevice(struct device *dev) +{ + struct device_node *node; + struct platform_device *ice_pdev = NULL; + + node = of_parse_phandle(dev->of_node, SDHC_MSM_CRYPTO_LABEL, 0); + if (!node) { + dev_dbg(dev, "%s: sdhc-msm-crypto property not specified\n", + __func__); + goto out; + } + ice_pdev = qcom_ice_get_pdevice(node); +out: + return ice_pdev; +} + +static +struct qcom_ice_variant_ops *sdhci_msm_ice_get_vops(struct device *dev) +{ + struct qcom_ice_variant_ops *ice_vops = NULL; + struct device_node *node; + + node = of_parse_phandle(dev->of_node, SDHC_MSM_CRYPTO_LABEL, 0); + if (!node) { + dev_dbg(dev, "%s: sdhc-msm-crypto property not specified\n", + __func__); + goto out; + } + ice_vops = qcom_ice_get_variant_ops(node); + of_node_put(node); +out: + return ice_vops; +} + +int sdhci_msm_ice_get_dev(struct sdhci_host *host) +{ + struct device *sdhc_dev; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (!msm_host || !msm_host->pdev) { + pr_err("%s: invalid msm_host %p or msm_host->pdev\n", + __func__, msm_host); + return -EINVAL; + } + + sdhc_dev = &msm_host->pdev->dev; + msm_host->ice.vops = sdhci_msm_ice_get_vops(sdhc_dev); + msm_host->ice.pdev = sdhci_msm_ice_get_pdevice(sdhc_dev); + + if (msm_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) { + dev_err(sdhc_dev, "%s: ICE device not probed yet\n", + __func__); + msm_host->ice.pdev = NULL; + msm_host->ice.vops = NULL; + return -EPROBE_DEFER; + } + + if (!msm_host->ice.pdev) { + dev_dbg(sdhc_dev, "%s: invalid platform device\n", __func__); + msm_host->ice.vops = NULL; + return -ENODEV; + } + if (!msm_host->ice.vops) { + dev_dbg(sdhc_dev, "%s: invalid ice vops\n", __func__); + msm_host->ice.pdev = NULL; + return -ENODEV; + } + msm_host->ice.state = SDHCI_MSM_ICE_STATE_DISABLED; + return 0; +} + +int sdhci_msm_ice_init(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int err = 0; + + init_completion(&msm_host->ice.async_done); + if (msm_host->ice.vops->config) { + err = msm_host->ice.vops->init(msm_host->ice.pdev, + msm_host, + sdhci_msm_ice_success_cb, + sdhci_msm_ice_error_cb); + if (err) { + pr_err("%s: ice init err %d\n", + mmc_hostname(host->mmc), err); + return err; + } + } + + if (!wait_for_completion_timeout(&msm_host->ice.async_done, + msecs_to_jiffies(SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS))) { + pr_err("%s: ice init timedout after %d ms\n", + mmc_hostname(host->mmc), + SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); + return -ETIMEDOUT; + } + + if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + return 0; +} + +int sdhci_msm_ice_cfg(struct sdhci_host *host, struct mmc_request *mrq, + u32 slot) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int err = 0; + struct ice_data_setting ice_set; + sector_t lba = 0; + unsigned int ctrl_info_val = 0; + unsigned int bypass = SDHCI_MSM_ICE_ENABLE_BYPASS; + struct request *req; + + if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + + BUG_ON(!mrq); + memset(&ice_set, 0, sizeof(struct ice_data_setting)); + req = mrq->req; + if (req) { + lba = req->__sector; + if (msm_host->ice.vops->config) { + err = msm_host->ice.vops->config(msm_host->ice.pdev, + req, &ice_set); + if (err) { + pr_err("%s: ice config failed %d\n", + mmc_hostname(host->mmc), err); + return err; + } + } + /* if writing data command */ + if (rq_data_dir(req) == WRITE) + bypass = ice_set.encr_bypass ? + SDHCI_MSM_ICE_ENABLE_BYPASS : + SDHCI_MSM_ICE_DISABLE_BYPASS; + /* if reading data command */ + else if (rq_data_dir(req) == READ) + bypass = ice_set.decr_bypass ? + SDHCI_MSM_ICE_ENABLE_BYPASS : + SDHCI_MSM_ICE_DISABLE_BYPASS; + pr_debug("%s: %s: slot %d encr_bypass %d bypass %d decr_bypass %d key_index %d\n", + mmc_hostname(host->mmc), + (rq_data_dir(req) == WRITE) ? "WRITE" : "READ", + slot, ice_set.encr_bypass, bypass, + ice_set.decr_bypass, + ice_set.crypto_data.key_index); + } + + /* Configure ICE index */ + ctrl_info_val = + (ice_set.crypto_data.key_index & + MASK_SDHCI_MSM_ICE_CTRL_INFO_KEY_INDEX) + << OFFSET_SDHCI_MSM_ICE_CTRL_INFO_KEY_INDEX; + + /* Configure data unit size of transfer request */ + ctrl_info_val |= + (SDHCI_MSM_ICE_TR_DATA_UNIT_512_B & + MASK_SDHCI_MSM_ICE_CTRL_INFO_CDU) + << OFFSET_SDHCI_MSM_ICE_CTRL_INFO_CDU; + + /* Configure ICE bypass mode */ + ctrl_info_val |= + (bypass & MASK_SDHCI_MSM_ICE_CTRL_INFO_BYPASS) + << OFFSET_SDHCI_MSM_ICE_CTRL_INFO_BYPASS; + + writel_relaxed((lba & 0xFFFFFFFF), + host->ioaddr + CORE_VENDOR_SPEC_ICE_CTRL_INFO_1_n + 16 * slot); + writel_relaxed(((lba >> 32) & 0xFFFFFFFF), + host->ioaddr + CORE_VENDOR_SPEC_ICE_CTRL_INFO_2_n + 16 * slot); + writel_relaxed(ctrl_info_val, + host->ioaddr + CORE_VENDOR_SPEC_ICE_CTRL_INFO_3_n + 16 * slot); + + /* Ensure ICE registers are configured before issuing SDHCI request */ + mb(); + return 0; +} + +int sdhci_msm_ice_reset(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int err = 0; + + if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state before reset %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + + init_completion(&msm_host->ice.async_done); + + if (msm_host->ice.vops->reset) { + err = msm_host->ice.vops->reset(msm_host->ice.pdev); + if (err) { + pr_err("%s: ice reset failed %d\n", + mmc_hostname(host->mmc), err); + return err; + } + } + + if (!wait_for_completion_timeout(&msm_host->ice.async_done, + msecs_to_jiffies(SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS))) { + pr_err("%s: ice reset timedout after %d ms\n", + mmc_hostname(host->mmc), + SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); + return -ETIMEDOUT; + } + + if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state after reset %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + return 0; +} + +int sdhci_msm_ice_resume(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int err = 0; + + if (msm_host->ice.state != + SDHCI_MSM_ICE_STATE_SUSPENDED) { + pr_err("%s: ice is in invalid state before resume %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + + init_completion(&msm_host->ice.async_done); + + if (msm_host->ice.vops->resume) { + err = msm_host->ice.vops->resume(msm_host->ice.pdev); + if (err) { + pr_err("%s: ice resume failed %d\n", + mmc_hostname(host->mmc), err); + return err; + } + } + + if (!wait_for_completion_timeout(&msm_host->ice.async_done, + msecs_to_jiffies(SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS))) { + pr_err("%s: ice resume timedout after %d ms\n", + mmc_hostname(host->mmc), + SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); + return -ETIMEDOUT; + } + + if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state after resume %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + return 0; +} + +int sdhci_msm_ice_suspend(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int err = 0; + + if (msm_host->ice.state != + SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state before resume %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + + if (msm_host->ice.vops->suspend) { + err = msm_host->ice.vops->suspend(msm_host->ice.pdev); + if (err) { + pr_err("%s: ice suspend failed %d\n", + mmc_hostname(host->mmc), err); + return -EINVAL; + } + } + msm_host->ice.state = SDHCI_MSM_ICE_STATE_SUSPENDED; + return 0; +} + +int sdhci_msm_ice_get_status(struct sdhci_host *host, int *ice_status) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int stat = -EINVAL; + + if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { + pr_err("%s: ice is in invalid state %d\n", + mmc_hostname(host->mmc), msm_host->ice.state); + return -EINVAL; + } + + if (msm_host->ice.vops->status) { + *ice_status = 0; + stat = msm_host->ice.vops->status(msm_host->ice.pdev); + if (stat < 0) { + pr_err("%s: ice get sts failed %d\n", + mmc_hostname(host->mmc), stat); + return -EINVAL; + } + *ice_status = stat; + } + return 0; +} diff --git a/drivers/mmc/host/sdhci-msm-ice.h b/drivers/mmc/host/sdhci-msm-ice.h new file mode 100644 index 000000000000..cceb2b4195b3 --- /dev/null +++ b/drivers/mmc/host/sdhci-msm-ice.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __SDHCI_MSM_ICE_H__ +#define __SDHCI_MSM_ICE_H__ + +#include +#include +#include +#include +#include + +#include "sdhci-msm.h" + +#define SDHC_MSM_CRYPTO_LABEL "sdhc-msm-crypto" +/* Timeout waiting for ICE initialization, that requires TZ access */ +#define SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS 500 + +/* + * SDHCI host controller ICE registers. There are n [0..31] + * of each of these registers + */ +#define NUM_SDHCI_MSM_ICE_CTRL_INFO_n_REGS 32 + +#define CORE_VENDOR_SPEC_ICE_CTRL 0x300 +#define CORE_VENDOR_SPEC_ICE_CTRL_INFO_1_n 0x304 +#define CORE_VENDOR_SPEC_ICE_CTRL_INFO_2_n 0x308 +#define CORE_VENDOR_SPEC_ICE_CTRL_INFO_3_n 0x30C + +/* SDHCI MSM ICE CTRL Info register offset */ +enum { + OFFSET_SDHCI_MSM_ICE_CTRL_INFO_BYPASS = 0, + OFFSET_SDHCI_MSM_ICE_CTRL_INFO_KEY_INDEX = 0x1, + OFFSET_SDHCI_MSM_ICE_CTRL_INFO_CDU = 0x6, +}; + +/* SDHCI MSM ICE CTRL Info register masks */ +enum { + MASK_SDHCI_MSM_ICE_CTRL_INFO_BYPASS = 0x1, + MASK_SDHCI_MSM_ICE_CTRL_INFO_KEY_INDEX = 0x1F, + MASK_SDHCI_MSM_ICE_CTRL_INFO_CDU = 0x7, +}; + +/* SDHCI MSM ICE encryption/decryption bypass state */ +enum { + SDHCI_MSM_ICE_DISABLE_BYPASS = 0, + SDHCI_MSM_ICE_ENABLE_BYPASS = 1, +}; + +/* SDHCI MSM ICE Crypto Data Unit of target DUN of Transfer Request */ +enum { + SDHCI_MSM_ICE_TR_DATA_UNIT_512_B = 0, + SDHCI_MSM_ICE_TR_DATA_UNIT_1_KB = 1, + SDHCI_MSM_ICE_TR_DATA_UNIT_2_KB = 2, + SDHCI_MSM_ICE_TR_DATA_UNIT_4_KB = 3, + SDHCI_MSM_ICE_TR_DATA_UNIT_8_KB = 4, + SDHCI_MSM_ICE_TR_DATA_UNIT_16_KB = 5, + SDHCI_MSM_ICE_TR_DATA_UNIT_32_KB = 6, + SDHCI_MSM_ICE_TR_DATA_UNIT_64_KB = 7, +}; + +/* SDHCI MSM ICE internal state */ +enum { + SDHCI_MSM_ICE_STATE_DISABLED = 0, + SDHCI_MSM_ICE_STATE_ACTIVE = 1, + SDHCI_MSM_ICE_STATE_SUSPENDED = 2, +}; + +#ifdef CONFIG_MMC_SDHCI_MSM_ICE +int sdhci_msm_ice_get_dev(struct sdhci_host *host); +int sdhci_msm_ice_init(struct sdhci_host *host); +int sdhci_msm_ice_cfg(struct sdhci_host *host, struct mmc_request *mrq, + u32 slot); +int sdhci_msm_ice_reset(struct sdhci_host *host); +int sdhci_msm_ice_resume(struct sdhci_host *host); +int sdhci_msm_ice_suspend(struct sdhci_host *host); +int sdhci_msm_ice_get_status(struct sdhci_host *host, int *ice_status); +void sdhci_msm_ice_print_regs(struct sdhci_host *host); +#else +inline int sdhci_msm_ice_get_dev(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (msm_host) { + msm_host->ice.pdev = NULL; + msm_host->ice.vops = NULL; + } + return -ENODEV; +} +inline int sdhci_msm_ice_init(struct sdhci_host *host) +{ + return 0; +} +inline int sdhci_msm_ice_cfg(struct sdhci_host *host, + struct mmc_request *mrq, u32 slot) +{ + return 0; +} +inline int sdhci_msm_ice_reset(struct sdhci_host *host) +{ + return 0; +} +inline int sdhci_msm_ice_resume(struct sdhci_host *host) +{ + return 0; +} +inline int sdhci_msm_ice_suspend(struct sdhci_host *host) +{ + return 0; +} +inline int sdhci_msm_ice_get_status(struct sdhci_host *host, + int *ice_status) +{ + return 0; +} +inline void sdhci_msm_ice_print_regs(struct sdhci_host *host) +{ + return; +} +#endif /* CONFIG_MMC_SDHCI_MSM_ICE */ +#endif /* __SDHCI_MSM_ICE_H__ */ diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index d2333c6f5d9b..708ab8ba3c3f 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -104,6 +104,8 @@ struct sdhci_msm_pltfm_data { u32 *sup_clk_table; unsigned char sup_clk_cnt; enum pm_qos_req_type cpu_affinity_type; + u32 *sup_ice_clk_table; + unsigned char sup_ice_clk_cnt; }; struct sdhci_msm_bus_vote { @@ -116,6 +118,13 @@ struct sdhci_msm_bus_vote { struct device_attribute max_bus_bw; }; +struct sdhci_msm_ice_data { + struct qcom_ice_variant_ops *vops; + struct completion async_done; + struct platform_device *pdev; + int state; +}; + struct sdhci_msm_host { struct platform_device *pdev; void __iomem *core_mem; /* MSM SDCC mapped address */ @@ -125,6 +134,7 @@ struct sdhci_msm_host { struct clk *bus_clk; /* SDHC bus voter clock */ struct clk *ff_clk; /* CDC calibration fixed feedback clock */ struct clk *sleep_clk; /* CDC calibration sleep clock */ + struct clk *ice_clk; /* SDHC peripheral ICE clock */ atomic_t clks_on; /* Set if clocks are enabled */ struct sdhci_msm_pltfm_data *pdata; struct mmc_host *mmc; @@ -145,5 +155,7 @@ struct sdhci_msm_host { bool use_updated_dll_reset; bool use_14lpp_dll; u32 caps_0; + struct sdhci_msm_ice_data ice; + u32 ice_clk_rate; }; #endif /* __SDHCI_MSM_H__ */ From 755824a89464af83c2ba740907cf9028827cbd0d Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 8 May 2015 11:53:29 +0530 Subject: [PATCH 242/472] mmc: sdhci-msm: Add Inline Crypto Engine (ICE) support Add ICE support to low-level driver sdhci-msm.c. This code is primarily responsible for enabling ICE (if present), managing ICE clocks, managing ICE suspend/resume and also provides a few host->ops for sdhci driver to use ICE functionality. Change-Id: I3ec62146982c9db0263d5e19f60274163f514859 Signed-off-by: Krishna Konda Signed-off-by: Sahitya Tummala Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- .../devicetree/bindings/mmc/sdhci-msm.txt | 5 + drivers/mmc/host/sdhci-msm.c | 141 +++++++++++++++++- drivers/mmc/host/sdhci.h | 5 + 3 files changed, 149 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index a95ff2d27c94..fe0a29bacc7c 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -12,6 +12,9 @@ Required properties: Required "interrupt-names" are "hc_irq" and "pwr_irq". - -supply: phandle to the regulator device tree node Required "supply-name" are "vdd" and "vdd-io". + - qcom,ice-clk-rates: this is an array that specifies supported Inline + Crypto Engine (ICE) clock frequencies, Units - Hz. + - sdhc-msm-crypto: phandle to SDHC ICE node Required alias: - The slot number is specified via an alias with the following format @@ -99,6 +102,7 @@ Example: reg-names = "hc_mem", "core_mem"; interrupts = <0 123 0>, <0 138 0>; interrupt-names = "hc_irq", "pwr_irq"; + sdhc-msm-crypto = <&sdcc1_ice>; vdd-supply = <&pm8941_l21>; vdd-io-supply = <&pm8941_l13>; @@ -121,6 +125,7 @@ Example: qcom,nonremovable; qcom,large-address-bus; qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v"; + qcom,ice-clk-rates = <300000000>; gpios = <&msmgpio 40 0>, /* CLK */ <&msmgpio 39 0>, /* CMD */ diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c60b2a9ddcd6..03040ad52b2b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -40,6 +40,7 @@ #include #include "sdhci-msm.h" +#include "sdhci-msm-ice.h" #define CORE_POWER 0x0 #define CORE_SW_RST (1 << 7) @@ -1378,6 +1379,8 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, int len, i; int clk_table_len; u32 *clk_table = NULL; + int ice_clk_table_len; + u32 *ice_clk_table = NULL; enum of_gpio_flags flags = OF_GPIO_ACTIVE_LOW; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); @@ -1427,6 +1430,20 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, pdata->sup_clk_table = clk_table; pdata->sup_clk_cnt = clk_table_len; + if (msm_host->ice.pdev) { + if (sdhci_msm_dt_get_array(dev, "qcom,ice-clk-rates", + &ice_clk_table, &ice_clk_table_len, 0)) { + dev_err(dev, "failed parsing supported ice clock rates\n"); + goto out; + } + if (!ice_clk_table || !ice_clk_table_len) { + dev_err(dev, "Invalid clock table\n"); + goto out; + } + pdata->sup_ice_clk_table = ice_clk_table; + pdata->sup_ice_clk_cnt = ice_clk_table_len; + } + pdata->vreg_data = devm_kzalloc(dev, sizeof(struct sdhci_msm_slot_reg_data), GFP_KERNEL); @@ -2315,11 +2332,22 @@ static int sdhci_msm_enable_controller_clock(struct sdhci_host *host) goto disable_pclk; } + if (!IS_ERR(msm_host->ice_clk)) { + rc = clk_prepare_enable(msm_host->ice_clk); + if (rc) { + pr_err("%s: %s: failed to enable the ice-clk with error %d\n", + mmc_hostname(host->mmc), __func__, rc); + goto disable_host_clk; + } + } atomic_set(&msm_host->controller_clock, 1); pr_debug("%s: %s: enabled controller clock\n", mmc_hostname(host->mmc), __func__); goto out; +disable_host_clk: + if (!IS_ERR(msm_host->clk)) + clk_disable_unprepare(msm_host->clk); disable_pclk: if (!IS_ERR(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); @@ -2398,6 +2426,8 @@ static int sdhci_msm_prepare_clocks(struct sdhci_host *host, bool enable) if (!IS_ERR_OR_NULL(msm_host->ff_clk)) clk_disable_unprepare(msm_host->ff_clk); clk_disable_unprepare(msm_host->clk); + if (!IS_ERR(msm_host->ice_clk)) + clk_disable_unprepare(msm_host->ice_clk); if (!IS_ERR(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); if (!IS_ERR_OR_NULL(msm_host->bus_clk)) @@ -2417,6 +2447,8 @@ disable_bus_clk: disable_controller_clk: if (!IS_ERR_OR_NULL(msm_host->clk)) clk_disable_unprepare(msm_host->clk); + if (!IS_ERR(msm_host->ice_clk)) + clk_disable_unprepare(msm_host->ice_clk); if (!IS_ERR_OR_NULL(msm_host->pclk)) clk_disable_unprepare(msm_host->pclk); atomic_set(&msm_host->controller_clock, 0); @@ -2658,6 +2690,7 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) int i, index = 0; u32 test_bus_val = 0; u32 debug_reg[MAX_TEST_BUS] = {0}; + u32 sts = 0; pr_info("----------- VENDOR REGISTER DUMP -----------\n"); pr_info("Data cnt: 0x%08x | Fifo cnt: 0x%08x | Int sts: 0x%08x\n", @@ -2701,9 +2734,27 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) /* Disable test bus */ writel_relaxed(~CORE_TESTBUS_ENA, msm_host->core_mem + CORE_TESTBUS_CONFIG); + if (host->is_crypto_en) { + sdhci_msm_ice_get_status(host, &sts); + pr_info("%s: ICE status %x\n", mmc_hostname(host->mmc), sts); + } +} + +void sdhci_msm_reset(struct sdhci_host *host, u8 mask) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + /* Set ICE core to be reset in sync with SDHC core */ + if (msm_host->ice.pdev) + writel_relaxed(1, host->ioaddr + CORE_VENDOR_SPEC_ICE_CTRL); + + sdhci_reset(host, mask); } static struct sdhci_ops sdhci_msm_ops = { + .crypto_engine_cfg = sdhci_msm_ice_cfg, + .crypto_engine_reset = sdhci_msm_ice_reset, .set_uhs_signaling = sdhci_msm_set_uhs_signaling, .check_power_status = sdhci_msm_check_power_status, .platform_execute_tuning = sdhci_msm_execute_tuning, @@ -2716,7 +2767,7 @@ static struct sdhci_ops sdhci_msm_ops = { .config_auto_tuning_cmd = sdhci_msm_config_auto_tuning_cmd, .enable_controller_clock = sdhci_msm_enable_controller_clock, .set_bus_width = sdhci_set_bus_width, - .reset = sdhci_reset, + .reset = sdhci_msm_reset, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, @@ -2812,7 +2863,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host = sdhci_pltfm_init(pdev, &msm_host->sdhci_msm_pdata, 0); if (IS_ERR(host)) { ret = PTR_ERR(host); - goto out; + goto out_host_free; } pltfm_host = sdhci_priv(host); @@ -2820,6 +2871,31 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc = host->mmc; msm_host->pdev = pdev; + /* get the ice device vops if present */ + ret = sdhci_msm_ice_get_dev(host); + if (ret == -EPROBE_DEFER) { + /* + * SDHCI driver might be probed before ICE driver does. + * In that case we would like to return EPROBE_DEFER code + * in order to delay its probing. + */ + dev_err(&pdev->dev, "%s: required ICE device not probed yet err = %d\n", + __func__, ret); + goto out_host_free; + + } else if (ret == -ENODEV) { + /* + * ICE device is not enabled in DTS file. No need for further + * initialization of ICE driver. + */ + dev_warn(&pdev->dev, "%s: ICE device is not enabled", + __func__); + } else if (ret) { + dev_err(&pdev->dev, "%s: sdhci_msm_ice_get_dev failed %d\n", + __func__, ret); + goto out_host_free; + } + /* Extract platform data */ if (pdev->dev.of_node) { ret = of_alias_get_id(pdev->dev.of_node, "sdhc"); @@ -2869,6 +2945,28 @@ static int sdhci_msm_probe(struct platform_device *pdev) } atomic_set(&msm_host->controller_clock, 1); + if (msm_host->ice.pdev) { + /* Setup SDC ICE clock */ + msm_host->ice_clk = devm_clk_get(&pdev->dev, "ice_core_clk"); + if (!IS_ERR(msm_host->ice_clk)) { + /* ICE core has only one clock frequency for now */ + ret = clk_set_rate(msm_host->ice_clk, + msm_host->pdata->sup_ice_clk_table[0]); + if (ret) { + dev_err(&pdev->dev, "ICE_CLK rate set failed (%d) for %u\n", + ret, + msm_host->pdata->sup_ice_clk_table[0]); + goto pclk_disable; + } + ret = clk_prepare_enable(msm_host->ice_clk); + if (ret) + goto pclk_disable; + + msm_host->ice_clk_rate = + msm_host->pdata->sup_clk_table[0]; + } + } + /* Setup SDC MMC clock */ msm_host->clk = devm_clk_get(&pdev->dev, "core_clk"); if (IS_ERR(msm_host->clk)) { @@ -3075,6 +3173,21 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; host->pm_qos_req_dma.type = msm_host->pdata->cpu_affinity_type; + /* Initialize ICE if present */ + if (msm_host->ice.pdev) { + ret = sdhci_msm_ice_init(host); + if (ret) { + dev_err(&pdev->dev, "%s: SDHCi ICE init failed (%d)\n", + mmc_hostname(host->mmc), ret); + ret = -EINVAL; + goto vreg_deinit; + } + host->is_crypto_en = true; + /* Packed commands cannot be encrypted/decrypted using ICE */ + msm_host->mmc->caps2 &= ~(MMC_CAP2_PACKED_WR | + MMC_CAP2_PACKED_WR_CONTROL); + } + init_completion(&msm_host->pwr_irq_completion); if (gpio_is_valid(msm_host->pdata->status_gpio)) { @@ -3183,6 +3296,8 @@ bus_clk_disable: clk_disable_unprepare(msm_host->bus_clk); pltfm_free: sdhci_pltfm_free(pdev); +out_host_free: + devm_kfree(&pdev->dev, msm_host); out: pr_debug("%s: Exit %s\n", dev_name(&pdev->dev), __func__); return ret; @@ -3227,6 +3342,7 @@ static int sdhci_msm_runtime_suspend(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; ktime_t start = ktime_get(); + int ret; disable_irq(host->irq); disable_irq(msm_host->pwr_irq); @@ -3243,6 +3359,12 @@ static int sdhci_msm_runtime_suspend(struct device *dev) trace_sdhci_msm_runtime_suspend(mmc_hostname(host->mmc), 0, ktime_to_us(ktime_sub(ktime_get(), start))); + if (host->is_crypto_en) { + ret = sdhci_msm_ice_suspend(host); + if (ret < 0) + pr_err("%s: failed to suspend crypto engine %d\n", + mmc_hostname(host->mmc), ret); + } return 0; } @@ -3252,6 +3374,21 @@ static int sdhci_msm_runtime_resume(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; ktime_t start = ktime_get(); + int ret; + + if (host->is_crypto_en) { + ret = sdhci_msm_enable_controller_clock(host); + if (ret) { + pr_err("%s: Failed to enable reqd clocks\n", + mmc_hostname(host->mmc)); + goto skip_ice_resume; + } + ret = sdhci_msm_ice_resume(host); + if (ret) + pr_err("%s: failed to resume crypto engine %d\n", + mmc_hostname(host->mmc), ret); + } +skip_ice_resume: enable_irq(msm_host->pwr_irq); enable_irq(host->irq); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 01b26839a660..f309315f92d3 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -605,6 +605,8 @@ struct sdhci_host { enum sdhci_power_policy power_policy; + bool is_crypto_en; + u32 auto_cmd_err_sts; struct ratelimit_state dbg_dump_rs; unsigned long private[0] ____cacheline_aligned; @@ -635,6 +637,9 @@ struct sdhci_ops { unsigned int (*get_ro)(struct sdhci_host *host); void (*reset)(struct sdhci_host *host, u8 mask); int (*platform_execute_tuning)(struct sdhci_host *host, u32 opcode); + int (*crypto_engine_cfg)(struct sdhci_host *host, + struct mmc_request *mrq, u32 slot); + int (*crypto_engine_reset)(struct sdhci_host *host); void (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); void (*hw_reset)(struct sdhci_host *host); void (*adma_workaround)(struct sdhci_host *host, u32 intmask); From 534f1ea89d21d8831a60c822a153967d3189dd5b Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 8 May 2015 11:14:23 +0530 Subject: [PATCH 243/472] mmc: sdhci: Add Inline Crypto Engine (ICE) support This patch adds ICE support to sdhci driver. It uses the new ICE host->ops like config/reset to configure/reset the ICE HW as appropriate. Change-Id: I64946d15d2f6ec8981e95c8817e82a2115b1196c Signed-off-by: Sahitya Tummala Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 43 ++++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 44 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d2262672f604..7cb82926a06b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -291,6 +291,8 @@ static void sdhci_do_reset(struct sdhci_host *host, u8 mask) /* Resetting the controller clears many */ host->preset_enabled = false; } + if (host->is_crypto_en) + host->crypto_reset_reqd = true; } static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios); @@ -1573,6 +1575,33 @@ static int sdhci_get_tuning_cmd(struct sdhci_host *host) return MMC_SEND_TUNING_BLOCK; } +static int sdhci_crypto_cfg(struct sdhci_host *host, struct mmc_request *mrq, + u32 slot) +{ + int err = 0; + + if (host->crypto_reset_reqd && host->ops->crypto_engine_reset) { + err = host->ops->crypto_engine_reset(host); + if (err) { + pr_err("%s: crypto reset failed\n", + mmc_hostname(host->mmc)); + goto out; + } + host->crypto_reset_reqd = false; + } + + if (host->ops->crypto_engine_cfg) { + err = host->ops->crypto_engine_cfg(host, mrq, slot); + if (err) { + pr_err("%s: failed to configure crypto\n", + mmc_hostname(host->mmc)); + goto out; + } + } +out: + return err; +} + static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct sdhci_host *host; @@ -1644,6 +1673,13 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) sdhci_get_tuning_cmd(host)); } + if (host->is_crypto_en) { + spin_unlock_irqrestore(&host->lock, flags); + if (sdhci_crypto_cfg(host, mrq, 0)) + goto end_req; + spin_lock_irqsave(&host->lock, flags); + } + if (mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23)) sdhci_send_command(host, mrq->sbc); else @@ -1652,6 +1688,13 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) mmiowb(); spin_unlock_irqrestore(&host->lock, flags); + return; +end_req: + mrq->cmd->error = -EIO; + if (mrq->data) + mrq->data->error = -EIO; + mmc_request_done(host->mmc, mrq); + sdhci_runtime_pm_put(host); } void sdhci_set_bus_width(struct sdhci_host *host, int width) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index f309315f92d3..8c033f7b2cf9 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -606,6 +606,7 @@ struct sdhci_host { enum sdhci_power_policy power_policy; bool is_crypto_en; + bool crypto_reset_reqd; u32 auto_cmd_err_sts; struct ratelimit_state dbg_dump_rs; From 6d1be7e42b9e5f2dbdf6e8c1909c20cdfa6edc02 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 21 May 2015 13:29:51 +0530 Subject: [PATCH 244/472] mmc: queue: initialization of command queue Command Queueing (CQ) feature is introduced to eMMC standard in revision 5.1. CQ includes new commands for issuing tasks to the device, for ordering the execution of previously issued tasks and for additional task management functions. The idea is to keep the legacy and CQ code as discrete as possible. Hence, a separate queue is created for CQ. The issuing path is non-blocking since several requests (max. 32) can be queued at a time. Change-Id: I5b48d1b3ed17585b907ec70ff7c8d583003ec9e1 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts & compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 2 +- drivers/mmc/card/queue.c | 191 ++++++++++++++++++++++++++++++++++++++- drivers/mmc/card/queue.h | 11 ++- include/linux/mmc/host.h | 19 ++++ 4 files changed, 219 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index c0acbfa6211c..81a572cb4e16 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2800,7 +2800,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, INIT_LIST_HEAD(&md->part); md->usage = 1; - ret = mmc_init_queue(&md->queue, card, &md->lock, subname); + ret = mmc_init_queue(&md->queue, card, &md->lock, subname, area_type); if (ret) goto err_putdisk; diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 943c979e7907..02fc50e65c55 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -55,6 +55,72 @@ static int mmc_prep_request(struct request_queue *q, struct request *req) return BLKPREP_OK; } +static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, + struct mmc_cmdq_context_info *ctx) +{ + if (test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { + pr_debug("%s: %s: skip pulling reqs: state: %lu\n", + mmc_hostname(host), __func__, ctx->curr_state); + return false; + } else { + return true; + } +} + +static int mmc_cmdq_thread(void *d) +{ + struct mmc_queue *mq = d; + struct request_queue *q = mq->queue; + struct mmc_card *card = mq->card; + + struct request *req; + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; + unsigned long flags; + + current->flags |= PF_MEMALLOC; + if (card->host->wakeup_on_idle) + set_wake_up_idle(true); + + while (1) { + int ret = 0; + + if (!mmc_cmdq_should_pull_reqs(host, ctx)) { + test_and_set_bit(0, &ctx->req_starved); + schedule(); + } + + spin_lock_irqsave(q->queue_lock, flags); + req = blk_peek_request(q); + if (req) { + ret = blk_queue_start_tag(q, req); + spin_unlock_irqrestore(q->queue_lock, flags); + if (ret) { + test_and_set_bit(0, &ctx->req_starved); + schedule(); + } else { + ret = mq->cmdq_issue_fn(mq, req); + if (ret) { + pr_err("%s: failed (%d) to issue req, requeue\n", + mmc_hostname(host), ret); + spin_lock_irqsave(q->queue_lock, flags); + blk_requeue_request(q, req); + spin_unlock_irqrestore(q->queue_lock, + flags); + } + } + } else { + spin_unlock_irqrestore(q->queue_lock, flags); + if (kthread_should_stop()) { + set_current_state(TASK_RUNNING); + break; + } + schedule(); + } + } /* loop */ + return 0; +} + static int mmc_queue_thread(void *d) { struct mmc_queue *mq = d; @@ -119,6 +185,13 @@ static int mmc_queue_thread(void *d) return 0; } +static void mmc_cmdq_dispatch_req(struct request_queue *q) +{ + struct mmc_queue *mq = q->queuedata; + + wake_up_process(mq->thread); +} + /* * Generic MMC request handler. This is called for any queue on a * particular host. When the host is not busy, we look for a request @@ -193,6 +266,29 @@ static void mmc_queue_setup_discard(struct request_queue *q, queue_flag_set_unlocked(QUEUE_FLAG_SECDISCARD, q); } +/** + * mmc_blk_cmdq_setup_queue + * @mq: mmc queue + * @card: card to attach to this queue + * + * Setup queue for CMDQ supporting MMC card + */ +void mmc_cmdq_setup_queue(struct mmc_queue *mq, struct mmc_card *card) +{ + u64 limit = BLK_BOUNCE_HIGH; + struct mmc_host *host = card->host; + + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue); + if (mmc_can_erase(card)) + mmc_queue_setup_discard(mq->queue, card); + + blk_queue_bounce_limit(mq->queue, limit); + blk_queue_max_hw_sectors(mq->queue, min(host->max_blk_count, + host->max_req_size / 512)); + blk_queue_max_segment_size(mq->queue, host->max_seg_size); + blk_queue_max_segments(mq->queue, host->max_segs); +} + /** * mmc_init_queue - initialise a queue structure. * @mq: mmc queue @@ -203,7 +299,7 @@ static void mmc_queue_setup_discard(struct request_queue *q, * Initialise a MMC card request queue. */ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, - spinlock_t *lock, const char *subname) + spinlock_t *lock, const char *subname, int area_type) { struct mmc_host *host = card->host; u64 limit = BLK_BOUNCE_HIGH; @@ -215,6 +311,28 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, limit = (u64)dma_max_pfn(mmc_dev(host)) << PAGE_SHIFT; mq->card = card; + if (card->ext_csd.cmdq_support && + (area_type == MMC_BLK_DATA_AREA_MAIN)) { + mq->queue = blk_init_queue(mmc_cmdq_dispatch_req, lock); + if (!mq->queue) + return -ENOMEM; + mmc_cmdq_setup_queue(mq, card); + ret = mmc_cmdq_init(mq, card); + if (ret) { + pr_err("%s: %d: cmdq: unable to set-up\n", + mmc_hostname(card->host), ret); + blk_cleanup_queue(mq->queue); + } else { + mq->queue->queuedata = mq; + mq->thread = kthread_run(mmc_cmdq_thread, mq, + "mmc-cmdqd/%d%s", + host->index, + subname ? subname : ""); + + return ret; + } + } + mq->queue = blk_init_queue(mmc_request_fn, lock); if (!mq->queue) return -ENOMEM; @@ -443,6 +561,77 @@ void mmc_packed_clean(struct mmc_queue *mq) mqrq_prev->packed = NULL; } +static void mmc_cmdq_softirq_done(struct request *rq) +{ + struct mmc_queue *mq = rq->q->queuedata; + mq->cmdq_complete_fn(rq); +} + +int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) +{ + int i, ret = 0; + /* one slot is reserved for dcmd requests */ + int q_depth = card->ext_csd.cmdq_depth - 1; + + if (!(card->host->caps2 & MMC_CAP2_CMD_QUEUE)) { + ret = -ENOTSUPP; + goto out; + } + + mq->mqrq_cmdq = kzalloc( + sizeof(struct mmc_queue_req) * q_depth, GFP_KERNEL); + if (!mq->mqrq_cmdq) { + pr_warn("%s: unable to allocate mqrq's for q_depth %d\n", + mmc_card_name(card), q_depth); + ret = -ENOMEM; + goto out; + } + + /* sg is allocated for data request slots only */ + for (i = 0; i < q_depth; i++) { + mq->mqrq_cmdq[i].sg = mmc_alloc_sg(card->host->max_segs, &ret); + if (ret) { + pr_warn("%s: unable to allocate cmdq sg of size %d\n", + mmc_card_name(card), + card->host->max_segs); + goto free_mqrq_sg; + } + } + + ret = blk_queue_init_tags(mq->queue, q_depth, NULL, BLK_TAG_ALLOC_FIFO); + if (ret) { + pr_warn("%s: unable to allocate cmdq tags %d\n", + mmc_card_name(card), q_depth); + goto free_mqrq_sg; + } + + blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); + goto out; + +free_mqrq_sg: + for (i = 0; i < q_depth; i++) + kfree(mq->mqrq_cmdq[i].sg); + kfree(mq->mqrq_cmdq); + mq->mqrq_cmdq = NULL; +out: + return ret; +} + +void mmc_cmdq_clean(struct mmc_queue *mq, struct mmc_card *card) +{ + int i; + int q_depth = card->ext_csd.cmdq_depth - 1; + + blk_free_tags(mq->queue->queue_tags); + mq->queue->queue_tags = NULL; + blk_queue_free_tags(mq->queue); + + for (i = 0; i < q_depth; i++) + kfree(mq->mqrq_cmdq[i].sg); + kfree(mq->mqrq_cmdq); + mq->mqrq_cmdq = NULL; +} + /** * mmc_queue_suspend - suspend a MMC request queue * @mq: MMC queue to suspend diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index bcb6827c0960..3e7d174f3d9f 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -52,12 +52,16 @@ struct mmc_queue { #define MMC_QUEUE_SUSPENDED 0 #define MMC_QUEUE_NEW_REQUEST 1 - int (*issue_fn)(struct mmc_queue *, struct request *); + int (*issue_fn)(struct mmc_queue *, struct request *); + int (*cmdq_issue_fn)(struct mmc_queue *, + struct request *); + void (*cmdq_complete_fn)(struct request *); void *data; struct request_queue *queue; struct mmc_queue_req mqrq[2]; struct mmc_queue_req *mqrq_cur; struct mmc_queue_req *mqrq_prev; + struct mmc_queue_req *mqrq_cmdq; bool wr_packing_enabled; int num_of_potential_packed_wr_reqs; int num_wr_reqs_to_start_packing; @@ -67,7 +71,7 @@ struct mmc_queue { }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, - const char *); + const char *, int); extern void mmc_cleanup_queue(struct mmc_queue *); extern int mmc_queue_suspend(struct mmc_queue *, int); extern void mmc_queue_resume(struct mmc_queue *); @@ -84,4 +88,7 @@ extern int mmc_access_rpmb(struct mmc_queue *); extern void print_mmc_packing_stats(struct mmc_card *card); +extern int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card); +extern void mmc_cmdq_clean(struct mmc_queue *mq, struct mmc_card *card); + #endif diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 5afbac264ddd..2324a76123cf 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -195,6 +195,23 @@ struct mmc_slot { void *handler_priv; }; + +/** + * mmc_cmdq_context_info - describes the contexts of cmdq + * @active_reqs requests being processed + * @curr_state state of cmdq engine + * @req_starved completion should invoke the request_fn since + * no tags were available + * @cmdq_ctx_lock acquire this before accessing this structure + */ +struct mmc_cmdq_context_info { + unsigned long active_reqs; /* in-flight requests */ + unsigned long curr_state; +#define CMDQ_STATE_ERR 0 + /* no free tag available */ + unsigned long req_starved; +}; + /** * mmc_context_info - synchronization details for mmc context * @is_done_rcv wake up reason was done request @@ -370,6 +387,7 @@ struct mmc_host { /* Some hosts need additional tuning */ #define MMC_CAP2_HS400_POST_TUNING (1 << 22) #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ +#define MMC_CAP2_CMD_QUEUE (1 << 26) /* support eMMC command queue */ mmc_pm_flag_t pm_caps; /* supported pm features */ @@ -492,6 +510,7 @@ struct mmc_host { #endif enum dev_state dev_status; bool wakeup_on_idle; + struct mmc_cmdq_context_info cmdq_ctx; unsigned long private[0] ____cacheline_aligned; }; From ee66ddaa077507729c911f8a9cad2b3376fe8284 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 21 May 2015 13:43:24 +0530 Subject: [PATCH 245/472] mmc: core: Add command queue initialzation support Command Queueing (CQ) feature is introduced to eMMC standard in revision 5.1. CQ includes new commands for issuing tasks to the device, for ordering the execution of previously issued tasks and for additional task management functions. This patch adds initialization and enabling of command queue in the card and controller. If the card and the controller support CQ, then it is enabled. Change-Id: I92d893d1503396d4b00848813cc546fc263e77fd Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 50 ++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/card.h | 6 +++++ include/linux/mmc/host.h | 6 +++++ include/linux/mmc/mmc.h | 1 + 4 files changed, 63 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 16f43e4b1d99..9ab7b7c74796 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1434,6 +1434,43 @@ static int mmc_hs200_tuning(struct mmc_card *card) return mmc_execute_tuning(card); } +static int mmc_select_cmdq(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + int ret = 0; + + if (!host->cmdq_ops) { + pr_err("%s: host controller doesn't support CMDQ\n", + mmc_hostname(host)); + return 0; + } + + ret = mmc_set_blocklen(card, MMC_CARD_CMDQ_BLK_SIZE); + if (ret) + goto out; + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CMDQ, 1, + card->ext_csd.generic_cmd6_time); + if (ret) + goto out; + + mmc_card_set_cmdq(card); + ret = host->cmdq_ops->enable(card->host); + if (ret) { + pr_err("%s: failed (%d) enabling CMDQ on host\n", + mmc_hostname(host), ret); + mmc_card_clr_cmdq(card); + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CMDQ, 0, + card->ext_csd.generic_cmd6_time); + if (ret) + goto out; + } + + pr_info("%s: CMDQ enabled on card\n", mmc_hostname(host)); +out: + return ret; +} + /* * Scale down from HS400 to HS in order to allow frequency change. * This is needed for cards that doesn't support changing frequency in HS400 @@ -1619,6 +1656,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * respond. * mmc_go_idle is needed for eMMC that are asleep */ +reinit: mmc_go_idle(host); /* The extra bit indicates that we support high capacity */ @@ -2004,6 +2042,18 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + if (card->ext_csd.cmdq_support && (card->host->caps2 & + MMC_CAP2_CMD_QUEUE)) { + err = mmc_select_cmdq(card); + if (err) { + pr_err("%s: selecting CMDQ mode: failed: %d\n", + mmc_hostname(card->host), err); + card->ext_csd.cmdq_support = 0; + oldcard = card; + goto reinit; + } + } + return 0; free_card: diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index ae943ccb331e..9885e75d196b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -15,6 +15,8 @@ #include #include +#define MMC_CARD_CMDQ_BLK_SIZE 512 + struct mmc_cid { unsigned int manfid; char prod_name[8]; @@ -293,6 +295,7 @@ struct mmc_card { #define MMC_CARD_REMOVED (1<<4) /* card has been removed */ #define MMC_STATE_DOING_BKOPS (1<<5) /* card is doing BKOPS */ #define MMC_STATE_SUSPENDED (1<<6) /* card is suspended */ +#define MMC_STATE_CMDQ (1<<12) /* card is in cmd queue 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 */ @@ -503,6 +506,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED)) #define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) #define mmc_card_suspended(c) ((c)->state & MMC_STATE_SUSPENDED) +#define mmc_card_cmdq(c) ((c)->state & MMC_STATE_CMDQ) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -513,6 +517,8 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_clr_doing_bkops(c) ((c)->state &= ~MMC_STATE_DOING_BKOPS) #define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED) #define mmc_card_clr_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED) +#define mmc_card_set_cmdq(c) ((c)->state |= MMC_STATE_CMDQ) +#define mmc_card_clr_cmdq(c) ((c)->state &= ~MMC_STATE_CMDQ) /* * Quirk add/remove for MMC products. diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 2324a76123cf..a2fbb73996a7 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -88,6 +88,11 @@ enum mmc_load { MMC_LOAD_LOW, }; +struct mmc_cmdq_host_ops { + int (*enable)(struct mmc_host *host); + void (*disable)(struct mmc_host *host, bool soft); +}; + struct mmc_host_ops { /* * 'enable' is called when the host is claimed and 'disable' is called @@ -295,6 +300,7 @@ struct mmc_host { struct mmc_devfeq_clk_scaling clk_scaling; int index; const struct mmc_host_ops *ops; + const struct mmc_cmdq_host_ops *cmdq_ops; struct mmc_pwrseq *pwrseq; unsigned int f_min; unsigned int f_max; diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index a85e7e3f9bb6..71b5c75a0995 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -215,6 +215,7 @@ struct _mmc_csd { * EXT_CSD fields */ +#define EXT_CSD_CMDQ 15 /* R/W */ #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ From fc4b531dfdca023ac59389e7acf24f21aa6074ed Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 29 May 2015 16:51:43 -0700 Subject: [PATCH 246/472] mmc: card: add read/write support in command queue mode Command queueing is defined in eMMC-5.1. It is designed for higher performance by ensuring upto 32 requests to be serviced at a time. Adds read/write support for CMDQ enabled devices. Change-Id: I136ddea8e5ca57eb4f85ca6e72c60001a7e24f78 Signed-off-by: Sujit Reddy Thumma Signed-off-by: Asutosh Das Signed-off-by: Konstantin Dorfman Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 243 ++++++++++++++++++++++++++++++++++++++- drivers/mmc/card/queue.c | 2 + drivers/mmc/card/queue.h | 1 + drivers/mmc/core/core.c | 59 ++++++++++ include/linux/mmc/card.h | 2 + include/linux/mmc/core.h | 7 ++ include/linux/mmc/host.h | 22 ++++ 7 files changed, 334 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 81a572cb4e16..70b50657dd23 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -36,6 +36,7 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include @@ -119,6 +120,7 @@ struct mmc_blk_data { #define MMC_BLK_CMD23 (1 << 0) /* Can do SET_BLOCK_COUNT for multiblock */ #define MMC_BLK_REL_WR (1 << 1) /* MMC Reliable write support */ #define MMC_BLK_PACKED_CMD (1 << 2) /* MMC packed command support */ +#define MMC_BLK_CMD_QUEUE (1 << 3) /* MMC command queue support */ unsigned int usage; unsigned int read_only; @@ -158,6 +160,8 @@ MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); static inline int mmc_blk_part_switch(struct mmc_card *card, struct mmc_blk_data *md); static int get_card_status(struct mmc_card *card, u32 *status, int retries); +static int mmc_blk_cmdq_switch(struct mmc_card *card, + struct mmc_blk_data *md, bool enable); static inline void mmc_blk_clear_packed(struct mmc_queue_req *mqrq) { @@ -1033,6 +1037,44 @@ static const struct block_device_operations mmc_bdops = { #endif }; +static int mmc_blk_cmdq_switch(struct mmc_card *card, + struct mmc_blk_data *md, bool enable) +{ + int ret = 0; + bool cmdq_mode = !!mmc_card_cmdq(card); + + if (!(card->host->caps2 & MMC_CAP2_CMD_QUEUE) || + !card->ext_csd.cmdq_support || + (enable && !(md->flags & MMC_BLK_CMD_QUEUE)) || + (cmdq_mode == enable)) + return 0; + + if (enable) { + ret = mmc_set_blocklen(card, MMC_CARD_CMDQ_BLK_SIZE); + if (ret) { + pr_err("%s: failed (%d) to set block-size to %d\n", + __func__, ret, MMC_CARD_CMDQ_BLK_SIZE); + goto out; + } + } + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CMDQ, enable, + card->ext_csd.generic_cmd6_time); + if (ret) { + pr_err("%s: cmdq mode %sable failed %d\n", + md->disk->disk_name, enable ? "en" : "dis", ret); + goto out; + } + + if (enable) + mmc_card_set_cmdq(card); + else + mmc_card_clr_cmdq(card); +out: + return ret; +} + static inline int mmc_blk_part_switch(struct mmc_card *card, struct mmc_blk_data *md) { @@ -1046,6 +1088,13 @@ static inline int mmc_blk_part_switch(struct mmc_card *card, if (mmc_card_mmc(card)) { u8 part_config = card->ext_csd.part_config; + if (md->part_type) { + /* disable CQ mode for non-user data partitions */ + ret = mmc_blk_cmdq_switch(card, md, false); + if (ret) + return ret; + } + part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; part_config |= md->part_type; @@ -2491,6 +2540,166 @@ static void mmc_blk_revert_packed_req(struct mmc_queue *mq, mmc_blk_clear_packed(mq_rq); } +static int mmc_blk_cmdq_start_req(struct mmc_host *host, + struct mmc_cmdq_req *cmdq_req) +{ + struct mmc_request *mrq = &cmdq_req->mrq; + + mrq->done = mmc_blk_cmdq_req_done; + return mmc_cmdq_start_req(host, cmdq_req); +} + +#define IS_RT_CLASS_REQ(x) \ + (IOPRIO_PRIO_CLASS(req_get_ioprio(x)) == IOPRIO_CLASS_RT) + +static struct mmc_cmdq_req *mmc_blk_cmdq_rw_prep( + struct mmc_queue_req *mqrq, struct mmc_queue *mq) +{ + struct mmc_card *card = mq->card; + struct request *req = mqrq->req; + struct mmc_blk_data *md = mq->data; + bool do_rel_wr = mmc_req_rel_wr(req) && (md->flags & MMC_BLK_REL_WR); + bool do_data_tag; + bool read_dir = (rq_data_dir(req) == READ); + bool prio = IS_RT_CLASS_REQ(req); + struct mmc_cmdq_req *cmdq_rq = &mqrq->cmdq_req; + + memset(&mqrq->cmdq_req, 0, sizeof(struct mmc_cmdq_req)); + + cmdq_rq->tag = req->tag; + if (read_dir) { + cmdq_rq->cmdq_req_flags |= DIR; + cmdq_rq->data.flags = MMC_DATA_READ; + } else { + cmdq_rq->data.flags = MMC_DATA_WRITE; + } + if (prio) + cmdq_rq->cmdq_req_flags |= PRIO; + + if (do_rel_wr) + cmdq_rq->cmdq_req_flags |= REL_WR; + + cmdq_rq->data.blocks = blk_rq_sectors(req); + cmdq_rq->blk_addr = blk_rq_pos(req); + cmdq_rq->data.blksz = MMC_CARD_CMDQ_BLK_SIZE; + + mmc_set_data_timeout(&cmdq_rq->data, card); + + do_data_tag = (card->ext_csd.data_tag_unit_size) && + (req->cmd_flags & REQ_META) && + (rq_data_dir(req) == WRITE) && + ((cmdq_rq->data.blocks * cmdq_rq->data.blksz) >= + card->ext_csd.data_tag_unit_size); + if (do_data_tag) + cmdq_rq->cmdq_req_flags |= DAT_TAG; + cmdq_rq->data.sg = mqrq->sg; + cmdq_rq->data.sg_len = mmc_queue_map_sg(mq, mqrq); + + /* + * Adjust the sg list so it is the same size as the + * request. + */ + if (cmdq_rq->data.blocks > card->host->max_blk_count) + cmdq_rq->data.blocks = card->host->max_blk_count; + + if (cmdq_rq->data.blocks != blk_rq_sectors(req)) { + int i, data_size = cmdq_rq->data.blocks << 9; + struct scatterlist *sg; + + for_each_sg(cmdq_rq->data.sg, sg, cmdq_rq->data.sg_len, i) { + data_size -= sg->length; + if (data_size <= 0) { + sg->length += data_size; + i++; + break; + } + } + cmdq_rq->data.sg_len = i; + } + + mqrq->cmdq_req.cmd_flags = req->cmd_flags; + mqrq->cmdq_req.mrq.req = mqrq->req; + mqrq->cmdq_req.mrq.cmdq_req = &mqrq->cmdq_req; + mqrq->cmdq_req.mrq.data = &mqrq->cmdq_req.data; + mqrq->req->special = mqrq; + + pr_debug("%s: %s: mrq: 0x%p req: 0x%p mqrq: 0x%p bytes to xf: %d mmc_cmdq_req: 0x%p card-addr: 0x%08x dir(r-1/w-0): %d\n", + mmc_hostname(card->host), __func__, &mqrq->cmdq_req.mrq, + mqrq->req, mqrq, (cmdq_rq->data.blocks * cmdq_rq->data.blksz), + cmdq_rq, cmdq_rq->blk_addr, + (cmdq_rq->cmdq_req_flags & DIR) ? 1 : 0); + + return &mqrq->cmdq_req; +} + +static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) +{ + struct mmc_queue_req *active_mqrq; + struct mmc_card *card = mq->card; + struct mmc_host *host = card->host; + struct mmc_cmdq_req *mc_rq; + int ret = 0; + + BUG_ON((req->tag < 0) || (req->tag > card->ext_csd.cmdq_depth)); + BUG_ON(test_and_set_bit(req->tag, &host->cmdq_ctx.active_reqs)); + + active_mqrq = &mq->mqrq_cmdq[req->tag]; + active_mqrq->req = req; + + mc_rq = mmc_blk_cmdq_rw_prep(active_mqrq, mq); + + ret = mmc_blk_cmdq_start_req(card->host, mc_rq); + return ret; +} + +/* invoked by block layer in softirq context */ +void mmc_blk_cmdq_complete_rq(struct request *rq) +{ + struct mmc_queue_req *mq_rq = rq->special; + struct mmc_request *mrq = &mq_rq->cmdq_req.mrq; + struct mmc_host *host = mrq->host; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + struct mmc_cmdq_req *cmdq_req = &mq_rq->cmdq_req; + struct mmc_queue *mq = (struct mmc_queue *)rq->q->queuedata; + int err = 0; + + if (mrq->cmd && mrq->cmd->error) + err = mrq->cmd->error; + else if (mrq->data && mrq->data->error) + err = mrq->data->error; + + mmc_cmdq_post_req(host, mrq, err); + if (err) { + pr_err("%s: %s: txfr error: %d\n", mmc_hostname(mrq->host), + __func__, err); + set_bit(CMDQ_STATE_ERR, &ctx_info->curr_state); + WARN_ON(1); + } + + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->active_reqs)); + + blk_end_request(rq, err, cmdq_req->data.bytes_xfered); + + if (test_and_clear_bit(0, &ctx_info->req_starved)) + blk_run_queue(mq->queue); + + mmc_release_host(host); + return; +} + +/* + * Complete reqs from block layer softirq context + * Invoked in irq context + */ +void mmc_blk_cmdq_req_done(struct mmc_request *mrq) +{ + struct request *req = mrq->req; + + blk_complete_request(req); +} +EXPORT_SYMBOL(mmc_blk_cmdq_req_done); + static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) { struct mmc_blk_data *md = mq->data; @@ -2681,6 +2890,28 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) return 0; } +static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) +{ + int ret; + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + + mmc_claim_host(card->host); + ret = mmc_blk_part_switch(card, md); + if (ret) { + pr_err("%s: %s: partition switch failed %d\n", + md->disk->disk_name, __func__, ret); + blk_end_request_all(req, ret); + mmc_release_host(card->host); + goto switch_failure; + } + + ret = mmc_blk_cmdq_issue_rw_rq(mq, req); + +switch_failure: + return ret; +} + static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) { int ret; @@ -2851,12 +3082,18 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, if (mmc_card_mmc(card) && md->flags & MMC_BLK_CMD23 && ((card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN) || - card->ext_csd.rel_sectors)) { + card->ext_csd.rel_sectors) && !card->cmdq_init) { md->flags |= MMC_BLK_REL_WR; blk_queue_flush(md->queue.queue, REQ_FLUSH | REQ_FUA); } - if (mmc_card_mmc(card) && + if (card->cmdq_init) { + md->flags |= MMC_BLK_CMD_QUEUE; + md->queue.cmdq_complete_fn = mmc_blk_cmdq_complete_rq; + md->queue.cmdq_issue_fn = mmc_blk_cmdq_issue_rq; + } + + if (mmc_card_mmc(card) && !card->cmdq_init && (area_type == MMC_BLK_DATA_AREA_MAIN) && (md->flags & MMC_BLK_CMD23) && card->ext_csd.packed_event_en) { @@ -2969,6 +3206,8 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md) mmc_cleanup_queue(&md->queue); if (md->flags & MMC_BLK_PACKED_CMD) mmc_packed_clean(&md->queue); + if (md->flags & MMC_BLK_CMD_QUEUE) + mmc_cmdq_clean(&md->queue, card); device_remove_file(disk_to_dev(md->disk), &md->num_wr_reqs_to_start_packing); if (md->disk->flags & GENHD_FL_UP) { diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 02fc50e65c55..04a9e9274b7a 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -573,6 +573,7 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) /* one slot is reserved for dcmd requests */ int q_depth = card->ext_csd.cmdq_depth - 1; + card->cmdq_init = false; if (!(card->host->caps2 & MMC_CAP2_CMD_QUEUE)) { ret = -ENOTSUPP; goto out; @@ -606,6 +607,7 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) } blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); + card->cmdq_init = true; goto out; free_mqrq_sg: diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 3e7d174f3d9f..2795ec5e3062 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -42,6 +42,7 @@ struct mmc_queue_req { struct mmc_async_req mmc_active; enum mmc_packed_type cmd_type; struct mmc_packed *packed; + struct mmc_cmdq_req cmdq_req; }; struct mmc_queue { diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 10063df35db1..9bc5495acc67 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -888,6 +888,37 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) return 0; } +static void mmc_start_cmdq_request(struct mmc_host *host, + struct mmc_request *mrq) +{ + if (mrq->data) { + pr_debug("%s: blksz %d blocks %d flags %08x tsac %lu ms nsac %d\n", + mmc_hostname(host), mrq->data->blksz, + mrq->data->blocks, mrq->data->flags, + mrq->data->timeout_ns / NSEC_PER_MSEC, + mrq->data->timeout_clks); + + BUG_ON(mrq->data->blksz > host->max_blk_size); + BUG_ON(mrq->data->blocks > host->max_blk_count); + BUG_ON(mrq->data->blocks * mrq->data->blksz > + host->max_req_size); + mrq->data->error = 0; + mrq->data->mrq = mrq; + } + + if (mrq->cmd) { + mrq->cmd->error = 0; + mrq->cmd->mrq = mrq; + } + + mmc_host_clk_hold(host); + if (likely(host->cmdq_ops->request)) + host->cmdq_ops->request(host, mrq); + else + pr_err("%s: %s: issue request failed\n", mmc_hostname(host), + __func__); +} + /** * mmc_start_bkops - start BKOPS for supported cards * @card: MMC card to start BKOPS @@ -1162,6 +1193,34 @@ static void mmc_post_req(struct mmc_host *host, struct mmc_request *mrq, } } +/** + * mmc_cmdq_post_req - post process of a completed request + * @host: host instance + * @mrq: the request to be processed + * @err: non-zero is error, success otherwise + */ +void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) +{ + if (likely(host->cmdq_ops->post_req)) + host->cmdq_ops->post_req(host, mrq, err); + mmc_host_clk_release(host); +} +EXPORT_SYMBOL(mmc_cmdq_post_req); + +int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req) +{ + struct mmc_request *mrq = &cmdq_req->mrq; + + mrq->host = host; + if (mmc_card_removed(host->card)) { + mrq->cmd->error = -ENOMEDIUM; + return -ENOMEDIUM; + } + mmc_start_cmdq_request(host, mrq); + return 0; +} +EXPORT_SYMBOL(mmc_cmdq_start_req); + /** * mmc_start_req - start a non-blocking request * @host: MMC host to start command diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 9885e75d196b..e7fdaff68d7e 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -358,6 +358,7 @@ struct mmc_card { struct notifier_block reboot_notify; bool issue_long_pon; u8 *cached_ext_csd; + bool cmdq_init; }; /* @@ -613,4 +614,5 @@ extern struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics( struct mmc_card *card); extern void mmc_blk_init_packed_statistics(struct mmc_card *card); extern int mmc_send_long_pon(struct mmc_card *card); +extern void mmc_blk_cmdq_req_done(struct mmc_request *mrq); #endif /* LINUX_MMC_CARD_H */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 07f96aa5ff6b..8139a58e6d8e 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -108,11 +108,18 @@ struct mmc_request { struct completion completion; void (*done)(struct mmc_request *);/* completion function */ struct mmc_host *host; + struct mmc_cmdq_req *cmdq_req; struct request *req; }; struct mmc_card; struct mmc_async_req; +struct mmc_cmdq_req; + +extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, + int err); +extern int mmc_cmdq_start_req(struct mmc_host *host, + struct mmc_cmdq_req *cmdq_req); extern int mmc_stop_bkops(struct mmc_card *); extern int mmc_read_bkops_status(struct mmc_card *); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index a2fbb73996a7..12b8d5505985 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -91,6 +91,9 @@ enum mmc_load { struct mmc_cmdq_host_ops { int (*enable)(struct mmc_host *host); void (*disable)(struct mmc_host *host, bool soft); + int (*request)(struct mmc_host *host, struct mmc_request *mrq); + void (*post_req)(struct mmc_host *host, struct mmc_request *mrq, + int err); }; struct mmc_host_ops { @@ -174,6 +177,25 @@ struct mmc_host_ops { struct mmc_card; struct device; +struct mmc_cmdq_req { + unsigned int cmd_flags; + u32 blk_addr; + /* active mmc request */ + struct mmc_request mrq; + struct mmc_data data; + struct mmc_command cmd; +#define DCMD (1 << 0) +#define QBR (1 << 1) +#define DIR (1 << 2) +#define PRIO (1 << 3) +#define REL_WR (1 << 4) +#define DAT_TAG (1 << 5) +#define FORCED_PRG (1 << 6) + unsigned int cmdq_req_flags; + int tag; /* used for command queuing */ + u8 ctx_id; +}; + struct mmc_async_req { /* active mmc request */ struct mmc_request *mrq; From 89ada7fc6d8bc5cc9b198f84517003d5886a1c30 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 23 Apr 2015 16:00:45 +0530 Subject: [PATCH 247/472] mmc: core: add flush request support to command queue Adds flush request support to command-queue. This uses DCMD feature of the controller for sending commands in command-queue mode. DCMD is a direct command feature that uses a pre-configured slot for sending commands other than Class 11. Change-Id: Iebf6b74173dc91b0dc7230d1e87c65983d15148e Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 81 ++++++++++++++++++++++++++++++++++++-- drivers/mmc/card/queue.c | 3 +- drivers/mmc/core/core.c | 8 ++++ drivers/mmc/core/mmc_ops.c | 47 +++++++++++++++++++--- include/linux/mmc/core.h | 4 ++ include/linux/mmc/host.h | 1 + 6 files changed, 133 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 70b50657dd23..c258543e7db5 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2549,6 +2549,26 @@ static int mmc_blk_cmdq_start_req(struct mmc_host *host, return mmc_cmdq_start_req(host, cmdq_req); } +/* prepare for non-data commands */ +static struct mmc_cmdq_req *mmc_cmdq_prep_dcmd( + struct mmc_queue_req *mqrq, struct mmc_queue *mq) +{ + struct request *req = mqrq->req; + struct mmc_cmdq_req *cmdq_req = &mqrq->cmdq_req; + + memset(&mqrq->cmdq_req, 0, sizeof(struct mmc_cmdq_req)); + + cmdq_req->mrq.data = NULL; + cmdq_req->cmd_flags = req->cmd_flags; + cmdq_req->mrq.req = mqrq->req; + req->special = mqrq; + cmdq_req->cmdq_req_flags |= DCMD; + cmdq_req->mrq.cmdq_req = cmdq_req; + + return &mqrq->cmdq_req; +} + + #define IS_RT_CLASS_REQ(x) \ (IOPRIO_PRIO_CLASS(req_get_ioprio(x)) == IOPRIO_CLASS_RT) @@ -2652,6 +2672,47 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) return ret; } +/* + * Issues a flush (dcmd) request + */ +int mmc_blk_cmdq_issue_flush_rq(struct mmc_queue *mq, struct request *req) +{ + int err; + struct mmc_queue_req *active_mqrq; + struct mmc_card *card = mq->card; + struct mmc_host *host; + struct mmc_cmdq_req *cmdq_req; + struct mmc_cmdq_context_info *ctx_info; + + BUG_ON(!card); + host = card->host; + BUG_ON(!host); + BUG_ON(req->tag > card->ext_csd.cmdq_depth); + BUG_ON(test_and_set_bit(req->tag, &host->cmdq_ctx.active_reqs)); + + ctx_info = &host->cmdq_ctx; + + set_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); + + active_mqrq = &mq->mqrq_cmdq[req->tag]; + active_mqrq->req = req; + + cmdq_req = mmc_cmdq_prep_dcmd(active_mqrq, mq); + cmdq_req->cmdq_req_flags |= QBR; + cmdq_req->mrq.cmd = &cmdq_req->cmd; + cmdq_req->tag = req->tag; + + err = mmc_cmdq_prepare_flush(cmdq_req->mrq.cmd); + if (err) { + pr_err("%s: failed (%d) preparing flush req\n", + mmc_hostname(host), err); + return err; + } + err = mmc_blk_cmdq_start_req(card->host, cmdq_req); + return err; +} +EXPORT_SYMBOL(mmc_blk_cmdq_issue_flush_rq); + /* invoked by block layer in softirq context */ void mmc_blk_cmdq_complete_rq(struct request *rq) { @@ -2678,12 +2739,17 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) BUG_ON(!test_and_clear_bit(cmdq_req->tag, &ctx_info->active_reqs)); + if (cmdq_req->cmdq_req_flags & DCMD) { + clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); + blk_end_request_all(rq, 0); + goto out; + } blk_end_request(rq, err, cmdq_req->data.bytes_xfered); +out: if (test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); - mmc_release_host(host); return; } @@ -2895,18 +2961,25 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) int ret; struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; + unsigned int cmd_flags = req ? req->cmd_flags : 0; mmc_claim_host(card->host); ret = mmc_blk_part_switch(card, md); if (ret) { pr_err("%s: %s: partition switch failed %d\n", md->disk->disk_name, __func__, ret); - blk_end_request_all(req, ret); + if (req) + blk_end_request_all(req, ret); mmc_release_host(card->host); goto switch_failure; } - ret = mmc_blk_cmdq_issue_rw_rq(mq, req); + if (req) { + if (cmd_flags & REQ_FLUSH) + ret = mmc_blk_cmdq_issue_flush_rq(mq, req); + else + ret = mmc_blk_cmdq_issue_rw_rq(mq, req); + } switch_failure: return ret; @@ -3082,7 +3155,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, if (mmc_card_mmc(card) && md->flags & MMC_BLK_CMD23 && ((card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN) || - card->ext_csd.rel_sectors) && !card->cmdq_init) { + card->ext_csd.rel_sectors)) { md->flags |= MMC_BLK_REL_WR; blk_queue_flush(md->queue.queue, REQ_FLUSH | REQ_FUA); } diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 04a9e9274b7a..e88c1fac7be0 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -58,7 +58,8 @@ static int mmc_prep_request(struct request_queue *q, struct request *req) static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, struct mmc_cmdq_context_info *ctx) { - if (test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { + if (test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state) || + test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { pr_debug("%s: %s: skip pulling reqs: state: %lu\n", mmc_hostname(host), __func__, ctx->curr_state); return false; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9bc5495acc67..9204e1054d15 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1221,6 +1221,14 @@ int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req) } EXPORT_SYMBOL(mmc_cmdq_start_req); +int mmc_cmdq_prepare_flush(struct mmc_command *cmd) +{ + return __mmc_switch_cmdq_mode(cmd, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_FLUSH_CACHE, 1, + 0, true, true); +} +EXPORT_SYMBOL(mmc_cmdq_prepare_flush); + /** * mmc_start_req - start a non-blocking request * @host: MMC host to start command diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 6384bf34660f..85ddc4007480 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -465,6 +465,45 @@ int mmc_switch_status_error(struct mmc_host *host, u32 status) return 0; } +/** + * mmc_prepare_switch - helper; prepare to 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 + * @tout_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 + * + * Helper to prepare to modify EXT_CSD register for selected card. + */ + +static inline void mmc_prepare_switch(struct mmc_command *cmd, u8 index, + u8 value, u8 set, unsigned int tout_ms, + bool use_busy_signal) +{ + cmd->opcode = MMC_SWITCH; + cmd->arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (index << 16) | + (value << 8) | + set; + cmd->flags = MMC_CMD_AC; + cmd->busy_timeout = tout_ms; + if (use_busy_signal) + cmd->flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B; + else + cmd->flags |= MMC_RSP_SPI_R1 | MMC_RSP_R1; +} + +int __mmc_switch_cmdq_mode(struct mmc_command *cmd, u8 set, u8 index, u8 value, + unsigned int timeout_ms, bool use_busy_signal, + bool ignore_timeout) +{ + mmc_prepare_switch(cmd, index, value, set, timeout_ms, use_busy_signal); + return 0; +} +EXPORT_SYMBOL(__mmc_switch_cmdq_mode); + /** * __mmc_switch - modify EXT_CSD register * @card: the MMC card associated with the data transfer @@ -502,12 +541,8 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, (timeout_ms > host->max_busy_timeout)) use_r1b_resp = false; - cmd.opcode = MMC_SWITCH; - cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | - (index << 16) | - (value << 8) | - set; - cmd.flags = MMC_CMD_AC; + mmc_prepare_switch(&cmd, index, value, set, timeout_ms, + use_r1b_resp); if (use_r1b_resp) { cmd.flags |= MMC_RSP_SPI_R1B | MMC_RSP_R1B; /* diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 8139a58e6d8e..809398c612bf 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -120,6 +120,7 @@ extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err); extern int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req); +extern int mmc_cmdq_prepare_flush(struct mmc_command *cmd); extern int mmc_stop_bkops(struct mmc_card *); extern int mmc_read_bkops_status(struct mmc_card *); @@ -133,6 +134,9 @@ 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); +extern int __mmc_switch_cmdq_mode(struct mmc_command *cmd, u8 set, u8 index, + u8 value, unsigned int timeout_ms, + bool use_busy_signal, bool ignore_timeout); extern int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error); extern int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 12b8d5505985..8968fa6d1eb1 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -235,6 +235,7 @@ struct mmc_cmdq_context_info { unsigned long active_reqs; /* in-flight requests */ unsigned long curr_state; #define CMDQ_STATE_ERR 0 +#define CMDQ_STATE_DCMD_ACTIVE 1 /* no free tag available */ unsigned long req_starved; }; From c811ac59932ffbe91526e2eaa3cb02bc200bdac5 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 29 May 2015 17:25:46 -0700 Subject: [PATCH 248/472] mmc: cmdq: support for command queue enabled host This patch adds CMDQ support for command-queue compatible hosts. Command queue is added in eMMC-5.1 specification. This enables the controller to process upto 32 requests at a time. Change-Id: I0486495ef57c64bf8427e917daeb184c69b8dc73 Signed-off-by: Asutosh Das Signed-off-by: Sujit Reddy Thumma Signed-off-by: Konstantin Dorfman Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/Kconfig | 13 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/cmdq_hci.c | 656 ++++++++++++++++++++++++++++++++++++ drivers/mmc/host/cmdq_hci.h | 211 ++++++++++++ include/linux/mmc/host.h | 12 + 5 files changed, 893 insertions(+) create mode 100644 drivers/mmc/host/cmdq_hci.c create mode 100644 drivers/mmc/host/cmdq_hci.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 0325e914899d..63c67e0e1aaf 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -791,6 +791,19 @@ config MMC_SUNXI This selects support for the SD/MMC Host Controller on Allwinner sunxi SoCs. +config MMC_CQ_HCI + tristate "Command Queue Support" + depends on HAS_DMA + help + This selects the Command Queue Host Controller Interface (CQHCI) + support present in host controllers of Qualcomm Technologies, Inc + amongst others. + This controller supports eMMC devices with command queue support. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_TOSHIBA_PCI tristate "Toshiba Type A SD/MMC Card Interface Driver" depends on PCI diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index ee65b49df3ff..b9cbe592f5e3 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_MMC_SDHCI_MSM) += sdhci-msm.o obj-$(CONFIG_MMC_SDHCI_MSM_ICE) += sdhci-msm-ice.o obj-$(CONFIG_MMC_SDHCI_IPROC) += sdhci-iproc.o obj-$(CONFIG_MMC_SDHCI_ST) += sdhci-st.o +obj-$(CONFIG_MMC_CQ_HCI) += cmdq_hci.o ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c new file mode 100644 index 000000000000..68c8e0346f43 --- /dev/null +++ b/drivers/mmc/host/cmdq_hci.c @@ -0,0 +1,656 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "cmdq_hci.h" + +#define DCMD_SLOT 31 +#define NUM_SLOTS 32 + +static inline u8 *get_desc(struct cmdq_host *cq_host, u8 tag) +{ + return cq_host->desc_base + (tag * cq_host->slot_sz); +} + +static inline u8 *get_link_desc(struct cmdq_host *cq_host, u8 tag) +{ + u8 *desc = get_desc(cq_host, tag); + + return desc + cq_host->task_desc_len; +} + +static inline dma_addr_t get_trans_desc_dma(struct cmdq_host *cq_host, u8 tag) +{ + return cq_host->trans_desc_dma_base + + (cq_host->mmc->max_segs * tag * + cq_host->trans_desc_len); +} + +static inline u8 *get_trans_desc(struct cmdq_host *cq_host, u8 tag) +{ + return cq_host->trans_desc_base + + (cq_host->trans_desc_len * cq_host->mmc->max_segs * tag); +} + +static void setup_trans_desc(struct cmdq_host *cq_host, u8 tag) +{ + u8 *link_temp; + dma_addr_t trans_temp; + + link_temp = get_link_desc(cq_host, tag); + trans_temp = get_trans_desc_dma(cq_host, tag); + + memset(link_temp, 0, cq_host->link_desc_len); + if (cq_host->link_desc_len > 8) + *(link_temp + 8) = 0; + + if (tag == DCMD_SLOT) { + *link_temp = VALID(0) | ACT(0) | END(1); + return; + } + + *link_temp = VALID(1) | ACT(0x6) | END(0); + + if (cq_host->dma64) { + __le64 *data_addr = (__le64 __force *)(link_temp + 4); + data_addr[0] = cpu_to_le64(trans_temp); + } else { + __le32 *data_addr = (__le32 __force *)(link_temp + 4); + data_addr[0] = cpu_to_le32(trans_temp); + } +} + +static void cmdq_clear_set_irqs(struct cmdq_host *cq_host, u32 clear, u32 set) +{ + u32 ier; + + ier = cmdq_readl(cq_host, CQISTE); + ier &= ~clear; + ier |= set; + cmdq_writel(cq_host, ier, CQISTE); + cmdq_writel(cq_host, ier, CQISGE); + /* ensure the writes are done */ + mb(); +} + + +#define DRV_NAME "cmdq-host" + +static void cmdq_dump_debug_ram(struct cmdq_host *cq_host) +{ + int i = 0; + + pr_err("---- Debug RAM dump ----\n"); + pr_err(DRV_NAME ": Debug RAM wrap-around: 0x%08x | Debug RAM overlap: 0x%08x\n", + cmdq_readl(cq_host, CQ_CMD_DBG_RAM_WA), + cmdq_readl(cq_host, CQ_CMD_DBG_RAM_OL)); + + while (i < 16) { + pr_err(DRV_NAME ": Debug RAM dump [%d]: 0x%08x\n", i, + cmdq_readl(cq_host, CQ_CMD_DBG_RAM + (0x4 * i))); + i++; + } + pr_err("-------------------------\n"); +} + +static void cmdq_dumpregs(struct cmdq_host *cq_host) +{ + struct mmc_host *mmc = cq_host->mmc; + + pr_info(DRV_NAME ": ========== REGISTER DUMP (%s)==========\n", + mmc_hostname(mmc)); + + pr_info(DRV_NAME ": Caps: 0x%08x | Version: 0x%08x\n", + cmdq_readl(cq_host, CQCAP), + cmdq_readl(cq_host, CQVER)); + pr_info(DRV_NAME ": Queing config: 0x%08x | Queue Ctrl: 0x%08x\n", + cmdq_readl(cq_host, CQCFG), + cmdq_readl(cq_host, CQCTL)); + pr_info(DRV_NAME ": Int stat: 0x%08x | Int enab: 0x%08x\n", + cmdq_readl(cq_host, CQIS), + cmdq_readl(cq_host, CQISTE)); + pr_info(DRV_NAME ": Int sig: 0x%08x | Int Coal: 0x%08x\n", + cmdq_readl(cq_host, CQISGE), + cmdq_readl(cq_host, CQIC)); + pr_info(DRV_NAME ": TDL base: 0x%08x | TDL up32: 0x%08x\n", + cmdq_readl(cq_host, CQTDLBA), + cmdq_readl(cq_host, CQTDLBAU)); + pr_info(DRV_NAME ": Doorbell: 0x%08x | Comp Notif: 0x%08x\n", + cmdq_readl(cq_host, CQTDBR), + cmdq_readl(cq_host, CQTCN)); + pr_info(DRV_NAME ": Dev queue: 0x%08x | Dev Pend: 0x%08x\n", + cmdq_readl(cq_host, CQDQS), + cmdq_readl(cq_host, CQDPT)); + pr_info(DRV_NAME ": Task clr: 0x%08x | Send stat 1: 0x%08x\n", + cmdq_readl(cq_host, CQTCLR), + cmdq_readl(cq_host, CQSSC1)); + pr_info(DRV_NAME ": Send stat 2: 0x%08x | DCMD resp: 0x%08x\n", + cmdq_readl(cq_host, CQSSC2), + cmdq_readl(cq_host, CQCRDCT)); + pr_info(DRV_NAME ": Resp err mask: 0x%08x | Task err: 0x%08x\n", + cmdq_readl(cq_host, CQRMEM), + cmdq_readl(cq_host, CQTERRI)); + pr_info(DRV_NAME ": Resp idx 0x%08x | Resp arg: 0x%08x\n", + cmdq_readl(cq_host, CQCRI), + cmdq_readl(cq_host, CQCRA)); + pr_info(DRV_NAME ": ===========================================\n"); + + cmdq_dump_debug_ram(cq_host); + if (cq_host->ops->dump_vendor_regs) + cq_host->ops->dump_vendor_regs(mmc); +} + +/** + * The allocated descriptor table for task, link & transfer descritors + * looks like: + * |----------| + * |task desc | |->|----------| + * |----------| | |trans desc| + * |link desc-|->| |----------| + * |----------| . + * . . + * no. of slots max-segs + * . |----------| + * |----------| + * The idea here is to create the [task+trans] table and mark & point the + * link desc to the transfer desc table on a per slot basis. + */ +static int cmdq_host_alloc_tdl(struct cmdq_host *cq_host) +{ + + size_t desc_size; + size_t data_size; + int i = 0; + + /* task descriptor can be 64/128 bit irrespective of arch */ + if (cq_host->caps & CMDQ_TASK_DESC_SZ_128) { + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCFG) | + CQ_TASK_DESC_SZ, CQCFG); + cq_host->task_desc_len = 16; + } else { + cq_host->task_desc_len = 8; + } + + /* + * 96 bits length of transfer desc instead of 128 bits which means + * ADMA would expect next valid descriptor at the 96th bit + * or 128th bit + */ + if (cq_host->dma64) { + if (cq_host->quirks & CMDQ_QUIRK_SHORT_TXFR_DESC_SZ) + cq_host->trans_desc_len = 12; + else + cq_host->trans_desc_len = 16; + cq_host->link_desc_len = 16; + } else { + cq_host->trans_desc_len = 8; + cq_host->link_desc_len = 8; + } + + /* total size of a slot: 1 task & 1 transfer (link) */ + cq_host->slot_sz = cq_host->task_desc_len + cq_host->link_desc_len; + + desc_size = cq_host->slot_sz * cq_host->num_slots; + + data_size = cq_host->trans_desc_len * cq_host->mmc->max_segs * + (cq_host->num_slots - 1); + + pr_info("%s: desc_size: %d data_sz: %d slot-sz: %d\n", __func__, + (int)desc_size, (int)data_size, cq_host->slot_sz); + + /* + * allocate a dma-mapped chunk of memory for the descriptors + * allocate a dma-mapped chunk of memory for link descriptors + * setup each link-desc memory offset per slot-number to + * the descriptor table. + */ + cq_host->desc_base = dmam_alloc_coherent(mmc_dev(cq_host->mmc), + desc_size, + &cq_host->desc_dma_base, + GFP_KERNEL); + cq_host->trans_desc_base = dmam_alloc_coherent(mmc_dev(cq_host->mmc), + data_size, + &cq_host->trans_desc_dma_base, + GFP_KERNEL); + if (!cq_host->desc_base || !cq_host->trans_desc_base) + return -ENOMEM; + + pr_info("desc-base: 0x%p trans-base: 0x%p\n desc_dma 0x%llx trans_dma: 0x%llx\n", + cq_host->desc_base, cq_host->trans_desc_base, + (unsigned long long)cq_host->desc_dma_base, + (unsigned long long) cq_host->trans_desc_dma_base); + + for (; i < (cq_host->num_slots); i++) + setup_trans_desc(cq_host, i); + + return 0; +} + +static int cmdq_enable(struct mmc_host *mmc) +{ + int err = 0; + u32 cqcfg; + bool dcmd_enable; + struct cmdq_host *cq_host = mmc_cmdq_private(mmc); + + if (!cq_host || !mmc->card || !mmc_card_cmdq(mmc->card)) { + err = -EINVAL; + goto out; + } + + if (cq_host->enabled) + goto out; + + cqcfg = cmdq_readl(cq_host, CQCFG); + if (cqcfg & 0x1) { + pr_info("%s: %s: cq_host is already enabled\n", + mmc_hostname(mmc), __func__); + WARN_ON(1); + goto out; + } + + if (cq_host->quirks & CMDQ_QUIRK_NO_DCMD) + dcmd_enable = false; + else + dcmd_enable = true; + + cqcfg = ((cq_host->caps & CMDQ_TASK_DESC_SZ_128 ? CQ_TASK_DESC_SZ : 0) | + (dcmd_enable ? CQ_DCMD : 0)); + + cmdq_writel(cq_host, cqcfg, CQCFG); + /* enable CQ_HOST */ + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCFG) | CQ_ENABLE, + CQCFG); + + if (!cq_host->desc_base || + !cq_host->trans_desc_base) { + err = cmdq_host_alloc_tdl(cq_host); + if (err) + goto out; + cmdq_writel(cq_host, lower_32_bits(cq_host->desc_dma_base), + CQTDLBA); + cmdq_writel(cq_host, upper_32_bits(cq_host->desc_dma_base), + CQTDLBAU); + cmdq_dumpregs(cq_host); + } + + /* + * disable all vendor interrupts + * enable CMDQ interrupts + * enable the vendor error interrupts + */ + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, true); + + cmdq_clear_set_irqs(cq_host, 0x0, CQ_INT_ALL); + + /* cq_host would use this rca to address the card */ + cmdq_writel(cq_host, mmc->card->rca, CQSSC2); + + /* send QSR at lesser intervals than the default */ + cmdq_writel(cq_host, cmdq_readl(cq_host, CQSSC1) | SEND_QSR_INTERVAL, + CQSSC1); + + /* ensure the writes are done before enabling CQE */ + mb(); + + cq_host->enabled = true; + + if (cq_host->ops->set_block_size) + cq_host->ops->set_block_size(cq_host->mmc); + + if (cq_host->ops->set_data_timeout) + cq_host->ops->set_data_timeout(mmc, 0xf); + + if (cq_host->ops->clear_set_dumpregs) + cq_host->ops->clear_set_dumpregs(mmc, 1); + +out: + return err; +} + +static void cmdq_disable(struct mmc_host *mmc, bool soft) +{ + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + + if (soft) { + cmdq_writel(cq_host, cmdq_readl( + cq_host, CQCFG) & ~(CQ_ENABLE), + CQCFG); + } + + cq_host->enabled = false; +} + +static void cmdq_prep_task_desc(struct mmc_request *mrq, + u64 *data, bool intr, bool qbr) +{ + struct mmc_cmdq_req *cmdq_req = mrq->cmdq_req; + u32 req_flags = cmdq_req->cmdq_req_flags; + + pr_debug("%s: %s: data-tag: 0x%08x - dir: %d - prio: %d - cnt: 0x%08x - addr: 0x%llx\n", + mmc_hostname(mrq->host), __func__, + !!(req_flags & DAT_TAG), !!(req_flags & DIR), + !!(req_flags & PRIO), cmdq_req->data.blocks, + (u64)mrq->cmdq_req->blk_addr); + + *data = VALID(1) | + END(1) | + INT(intr) | + ACT(0x5) | + FORCED_PROG(!!(req_flags & FORCED_PRG)) | + CONTEXT(mrq->cmdq_req->ctx_id) | + DATA_TAG(!!(req_flags & DAT_TAG)) | + DATA_DIR(!!(req_flags & DIR)) | + PRIORITY(!!(req_flags & PRIO)) | + QBAR(qbr) | + REL_WRITE(!!(req_flags & REL_WR)) | + BLK_COUNT(mrq->cmdq_req->data.blocks) | + BLK_ADDR((u64)mrq->cmdq_req->blk_addr); +} + +static int cmdq_dma_map(struct mmc_host *host, struct mmc_request *mrq) +{ + int sg_count; + struct mmc_data *data = mrq->data; + + if (!data) + return -EINVAL; + + sg_count = dma_map_sg(mmc_dev(host), data->sg, + data->sg_len, + (data->flags & MMC_DATA_WRITE) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (!sg_count) { + pr_err("%s: sg-len: %d\n", __func__, data->sg_len); + return -ENOMEM; + } + + return sg_count; +} + +static void cmdq_set_tran_desc(u8 *desc, + dma_addr_t addr, int len, bool end) +{ + __le64 *dataddr = (__le64 __force *)(desc + 4); + __le32 *attr = (__le32 __force *)desc; + + *attr = (VALID(1) | + END(end ? 1 : 0) | + INT(0) | + ACT(0x4) | + DAT_LENGTH(len)); + + dataddr[0] = cpu_to_le64(addr); +} + +static int cmdq_prep_tran_desc(struct mmc_request *mrq, + struct cmdq_host *cq_host, int tag) +{ + struct mmc_data *data = mrq->data; + int i, sg_count, len; + bool end = false; + dma_addr_t addr; + u8 *desc; + struct scatterlist *sg; + + sg_count = cmdq_dma_map(mrq->host, mrq); + if (sg_count < 0) { + pr_err("%s: %s: unable to map sg lists, %d\n", + mmc_hostname(mrq->host), __func__, sg_count); + return sg_count; + } + + desc = get_trans_desc(cq_host, tag); + memset(desc, 0, cq_host->trans_desc_len * cq_host->mmc->max_segs); + + for_each_sg(data->sg, sg, sg_count, i) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + + if ((i+1) == sg_count) + end = true; + cmdq_set_tran_desc(desc, addr, len, end); + desc += cq_host->trans_desc_len; + } + + pr_debug("%s: req: 0x%p tag: %d calc_trans_des: 0x%p sg-cnt: %d\n", + __func__, mrq->req, tag, desc, sg_count); + + return 0; +} + +static void cmdq_prep_dcmd_desc(struct mmc_host *mmc, + struct mmc_request *mrq) +{ + u64 *task_desc = NULL; + u64 data = 0; + u8 resp_type; + u8 *desc; + __le64 *dataddr; + struct cmdq_host *cq_host = mmc_cmdq_private(mmc); + u8 timing; + + if (!(mrq->cmd->flags & MMC_RSP_PRESENT)) { + resp_type = 0x0; + timing = 0x1; + } else { + if (mrq->cmd->flags & MMC_RSP_R1B) { + resp_type = 0x3; + timing = 0x0; + } else { + resp_type = 0x2; + timing = 0x1; + } + } + + task_desc = (__le64 __force *)get_desc(cq_host, cq_host->dcmd_slot); + memset(task_desc, 0, cq_host->task_desc_len); + data |= (VALID(1) | + END(1) | + INT(1) | + QBAR(1) | + ACT(0x5) | + CMD_INDEX(mrq->cmd->opcode) | + CMD_TIMING(timing) | RESP_TYPE(resp_type)); + *task_desc |= data; + desc = (u8 *)task_desc; + pr_debug("cmdq: dcmd: cmd: %d timing: %d resp: %d\n", + mrq->cmd->opcode, timing, resp_type); + dataddr = (__le64 __force *)(desc + 4); + dataddr[0] = cpu_to_le64((u64)mrq->cmd->arg); + +} + +static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + int err; + u64 data = 0; + u64 *task_desc = NULL; + u32 tag = mrq->cmdq_req->tag; + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + + if (!cq_host->enabled) { + pr_err("%s: CMDQ host not enabled yet !!!\n", + mmc_hostname(mmc)); + err = -EINVAL; + goto out; + } + + if (mrq->cmdq_req->cmdq_req_flags & DCMD) { + cmdq_prep_dcmd_desc(mmc, mrq); + cq_host->mrq_slot[DCMD_SLOT] = mrq; + cmdq_writel(cq_host, 1 << DCMD_SLOT, CQTDBR); + return 0; + } + + task_desc = (__le64 __force *)get_desc(cq_host, tag); + + cmdq_prep_task_desc(mrq, &data, 1, + (mrq->cmdq_req->cmdq_req_flags & QBR)); + *task_desc = cpu_to_le64(data); + + err = cmdq_prep_tran_desc(mrq, cq_host, tag); + if (err) { + pr_err("%s: %s: failed to setup tx desc: %d\n", + mmc_hostname(mmc), __func__, err); + return err; + } + + BUG_ON(cmdq_readl(cq_host, CQTDBR) & (1 << tag)); + + cq_host->mrq_slot[tag] = mrq; + if (cq_host->ops->set_tranfer_params) + cq_host->ops->set_tranfer_params(mmc); + + cmdq_writel(cq_host, 1 << tag, CQTDBR); + +out: + return err; +} + +static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) +{ + struct mmc_request *mrq; + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + + mrq = cq_host->mrq_slot[tag]; + mrq->done(mrq); +} + +irqreturn_t cmdq_irq(struct mmc_host *mmc, u32 intmask) +{ + u32 status; + unsigned long tag = 0, comp_status; + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + + status = cmdq_readl(cq_host, CQIS); + cmdq_writel(cq_host, status, CQIS); + + if (status & CQIS_TCC) { + /* read QCTCN and complete the request */ + comp_status = cmdq_readl(cq_host, CQTCN); + if (!comp_status) + goto out; + + for_each_set_bit(tag, &comp_status, cq_host->num_slots) { + /* complete the corresponding mrq */ + pr_debug("%s: completing tag -> %lu\n", + mmc_hostname(mmc), tag); + cmdq_finish_data(mmc, tag); + } + cmdq_writel(cq_host, comp_status, CQTCN); + } + + if (status & CQIS_RED) { + /* task response has an error */ + pr_err("%s: RED error %d !!!\n", mmc_hostname(mmc), status); + cmdq_dumpregs(cq_host); + } + +out: + return IRQ_HANDLED; +} +EXPORT_SYMBOL(cmdq_irq); + +static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, + int err) +{ + struct mmc_data *data = mrq->data; + + if (data) { + data->error = err; + dma_unmap_sg(mmc_dev(host), data->sg, data->sg_len, + (data->flags & MMC_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE); + if (err) + data->bytes_xfered = 0; + else + data->bytes_xfered = blk_rq_bytes(mrq->req); + } +} + +static const struct mmc_cmdq_host_ops cmdq_host_ops = { + .enable = cmdq_enable, + .disable = cmdq_disable, + .request = cmdq_request, + .post_req = cmdq_post_req, +}; + +struct cmdq_host *cmdq_pltfm_init(struct platform_device *pdev) +{ + struct cmdq_host *cq_host; + struct resource *cmdq_memres = NULL; + + /* check and setup CMDQ interface */ + cmdq_memres = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "cmdq_mem"); + if (!cmdq_memres) { + dev_dbg(&pdev->dev, "CMDQ not supported\n"); + return ERR_PTR(-EINVAL); + } + + cq_host = kzalloc(sizeof(*cq_host), GFP_KERNEL); + if (!cq_host) { + dev_err(&pdev->dev, "failed to allocate memory for CMDQ\n"); + return ERR_PTR(-ENOMEM); + } + cq_host->mmio = devm_ioremap(&pdev->dev, + cmdq_memres->start, + resource_size(cmdq_memres)); + if (!cq_host->mmio) { + dev_err(&pdev->dev, "failed to remap cmdq regs\n"); + kfree(cq_host); + return ERR_PTR(-EBUSY); + } + dev_dbg(&pdev->dev, "CMDQ ioremap: done\n"); + + return cq_host; +} +EXPORT_SYMBOL(cmdq_pltfm_init); + +int cmdq_init(struct cmdq_host *cq_host, struct mmc_host *mmc, + bool dma64) +{ + int err = 0; + + cq_host->dma64 = dma64; + cq_host->mmc = mmc; + cq_host->mmc->cmdq_private = cq_host; + + cq_host->num_slots = NUM_SLOTS; + cq_host->dcmd_slot = DCMD_SLOT; + + mmc->cmdq_ops = &cmdq_host_ops; + + cq_host->mrq_slot = kzalloc(sizeof(cq_host->mrq_slot) * + cq_host->num_slots, GFP_KERNEL); + if (!cq_host->mrq_slot) + return -ENOMEM; + + init_completion(&cq_host->halt_comp); + return err; +} +EXPORT_SYMBOL(cmdq_init); diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h new file mode 100644 index 000000000000..e7f5a158b8a2 --- /dev/null +++ b/drivers/mmc/host/cmdq_hci.h @@ -0,0 +1,211 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef LINUX_MMC_CQ_HCI_H +#define LINUX_MMC_CQ_HCI_H +#include + +/* registers */ +/* version */ +#define CQVER 0x00 +/* capabilities */ +#define CQCAP 0x04 +/* configuration */ +#define CQCFG 0x08 +#define CQ_DCMD 0x00001000 +#define CQ_TASK_DESC_SZ 0x00000100 +#define CQ_ENABLE 0x00000001 + +/* control */ +#define CQCTL 0x0C +#define CLEAR_ALL_TASKS 0x00000100 +#define HALT 0x00000001 + +/* interrupt status */ +#define CQIS 0x10 +#define CQIS_HAC (1 << 0) +#define CQIS_TCC (1 << 1) +#define CQIS_RED (1 << 2) +#define CQIS_TCL (1 << 3) + +/* interrupt status enable */ +#define CQISTE 0x14 + +/* interrupt signal enable */ +#define CQISGE 0x18 + +/* interrupt coalescing */ +#define CQIC 0x1C +#define CQIC_ENABLE (1 << 31) +#define CQIC_RESET (1 << 16) +#define CQIC_ICCTHWEN (1 << 15) +#define CQIC_ICCTH(x) ((x & 0x1F) << 8) +#define CQIC_ICTOVALWEN (1 << 7) +#define CQIC_ICTOVAL(x) (x & 0x7F) + +/* task list base address */ +#define CQTDLBA 0x20 + +/* task list base address upper */ +#define CQTDLBAU 0x24 + +/* door-bell */ +#define CQTDBR 0x28 + +/* task completion notification */ +#define CQTCN 0x2C + +/* device queue status */ +#define CQDQS 0x30 + +/* device pending tasks */ +#define CQDPT 0x34 + +/* task clear */ +#define CQTCLR 0x38 + +/* send status config 1 */ +#define CQSSC1 0x40 +/* + * Value n means CQE would send CMD13 during the transfer of data block + * BLOCK_CNT-n + */ +#define SEND_QSR_INTERVAL 0x70000 + +/* send status config 2 */ +#define CQSSC2 0x44 + +/* response for dcmd */ +#define CQCRDCT 0x48 + +/* response mode error mask */ +#define CQRMEM 0x50 + +/* task error info */ +#define CQTERRI 0x54 + +/* command response index */ +#define CQCRI 0x58 + +/* command response argument */ +#define CQCRA 0x5C + +#define CQ_INT_ALL 0xF +#define CQIC_DEFAULT_ICCTH 31 +#define CQIC_DEFAULT_ICTOVAL 1 + +#define CQ_CMD_DBG_RAM 0x158 +#define CQ_CMD_DBG_RAM_WA 0x198 +#define CQ_CMD_DBG_RAM_OL 0x19C + +/* attribute fields */ +#define VALID(x) ((x & 1) << 0) +#define END(x) ((x & 1) << 1) +#define INT(x) ((x & 1) << 2) +#define ACT(x) ((x & 0x7) << 3) + +/* data command task descriptor fields */ +#define FORCED_PROG(x) ((x & 1) << 6) +#define CONTEXT(x) ((x & 0xF) << 7) +#define DATA_TAG(x) ((x & 1) << 11) +#define DATA_DIR(x) ((x & 1) << 12) +#define PRIORITY(x) ((x & 1) << 13) +#define QBAR(x) ((x & 1) << 14) +#define REL_WRITE(x) ((x & 1) << 15) +#define BLK_COUNT(x) ((x & 0xFFFF) << 16) +#define BLK_ADDR(x) ((x & 0xFFFFFFFF) << 32) + +/* direct command task descriptor fields */ +#define CMD_INDEX(x) ((x & 0x3F) << 16) +#define CMD_TIMING(x) ((x & 1) << 22) +#define RESP_TYPE(x) ((x & 0x3) << 23) + +/* transfer descriptor fields */ +#define DAT_LENGTH(x) ((x & 0xFFFF) << 16) +#define DAT_ADDR_LO(x) ((x & 0xFFFFFFFF) << 32) +#define DAT_ADDR_HI(x) ((x & 0xFFFFFFFF) << 0) + +struct cmdq_host { + const struct cmdq_host_ops *ops; + void __iomem *mmio; + struct mmc_host *mmc; + + /* 64 bit DMA */ + bool dma64; + int num_slots; + + u32 dcmd_slot; + u32 caps; +#define CMDQ_TASK_DESC_SZ_128 0x1 + + u32 quirks; +#define CMDQ_QUIRK_SHORT_TXFR_DESC_SZ 0x1 +#define CMDQ_QUIRK_NO_DCMD 0x2 + + bool enabled; + bool halted; + bool init_done; + + u8 *desc_base; + + /* total descriptor size */ + u8 slot_sz; + + /* 64/128 bit depends on CQCFG */ + u8 task_desc_len; + + /* 64 bit on 32-bit arch, 128 bit on 64-bit */ + u8 link_desc_len; + + u8 *trans_desc_base; + /* same length as transfer descriptor */ + u8 trans_desc_len; + + dma_addr_t desc_dma_base; + dma_addr_t trans_desc_dma_base; + + struct completion halt_comp; + struct mmc_request **mrq_slot; + void *private; +}; + +struct cmdq_host_ops { + void (*set_tranfer_params)(struct mmc_host *mmc); + void (*set_data_timeout)(struct mmc_host *mmc, u32 val); + void (*clear_set_irqs)(struct mmc_host *mmc, bool clear); + void (*set_block_size)(struct mmc_host *mmc); + void (*dump_vendor_regs)(struct mmc_host *mmc); + void (*write_l)(struct cmdq_host *host, u32 val, int reg); + u32 (*read_l)(struct cmdq_host *host, int reg); + void (*clear_set_dumpregs)(struct mmc_host *mmc, bool set); +}; + +static inline void cmdq_writel(struct cmdq_host *host, u32 val, int reg) +{ + if (unlikely(host->ops->write_l)) + host->ops->write_l(host, val, reg); + else + writel_relaxed(val, host->mmio + reg); +} + +static inline u32 cmdq_readl(struct cmdq_host *host, int reg) +{ + if (unlikely(host->ops->read_l)) + return host->ops->read_l(host, reg); + else + return readl_relaxed(host->mmio + reg); +} + +extern irqreturn_t cmdq_irq(struct mmc_host *mmc, u32 intmask); +extern int cmdq_init(struct cmdq_host *cq_host, struct mmc_host *mmc, + bool dma64); +extern struct cmdq_host *cmdq_pltfm_init(struct platform_device *pdev); +#endif diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 8968fa6d1eb1..2159303f111f 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -540,6 +540,13 @@ struct mmc_host { enum dev_state dev_status; bool wakeup_on_idle; struct mmc_cmdq_context_info cmdq_ctx; + /* + * several cmdq supporting host controllers are extensions + * of legacy controllers. This variable can be used to store + * a reference to the cmdq extension of the existing host + * controller. + */ + void *cmdq_private; unsigned long private[0] ____cacheline_aligned; }; @@ -563,6 +570,11 @@ static inline void *mmc_priv(struct mmc_host *host) return (void *)host->private; } +static inline void *mmc_cmdq_private(struct mmc_host *host) +{ + return host->cmdq_private; +} + #define mmc_host_is_spi(host) ((host)->caps & MMC_CAP_SPI) #define mmc_dev(x) ((x)->parent) From 44a464b93ce11a63b43e0f05b9baf9756fed1117 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 21 May 2015 17:21:07 +0530 Subject: [PATCH 249/472] mmc: core: Add halt support Halt is a controller feature that can change the controller mode from command-queue to legacy. This feature is very helpful in error cases. Change-Id: I7f1465b609afed68886256bd605d4019716964f4 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/queue.c | 1 + drivers/mmc/core/core.c | 33 +++++++++++++++++++++++++++++++++ include/linux/mmc/core.h | 1 + include/linux/mmc/host.h | 17 +++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index e88c1fac7be0..1a441b4c90a4 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -59,6 +59,7 @@ static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, struct mmc_cmdq_context_info *ctx) { if (test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state) || + mmc_host_halt(host) || test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { pr_debug("%s: %s: skip pulling reqs: state: %lu\n", mmc_hostname(host), __func__, ctx->curr_state); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9204e1054d15..c1cc8c96da34 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1207,6 +1207,39 @@ void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) } EXPORT_SYMBOL(mmc_cmdq_post_req); +/** + * mmc_cmdq_halt - halt/un-halt the command queue engine + * @host: host instance + * @halt: true - halt, un-halt otherwise + * + * Host halts the command queue engine. It should complete + * the ongoing transfer and release the bus. + * All legacy commands can be sent upon successful + * completion of this function. + * Returns 0 on success, negative otherwise + */ +int mmc_cmdq_halt(struct mmc_host *host, bool halt) +{ + int err = 0; + + if ((halt && mmc_host_halt(host)) || + (!halt && !mmc_host_halt(host))) + return -EINVAL; + + if (host->cmdq_ops->halt) { + err = host->cmdq_ops->halt(host, halt); + if (!err && halt) + mmc_host_set_halt(host); + else if (!err && !halt) + mmc_host_clr_halt(host); + } else { + err = -ENOSYS; + } + + return err; +} +EXPORT_SYMBOL(mmc_cmdq_halt); + int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req) { struct mmc_request *mrq = &cmdq_req->mrq; diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 809398c612bf..8bf192d6a7d4 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -116,6 +116,7 @@ struct mmc_card; struct mmc_async_req; struct mmc_cmdq_req; +extern int mmc_cmdq_halt(struct mmc_host *host, bool enable); extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err); extern int mmc_cmdq_start_req(struct mmc_host *host, diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 2159303f111f..4e555b99223e 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -94,6 +94,7 @@ struct mmc_cmdq_host_ops { int (*request)(struct mmc_host *host, struct mmc_request *mrq); void (*post_req)(struct mmc_host *host, struct mmc_request *mrq, int err); + int (*halt)(struct mmc_host *host, bool halt); }; struct mmc_host_ops { @@ -236,6 +237,7 @@ struct mmc_cmdq_context_info { unsigned long curr_state; #define CMDQ_STATE_ERR 0 #define CMDQ_STATE_DCMD_ACTIVE 1 +#define CMDQ_STATE_HALT 2 /* no free tag available */ unsigned long req_starved; }; @@ -665,6 +667,21 @@ static inline int mmc_host_packed_wr(struct mmc_host *host) return host->caps2 & MMC_CAP2_PACKED_WR; } +static inline void mmc_host_set_halt(struct mmc_host *host) +{ + set_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); +} + +static inline void mmc_host_clr_halt(struct mmc_host *host) +{ + clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); +} + +static inline int mmc_host_halt(struct mmc_host *host) +{ + return test_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); +} + #ifdef CONFIG_MMC_CLKGATE void mmc_host_clk_hold(struct mmc_host *host); void mmc_host_clk_release(struct mmc_host *host); From 5434534be02ad105213949532796ffbb717814ea Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 21 May 2015 17:22:10 +0530 Subject: [PATCH 250/472] mmc: cmdq-host: add halt support to command queue host Halt can be used in error cases to get control of the bus. This is used to remove a task from device queue and/or other recovery mechanisms. Change-Id: I4ada286aefe57b90bfd20d60f8fbe2c013d9db71 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/cmdq_hci.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 68c8e0346f43..64be0ceda311 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -29,6 +29,9 @@ #define DCMD_SLOT 31 #define NUM_SLOTS 32 +/* 1 sec */ +#define HALT_TIMEOUT_MS 1000 + static inline u8 *get_desc(struct cmdq_host *cq_host, u8 tag) { return cq_host->desc_base + (tag * cq_host->slot_sz); @@ -570,11 +573,42 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, u32 intmask) cmdq_dumpregs(cq_host); } + if (status & CQIS_HAC) { + /* halt is completed, wakeup waiting thread */ + complete(&cq_host->halt_comp); + } + out: return IRQ_HANDLED; } EXPORT_SYMBOL(cmdq_irq); +/* May sleep */ +static int cmdq_halt(struct mmc_host *mmc, bool halt) +{ + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + u32 val; + + if (halt) { + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) | HALT, + CQCTL); + val = wait_for_completion_timeout(&cq_host->halt_comp, + msecs_to_jiffies(HALT_TIMEOUT_MS)); + /* halt done: re-enable legacy interrupts */ + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, false); + + return val ? 0 : -ETIMEDOUT; + } else { + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, true); + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) & ~HALT, + CQCTL); + } + + return 0; +} + static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) { @@ -597,6 +631,7 @@ static const struct mmc_cmdq_host_ops cmdq_host_ops = { .disable = cmdq_disable, .request = cmdq_request, .post_req = cmdq_post_req, + .halt = cmdq_halt, }; struct cmdq_host *cmdq_pltfm_init(struct platform_device *pdev) From c4dd8dad71e8b77f7585e2b7d74d2ea7dcfcf91d Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 17 Oct 2014 16:36:47 +0530 Subject: [PATCH 251/472] mmc: sdhci: add command queue support to sdhci Adds command-queue support to SDHCi compliant drivers. Change-Id: I1efee7f1c86e102364083e9158e4d45c887dd06e Signed-off-by: Asutosh Das Signed-off-by: Konstantin Dorfman Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts and compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 146 +++++++++++++++++++++++++++++++++++++-- drivers/mmc/host/sdhci.h | 6 ++ 2 files changed, 148 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 7cb82926a06b..1522405f5567 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -35,6 +35,7 @@ #include #include "sdhci.h" +#include "cmdq_hci.h" #define DRIVER_NAME "sdhci" @@ -2918,6 +2919,20 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } } +#ifdef CONFIG_MMC_CQ_HCI +static irqreturn_t sdhci_cmdq_irq(struct mmc_host *mmc, u32 intmask) +{ + return cmdq_irq(mmc, intmask); +} + +#else +static irqreturn_t sdhci_cmdq_irq(struct mmc_host *mmc, u32 intmask) +{ + pr_err("%s: rxd cmdq-irq when disabled !!!!\n", mmc_hostname(mmc)); + return IRQ_NONE; +} +#endif + static irqreturn_t sdhci_irq(int irq, void *dev_id) { irqreturn_t result = IRQ_NONE; @@ -2939,6 +2954,15 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) } do { + if (host->mmc->card && mmc_card_cmdq(host->mmc->card) && + !mmc_host_halt(host->mmc)) { + pr_debug("*** %s: cmdq intr: 0x%08x\n", + mmc_hostname(host->mmc), + intmask); + result = sdhci_cmdq_irq(host->mmc, intmask); + goto out; + } + if (intmask & SDHCI_INT_AUTO_CMD_ERR) host->auto_cmd_err_sts = sdhci_readw(host, SDHCI_AUTO_CMD_ERR); @@ -3302,6 +3326,106 @@ static void sdhci_set_pmqos_req_type(struct sdhci_host *host) } #endif +#ifdef CONFIG_MMC_CQ_HCI +static void sdhci_cmdq_clear_set_irqs(struct mmc_host *mmc, bool clear) +{ + struct sdhci_host *host = mmc_priv(mmc); + u32 ier = 0; + + ier &= ~SDHCI_INT_ALL_MASK; + + if (clear) { + ier = SDHCI_INT_CMDQ_EN | SDHCI_INT_ERROR_MASK; + sdhci_writel(host, ier, SDHCI_INT_ENABLE); + sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE); + } else { + ier = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | + SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | + SDHCI_INT_INDEX | SDHCI_INT_END_BIT | + SDHCI_INT_CRC | SDHCI_INT_TIMEOUT | + SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE | + SDHCI_INT_AUTO_CMD_ERR; + sdhci_writel(host, ier, SDHCI_INT_ENABLE); + sdhci_writel(host, ier, SDHCI_SIGNAL_ENABLE); + } +} + +static void sdhci_cmdq_set_data_timeout(struct mmc_host *mmc, u32 val) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_writeb(host, val, SDHCI_TIMEOUT_CONTROL); +} + +static void sdhci_cmdq_dump_vendor_regs(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_dumpregs(host); +} + +static int sdhci_cmdq_init(struct sdhci_host *host, struct mmc_host *mmc, + bool dma64) +{ + return cmdq_init(host->cq_host, mmc, dma64); +} + +static void sdhci_cmdq_set_block_size(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_set_blk_size_reg(host, 512, 0); +} + +static void sdhci_cmdq_clear_set_dumpregs(struct mmc_host *mmc, bool set) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->ops->clear_set_dumpregs) + host->ops->clear_set_dumpregs(host, set); +} +#else +static void sdhci_cmdq_clear_set_irqs(struct mmc_host *mmc, bool clear) +{ + +} + +static void sdhci_cmdq_set_data_timeout(struct mmc_host *mmc, u32 val) +{ + +} + +static void sdhci_cmdq_dump_vendor_regs(struct mmc_host *mmc) +{ + +} + +static int sdhci_cmdq_init(struct sdhci_host *host, struct mmc_host *mmc, + bool dma64) +{ + return -ENOSYS; +} + +static void sdhci_cmdq_set_block_size(struct mmc_host *mmc) +{ + +} + +static void sdhci_cmdq_clear_set_dumpregs(struct mmc_host *mmc, bool set) +{ + +} + +#endif + +static const struct cmdq_host_ops sdhci_cmdq_ops = { + .clear_set_irqs = sdhci_cmdq_clear_set_irqs, + .set_data_timeout = sdhci_cmdq_set_data_timeout, + .dump_vendor_regs = sdhci_cmdq_dump_vendor_regs, + .set_block_size = sdhci_cmdq_set_block_size, + .clear_set_dumpregs = sdhci_cmdq_clear_set_dumpregs, +}; + int sdhci_add_host(struct sdhci_host *host) { struct mmc_host *mmc; @@ -3861,11 +3985,25 @@ int sdhci_add_host(struct sdhci_host *host) sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); } - pr_info("%s: SDHCI controller on %s [%s] using %s\n", - mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), + if (mmc->caps2 & MMC_CAP2_CMD_QUEUE) { + bool dma64 = (host->flags & SDHCI_USE_ADMA_64BIT) ? + true : false; + ret = sdhci_cmdq_init(host, mmc, dma64); + if (ret) + pr_err("%s: CMDQ init: failed (%d)\n", + mmc_hostname(host->mmc), ret); + else + host->cq_host->ops = &sdhci_cmdq_ops; + } + + pr_info("%s: SDHCI controller on %s [%s] using %s in %s mode\n", + mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), (host->flags & SDHCI_USE_ADMA) ? - (host->flags & SDHCI_USE_64_BIT_DMA) ? "ADMA 64-bit" : "ADMA" : - (host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"); + ((host->flags & SDHCI_USE_ADMA_64BIT) ? + "64-bit ADMA" : "32-bit ADMA") : + ((host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"), + ((mmc->caps2 & MMC_CAP2_CMD_QUEUE) && !ret) ? + "CMDQ" : "legacy"); sdhci_enable_card_detection(host); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 8c033f7b2cf9..5e99cf53e3d2 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -153,6 +153,8 @@ SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \ SDHCI_INT_DATA_END_BIT | SDHCI_INT_ADMA_ERROR | \ SDHCI_INT_BLK_GAP) + +#define SDHCI_INT_CMDQ_EN (0x1 << 14) #define SDHCI_INT_ALL_MASK ((unsigned int)-1) #define SDHCI_AUTO_CMD_ERR 0x3C @@ -528,6 +530,7 @@ struct sdhci_host { #define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */ #define SDHCI_SDR104_NEEDS_TUNING (1<<10) /* SDR104/HS200 needs tuning */ #define SDHCI_USE_64_BIT_DMA (1<<12) /* Use 64-bit DMA */ +#define SDHCI_USE_ADMA_64BIT (1<<12) /* Host is 64-bit ADMA capable */ #define SDHCI_HS400_TUNING (1<<13) /* Tuning for HS400 */ unsigned int version; /* SDHCI spec. version */ @@ -610,6 +613,8 @@ struct sdhci_host { u32 auto_cmd_err_sts; struct ratelimit_state dbg_dump_rs; + struct cmdq_host *cq_host; + unsigned long private[0] ____cacheline_aligned; }; @@ -657,6 +662,7 @@ struct sdhci_ops { bool enable, u32 type); int (*enable_controller_clock)(struct sdhci_host *host); + void (*clear_set_dumpregs)(struct sdhci_host *host, bool set); void (*dump_vendor_regs)(struct sdhci_host *host); void (*toggle_cdr)(struct sdhci_host *host, bool enable); void (*voltage_switch)(struct sdhci_host *host); From 11bb322287709984f829ca51e65a929cd8fb7f9c Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 29 May 2015 17:49:46 -0700 Subject: [PATCH 252/472] mmc: sdhci-msm: add support for command-queue Adds support for command-queue (CQ). MSM driver supports CQ in the hardware. The controller can send commands to the card and read statuses from the device. This patch adds support for the same. Change-Id: I1b19a2ce4c124c96dc6c3852d8f58ad076851f4b Signed-off-by: Asutosh Das Signed-off-by: Konstantin Dorfman Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 03040ad52b2b..c5c2881556a7 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -41,6 +41,7 @@ #include "sdhci-msm.h" #include "sdhci-msm-ice.h" +#include "cmdq_hci.h" #define CORE_POWER 0x0 #define CORE_SW_RST (1 << 7) @@ -2841,6 +2842,28 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, msm_host->caps_0 = caps; } +#ifdef CONFIG_MMC_CQ_HCI +static void sdhci_msm_cmdq_init(struct sdhci_host *host, + struct platform_device *pdev) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + host->cq_host = cmdq_pltfm_init(pdev); + if (IS_ERR(host->cq_host)) + dev_dbg(&pdev->dev, "cmdq-pltfm init: failed: %ld\n", + PTR_ERR(host->cq_host)); + else + msm_host->mmc->caps2 |= MMC_CAP2_CMD_QUEUE; +} +#else +static void sdhci_msm_cmdq_init(struct sdhci_host *host, + struct platform_device *pdev) +{ + +} +#endif + static int sdhci_msm_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -3218,6 +3241,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__); } + sdhci_msm_cmdq_init(host, pdev); ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)\n", ret); From 6761d88e7ca29809794434fdf08fe7101ac3e617 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 29 May 2015 17:56:59 -0700 Subject: [PATCH 253/472] mmc: sdhci-msm: do not disable test-bus There is a hardware ring-buffer that logs the commands sent and doorbells rung. This is helpful for debugging in case of errors. This requires that the testbus be enabled. Hence don't disable testbus. Change-Id: I9c2fa984740aa9a0f8135d2196be6b3639ec22d1 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c5c2881556a7..dbcada1583bb 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2732,9 +2732,6 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) pr_info(" Test bus[%d to %d]: 0x%08x 0x%08x 0x%08x 0x%08x\n", i, i + 3, debug_reg[i], debug_reg[i+1], debug_reg[i+2], debug_reg[i+3]); - /* Disable test bus */ - writel_relaxed(~CORE_TESTBUS_ENA, msm_host->core_mem + - CORE_TESTBUS_CONFIG); if (host->is_crypto_en) { sdhci_msm_ice_get_status(host, &sts); pr_info("%s: ICE status %x\n", mmc_hostname(host->mmc), sts); @@ -2753,6 +2750,22 @@ void sdhci_msm_reset(struct sdhci_host *host, u8 mask) sdhci_reset(host, mask); } +static void sdhci_msm_clear_set_dumpregs(struct sdhci_host *host, bool set) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (set) { + writel_relaxed(CORE_TESTBUS_ENA, + msm_host->core_mem + CORE_TESTBUS_CONFIG); + } else { + u32 value; + value = readl_relaxed(msm_host->core_mem + CORE_TESTBUS_CONFIG); + value &= ~CORE_TESTBUS_ENA; + writel_relaxed(value, msm_host->core_mem + CORE_TESTBUS_CONFIG); + } +} + static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, .crypto_engine_reset = sdhci_msm_ice_reset, @@ -2769,6 +2782,7 @@ static struct sdhci_ops sdhci_msm_ops = { .enable_controller_clock = sdhci_msm_enable_controller_clock, .set_bus_width = sdhci_set_bus_width, .reset = sdhci_msm_reset, + .clear_set_dumpregs = sdhci_msm_clear_set_dumpregs, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, From 99b730eb035d2f1057f558928471c8b78d56371e Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 23 Apr 2015 09:55:43 +0530 Subject: [PATCH 254/472] mmc: block: add support for partition switch There could be a switch of partition while RPMB access. RPMB thread would then hand-off the control to mmc-cmdq-thread in the following state: - partition set to RPMB in card - CMDQ mode disabled in card - Controller in halt state When the next request is received, the following has to be done - switch partition to the current partition - enable CMDQ in card - unhalt controller Change-Id: I9eca350572fd88476dfee9642696a223c5cd7ada Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 47 +++++++++++++++++++++++++++++++++++++++- include/linux/mmc/card.h | 2 ++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index c258543e7db5..f175598ae56f 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2956,6 +2956,51 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) return 0; } +static inline int mmc_blk_cmdq_part_switch(struct mmc_card *card, + struct mmc_blk_data *md) +{ + struct mmc_blk_data *main_md = mmc_get_drvdata(card); + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; + u8 part_config = card->ext_csd.part_config; + + if ((main_md->part_curr == md->part_type) && + (card->part_curr == md->part_type)) + return 0; + + WARN_ON(!((card->host->caps2 & MMC_CAP2_CMD_QUEUE) && + card->ext_csd.cmdq_support && + (md->flags & MMC_BLK_CMD_QUEUE))); + + if (!test_bit(CMDQ_STATE_HALT, &ctx->curr_state)) + WARN_ON(mmc_cmdq_halt(host, true)); + + /* disable CQ mode in card */ + if (mmc_card_cmdq(card)) { + WARN_ON(mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CMDQ, 0, + card->ext_csd.generic_cmd6_time)); + mmc_card_clr_cmdq(card); + } + + part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; + part_config |= md->part_type; + + WARN_ON(mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PART_CONFIG, part_config, + card->ext_csd.part_time)); + + card->ext_csd.part_config = part_config; + card->part_curr = md->part_type; + + main_md->part_curr = md->part_type; + + WARN_ON(mmc_blk_cmdq_switch(card, md, true)); + WARN_ON(mmc_cmdq_halt(host, false)); + + return 0; +} + static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) { int ret; @@ -2964,7 +3009,7 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) unsigned int cmd_flags = req ? req->cmd_flags : 0; mmc_claim_host(card->host); - ret = mmc_blk_part_switch(card, md); + ret = mmc_blk_cmdq_part_switch(card, md); if (ret) { pr_err("%s: %s: partition switch failed %d\n", md->disk->disk_name, __func__, ret); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index e7fdaff68d7e..609a792e9e5c 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -594,6 +594,8 @@ static inline int mmc_card_broken_irq_polling(const struct mmc_card *c) #define mmc_card_id(c) (dev_name(&(c)->dev)) #define mmc_dev_to_card(d) container_of(d, struct mmc_card, dev) +#define mmc_get_drvdata(c) dev_get_drvdata(&(c)->dev) +#define mmc_set_drvdata(c,d) dev_set_drvdata(&(c)->dev, d) /* * MMC device driver (e.g., Flash card, I/O card...) From 444223d88485b28d8b4f02906c314c3081c8aa96 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 20 May 2015 16:52:04 +0530 Subject: [PATCH 255/472] mmc: block: Add error handling to command queue host On error, the CMDQ engine stops processing requests. It is then halted and error handled. The error have been categorized as below: 1. Command error a. time-out - invalidate all pending tags & requeue - reset both card & controller b. crc - end the error mrq - tune - unhalt 2. Data error a. time-out - invalidate all pending tags & requeue - reset both card and controller b. crc - end the error mrq - tune - unhalt 3. RED error This is device specific error and is not recoverable. The card and controller are reset in this case and all pending tags are invalidated and requeued. Change-Id: I791d05f6b31d8f9b35a56fe85007b320c14e8b46 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 122 ++++++++++++++++++++++++++++++-- drivers/mmc/card/queue.c | 11 +++ drivers/mmc/card/queue.h | 3 + drivers/mmc/core/core.c | 13 ++++ drivers/mmc/core/mmc_ops.c | 18 +++++ drivers/mmc/core/mmc_ops.h | 1 + drivers/mmc/host/cmdq_hci.c | 134 ++++++++++++++++++++++++++++++------ drivers/mmc/host/cmdq_hci.h | 15 +++- include/linux/mmc/core.h | 1 + include/linux/mmc/host.h | 7 ++ include/linux/mmc/mmc.h | 5 ++ 11 files changed, 301 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index f175598ae56f..fc98eea77fe7 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -66,6 +66,7 @@ MODULE_ALIAS("mmc:block"); #define MMC_BLK_TIMEOUT_MS (30 * 1000) /* 30 sec timeout */ #define MMC_SANITIZE_REQ_TIMEOUT 240000 #define MMC_EXTRACT_INDEX_FROM_ARG(x) ((x & 0x00FF0000) >> 16) +#define MMC_CMDQ_STOP_TIMEOUT_MS 100 #define mmc_req_rel_wr(req) ((req->cmd_flags & REQ_FUA) && \ (rq_data_dir(req) == WRITE)) @@ -80,6 +81,7 @@ MODULE_ALIAS("mmc:block"); stats->pack_stop_reason[reason]++; \ } while (0) +#define MAX_RETRIES 5 #define PCKD_TRGR_INIT_MEAN_POTEN 17 #define PCKD_TRGR_POTEN_LOWER_BOUND 5 #define PCKD_TRGR_URGENT_PENALTY 2 @@ -2713,6 +2715,103 @@ int mmc_blk_cmdq_issue_flush_rq(struct mmc_queue *mq, struct request *req) } EXPORT_SYMBOL(mmc_blk_cmdq_issue_flush_rq); +static void mmc_blk_cmdq_reset(struct mmc_host *host, bool clear_all) +{ + if (!host->cmdq_ops->reset) + return; + + if (!test_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state)) { + if (mmc_cmdq_halt(host, true)) { + pr_err("%s: halt failed\n", mmc_hostname(host)); + goto reset; + } + } + + if (clear_all) + mmc_cmdq_discard_queue(host, 0); +reset: + mmc_hw_reset(host); + host->cmdq_ops->reset(host, true); + clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); +} + +static void mmc_blk_cmdq_err(struct mmc_queue *mq) +{ + int err; + int retry = 0; + int gen_err; + u32 status; + + struct mmc_host *host = mq->card->host; + struct mmc_request *mrq = host->err_mrq; + struct mmc_card *card = mq->card; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + + err = mmc_cmdq_halt(host, true); + if (err) { + pr_err("halt: failed: %d\n", err); + goto reset; + } + + /* RED error - Fatal: requires reset */ + if (mrq->cmdq_req->resp_err) { + pr_crit("%s: Response error detected: Device in bad state\n", + mmc_hostname(host)); + blk_end_request_all(mrq->req, -EIO); + goto reset; + } + + if (mrq->data->error) { + blk_end_request_all(mrq->req, mrq->data->error); + for (; retry < MAX_RETRIES; retry++) { + err = get_card_status(card, &status, 0); + if (!err) + break; + } + + if (err) { + pr_err("%s: No response from card !!!\n", + mmc_hostname(host)); + goto reset; + } + + if (R1_CURRENT_STATE(status) == R1_STATE_DATA || + R1_CURRENT_STATE(status) == R1_STATE_RCV) { + err = send_stop(card, MMC_CMDQ_STOP_TIMEOUT_MS, + mrq->req, &gen_err, &status); + if (err) { + pr_err("%s: error %d sending stop (%d) command\n", + mrq->req->rq_disk->disk_name, + err, status); + goto reset; + } + } + + if (mmc_cmdq_discard_queue(host, mrq->req->tag)) + goto reset; + else + goto unhalt; + } + + /* DCMD commands */ + if (mrq->cmd->error) + blk_end_request_all(mrq->req, mrq->cmd->error); + +reset: + spin_lock_irq(mq->queue->queue_lock); + blk_queue_invalidate_tags(mrq->req->q); + spin_unlock_irq(mq->queue->queue_lock); + mmc_blk_cmdq_reset(host, true); + goto out; + +unhalt: + mmc_cmdq_halt(host, false); + +out: + if (test_and_clear_bit(0, &ctx_info->req_starved)) + blk_run_queue(mrq->req->q); +} + /* invoked by block layer in softirq context */ void mmc_blk_cmdq_complete_rq(struct request *rq) { @@ -2729,26 +2828,36 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) else if (mrq->data && mrq->data->error) err = mrq->data->error; + /* clear pending request */ + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->active_reqs)); + mmc_cmdq_post_req(host, mrq, err); if (err) { pr_err("%s: %s: txfr error: %d\n", mmc_hostname(mrq->host), __func__, err); - set_bit(CMDQ_STATE_ERR, &ctx_info->curr_state); - WARN_ON(1); + if (test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) { + pr_err("%s: CQ in error state, ending current req: %d\n", + __func__, err); + blk_end_request_all(rq, err); + } else { + set_bit(CMDQ_STATE_ERR, &ctx_info->curr_state); + schedule_work(&mq->cmdq_err_work); + } + goto out; } - BUG_ON(!test_and_clear_bit(cmdq_req->tag, - &ctx_info->active_reqs)); if (cmdq_req->cmdq_req_flags & DCMD) { clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); - blk_end_request_all(rq, 0); + blk_end_request_all(rq, err); goto out; } blk_end_request(rq, err, cmdq_req->data.bytes_xfered); out: - if (test_and_clear_bit(0, &ctx_info->req_starved)) + if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state) && + test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); mmc_release_host(host); return; @@ -3209,6 +3318,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, md->flags |= MMC_BLK_CMD_QUEUE; md->queue.cmdq_complete_fn = mmc_blk_cmdq_complete_rq; md->queue.cmdq_issue_fn = mmc_blk_cmdq_issue_rq; + md->queue.cmdq_error_fn = mmc_blk_cmdq_err; } if (mmc_card_mmc(card) && !card->cmdq_init && diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 1a441b4c90a4..676e080550f8 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -569,6 +569,14 @@ static void mmc_cmdq_softirq_done(struct request *rq) mq->cmdq_complete_fn(rq); } +static void mmc_cmdq_error_work(struct work_struct *work) +{ + struct mmc_queue *mq = container_of(work, struct mmc_queue, + cmdq_err_work); + + mq->cmdq_error_fn(mq); +} + int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) { int i, ret = 0; @@ -609,7 +617,10 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) } blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); + INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work); + card->cmdq_init = true; + goto out; free_mqrq_sg: diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 2795ec5e3062..c4acab9bff27 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -57,6 +57,7 @@ struct mmc_queue { int (*cmdq_issue_fn)(struct mmc_queue *, struct request *); void (*cmdq_complete_fn)(struct request *); + void (*cmdq_error_fn)(struct mmc_queue *); void *data; struct request_queue *queue; struct mmc_queue_req mqrq[2]; @@ -67,6 +68,8 @@ struct mmc_queue { int num_of_potential_packed_wr_reqs; int num_wr_reqs_to_start_packing; bool no_pack_for_random; + struct work_struct cmdq_err_work; + int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); }; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index c1cc8c96da34..21d3968626b4 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1193,6 +1193,19 @@ static void mmc_post_req(struct mmc_host *host, struct mmc_request *mrq, } } +/** + * mmc_cmdq_discard_card_queue - discard the task[s] in the device + * @host: host instance + * @tasks: mask of tasks to be knocked off + * 0: remove all queued tasks + */ +int mmc_cmdq_discard_queue(struct mmc_host *host, u32 tasks) +{ + return mmc_discard_queue(host, tasks); +} +EXPORT_SYMBOL(mmc_cmdq_discard_queue); + + /** * mmc_cmdq_post_req - post process of a completed request * @host: host instance diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 85ddc4007480..ec0e0095e03a 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -834,3 +834,21 @@ int mmc_can_ext_csd(struct mmc_card *card) { return (card && card->csd.mmca_vsn > CSD_SPEC_VER_3); } + +int mmc_discard_queue(struct mmc_host *host, u32 tasks) +{ + struct mmc_command cmd = {0}; + + cmd.opcode = MMC_CMDQ_TASK_MGMT; + if (tasks) { + cmd.arg = DISCARD_TASK; + cmd.arg |= (tasks << 16); + } else { + cmd.arg = DISCARD_QUEUE; + } + + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + + return mmc_wait_for_cmd(host, &cmd, 0); +} +EXPORT_SYMBOL(mmc_discard_queue); diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index f1b8e81aaa28..1eea7bd51367 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -27,6 +27,7 @@ int mmc_spi_set_crc(struct mmc_host *host, int use_crc); int mmc_bus_test(struct mmc_card *card, u8 bus_width); int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status); int mmc_can_ext_csd(struct mmc_card *card); +int mmc_discard_queue(struct mmc_host *host, u32 tasks); int mmc_switch_status_error(struct mmc_host *host, u32 status); int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, unsigned int timeout_ms, bool use_busy_signal, bool send_status, diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 64be0ceda311..affc92756c0b 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -32,6 +32,12 @@ /* 1 sec */ #define HALT_TIMEOUT_MS 1000 +static inline struct mmc_request *get_req_by_tag(struct cmdq_host *cq_host, + unsigned int tag) +{ + return cq_host->mrq_slot[tag]; +} + static inline u8 *get_desc(struct cmdq_host *cq_host, u8 tag) { return cq_host->desc_base + (tag * cq_host->slot_sz); @@ -122,43 +128,43 @@ static void cmdq_dumpregs(struct cmdq_host *cq_host) { struct mmc_host *mmc = cq_host->mmc; - pr_info(DRV_NAME ": ========== REGISTER DUMP (%s)==========\n", + pr_err(DRV_NAME ": ========== REGISTER DUMP (%s)==========\n", mmc_hostname(mmc)); - pr_info(DRV_NAME ": Caps: 0x%08x | Version: 0x%08x\n", + pr_err(DRV_NAME ": Caps: 0x%08x | Version: 0x%08x\n", cmdq_readl(cq_host, CQCAP), cmdq_readl(cq_host, CQVER)); - pr_info(DRV_NAME ": Queing config: 0x%08x | Queue Ctrl: 0x%08x\n", + pr_err(DRV_NAME ": Queing config: 0x%08x | Queue Ctrl: 0x%08x\n", cmdq_readl(cq_host, CQCFG), cmdq_readl(cq_host, CQCTL)); - pr_info(DRV_NAME ": Int stat: 0x%08x | Int enab: 0x%08x\n", + pr_err(DRV_NAME ": Int stat: 0x%08x | Int enab: 0x%08x\n", cmdq_readl(cq_host, CQIS), cmdq_readl(cq_host, CQISTE)); - pr_info(DRV_NAME ": Int sig: 0x%08x | Int Coal: 0x%08x\n", + pr_err(DRV_NAME ": Int sig: 0x%08x | Int Coal: 0x%08x\n", cmdq_readl(cq_host, CQISGE), cmdq_readl(cq_host, CQIC)); - pr_info(DRV_NAME ": TDL base: 0x%08x | TDL up32: 0x%08x\n", + pr_err(DRV_NAME ": TDL base: 0x%08x | TDL up32: 0x%08x\n", cmdq_readl(cq_host, CQTDLBA), cmdq_readl(cq_host, CQTDLBAU)); - pr_info(DRV_NAME ": Doorbell: 0x%08x | Comp Notif: 0x%08x\n", + pr_err(DRV_NAME ": Doorbell: 0x%08x | Comp Notif: 0x%08x\n", cmdq_readl(cq_host, CQTDBR), cmdq_readl(cq_host, CQTCN)); - pr_info(DRV_NAME ": Dev queue: 0x%08x | Dev Pend: 0x%08x\n", + pr_err(DRV_NAME ": Dev queue: 0x%08x | Dev Pend: 0x%08x\n", cmdq_readl(cq_host, CQDQS), cmdq_readl(cq_host, CQDPT)); - pr_info(DRV_NAME ": Task clr: 0x%08x | Send stat 1: 0x%08x\n", + pr_err(DRV_NAME ": Task clr: 0x%08x | Send stat 1: 0x%08x\n", cmdq_readl(cq_host, CQTCLR), cmdq_readl(cq_host, CQSSC1)); - pr_info(DRV_NAME ": Send stat 2: 0x%08x | DCMD resp: 0x%08x\n", + pr_err(DRV_NAME ": Send stat 2: 0x%08x | DCMD resp: 0x%08x\n", cmdq_readl(cq_host, CQSSC2), cmdq_readl(cq_host, CQCRDCT)); - pr_info(DRV_NAME ": Resp err mask: 0x%08x | Task err: 0x%08x\n", + pr_err(DRV_NAME ": Resp err mask: 0x%08x | Task err: 0x%08x\n", cmdq_readl(cq_host, CQRMEM), cmdq_readl(cq_host, CQTERRI)); - pr_info(DRV_NAME ": Resp idx 0x%08x | Resp arg: 0x%08x\n", + pr_err(DRV_NAME ": Resp idx 0x%08x | Resp arg: 0x%08x\n", cmdq_readl(cq_host, CQCRI), cmdq_readl(cq_host, CQCRA)); - pr_info(DRV_NAME ": ===========================================\n"); + pr_err(DRV_NAME ": ===========================================\n"); cmdq_dump_debug_ram(cq_host); if (cq_host->ops->dump_vendor_regs) @@ -296,7 +302,6 @@ static int cmdq_enable(struct mmc_host *mmc) CQTDLBA); cmdq_writel(cq_host, upper_32_bits(cq_host->desc_dma_base), CQTDLBAU); - cmdq_dumpregs(cq_host); } /* @@ -347,6 +352,49 @@ static void cmdq_disable(struct mmc_host *mmc, bool soft) cq_host->enabled = false; } +static void cmdq_reset(struct mmc_host *mmc, bool soft) +{ + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + unsigned int cqcfg; + unsigned int tdlba; + unsigned int tdlbau; + unsigned int rca; + int ret; + + cqcfg = cmdq_readl(cq_host, CQCFG); + tdlba = cmdq_readl(cq_host, CQTDLBA); + tdlbau = cmdq_readl(cq_host, CQTDLBAU); + rca = cmdq_readl(cq_host, CQSSC2); + + cmdq_disable(mmc, true); + + if (cq_host->ops->reset) { + ret = cq_host->ops->reset(mmc); + if (ret) { + pr_crit("%s: reset CMDQ controller: failed\n", + mmc_hostname(mmc)); + BUG(); + } + } + + cmdq_writel(cq_host, tdlba, CQTDLBA); + cmdq_writel(cq_host, tdlbau, CQTDLBAU); + + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, true); + + cmdq_clear_set_irqs(cq_host, 0x0, CQ_INT_ALL); + + /* cq_host would use this rca to address the card */ + cmdq_writel(cq_host, rca, CQSSC2); + + /* ensure the writes are done before enabling CQE */ + mb(); + + cmdq_writel(cq_host, cqcfg, CQCFG); + cq_host->enabled = true; +} + static void cmdq_prep_task_desc(struct mmc_request *mrq, u64 *data, bool intr, bool qbr) { @@ -539,19 +587,66 @@ static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) struct mmc_request *mrq; struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); - mrq = cq_host->mrq_slot[tag]; + mrq = get_req_by_tag(cq_host, tag); mrq->done(mrq); } -irqreturn_t cmdq_irq(struct mmc_host *mmc, u32 intmask) +irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) { u32 status; unsigned long tag = 0, comp_status; struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + unsigned long err_info = 0; + struct mmc_request *mrq; status = cmdq_readl(cq_host, CQIS); cmdq_writel(cq_host, status, CQIS); + if (!status && !err) + return IRQ_NONE; + + if (err || (status & CQIS_RED)) { + err_info = cmdq_readl(cq_host, CQTERRI); + pr_err("%s: err: %d status: 0x%08x task-err-info (0x%08lx)\n", + mmc_hostname(mmc), err, status, err_info); + + cmdq_dumpregs(cq_host); + + if (err_info & CQ_RMEFV) { + tag = GET_CMD_ERR_TAG(err_info); + pr_err("%s: CMD err tag: %lu\n", __func__, tag); + + mrq = get_req_by_tag(cq_host, tag); + /* CMD44/45/46/47 will not have a valid cmd */ + if (mrq->cmd) + mrq->cmd->error = err; + else + mrq->data->error = err; + } else if (err_info & CQ_DTEFV) { + tag = GET_DAT_ERR_TAG(err_info); + pr_err("%s: Dat err tag: %lu\n", __func__, tag); + mrq = get_req_by_tag(cq_host, tag); + mrq->data->error = err; + } + + tag = 0; + /* + * CQE detected a reponse error from device + * In most cases, this would require a reset. + */ + if (status & CQIS_RED) { + mrq->cmdq_req->resp_err = true; + pr_err("%s: Response error (0x%08x) from card !!!", + mmc_hostname(mmc), status); + } else { + mrq->cmdq_req->resp_idx = cmdq_readl(cq_host, CQCRI); + mrq->cmdq_req->resp_arg = cmdq_readl(cq_host, CQCRA); + } + + mmc->err_mrq = mrq; + cmdq_finish_data(mmc, tag); + } + if (status & CQIS_TCC) { /* read QCTCN and complete the request */ comp_status = cmdq_readl(cq_host, CQTCN); @@ -567,12 +662,6 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, u32 intmask) cmdq_writel(cq_host, comp_status, CQTCN); } - if (status & CQIS_RED) { - /* task response has an error */ - pr_err("%s: RED error %d !!!\n", mmc_hostname(mmc), status); - cmdq_dumpregs(cq_host); - } - if (status & CQIS_HAC) { /* halt is completed, wakeup waiting thread */ complete(&cq_host->halt_comp); @@ -632,6 +721,7 @@ static const struct mmc_cmdq_host_ops cmdq_host_ops = { .request = cmdq_request, .post_req = cmdq_post_req, .halt = cmdq_halt, + .reset = cmdq_reset, }; struct cmdq_host *cmdq_pltfm_init(struct platform_device *pdev) diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index e7f5a158b8a2..6786b38f953c 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -92,6 +92,17 @@ /* task error info */ #define CQTERRI 0x54 +/* CQTERRI bit fields */ +#define CQ_RMECI 0x1F +#define CQ_RMETI (0x1F << 8) +#define CQ_RMEFV (1 << 15) +#define CQ_DTECI (0x3F << 16) +#define CQ_DTETI (0x1F << 24) +#define CQ_DTEFV (1 << 31) + +#define GET_CMD_ERR_TAG(__r__) ((__r__ & CQ_RMETI) >> 8) +#define GET_DAT_ERR_TAG(__r__) ((__r__ & CQ_DTETI) >> 24) + /* command response index */ #define CQCRI 0x58 @@ -106,6 +117,7 @@ #define CQ_CMD_DBG_RAM_WA 0x198 #define CQ_CMD_DBG_RAM_OL 0x19C + /* attribute fields */ #define VALID(x) ((x & 1) << 0) #define END(x) ((x & 1) << 1) @@ -186,6 +198,7 @@ struct cmdq_host_ops { void (*write_l)(struct cmdq_host *host, u32 val, int reg); u32 (*read_l)(struct cmdq_host *host, int reg); void (*clear_set_dumpregs)(struct mmc_host *mmc, bool set); + int (*reset)(struct mmc_host *mmc); }; static inline void cmdq_writel(struct cmdq_host *host, u32 val, int reg) @@ -204,7 +217,7 @@ static inline u32 cmdq_readl(struct cmdq_host *host, int reg) return readl_relaxed(host->mmio + reg); } -extern irqreturn_t cmdq_irq(struct mmc_host *mmc, u32 intmask); +extern irqreturn_t cmdq_irq(struct mmc_host *mmc, int err); extern int cmdq_init(struct cmdq_host *cq_host, struct mmc_host *mmc, bool dma64); extern struct cmdq_host *cmdq_pltfm_init(struct platform_device *pdev); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 8bf192d6a7d4..59d9196250c1 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -116,6 +116,7 @@ struct mmc_card; struct mmc_async_req; struct mmc_cmdq_req; +extern int mmc_cmdq_discard_queue(struct mmc_host *host, u32 tasks); extern int mmc_cmdq_halt(struct mmc_host *host, bool enable); extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 4e555b99223e..18d041f85f95 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -95,6 +95,7 @@ struct mmc_cmdq_host_ops { void (*post_req)(struct mmc_host *host, struct mmc_request *mrq, int err); int (*halt)(struct mmc_host *host, bool halt); + void (*reset)(struct mmc_host *host, bool soft); }; struct mmc_host_ops { @@ -193,6 +194,11 @@ struct mmc_cmdq_req { #define DAT_TAG (1 << 5) #define FORCED_PRG (1 << 6) unsigned int cmdq_req_flags; + + unsigned int resp_idx; + unsigned int resp_arg; + unsigned int dev_pend_tasks; + bool resp_err; int tag; /* used for command queuing */ u8 ctx_id; }; @@ -549,6 +555,7 @@ struct mmc_host { * controller. */ void *cmdq_private; + struct mmc_request *err_mrq; unsigned long private[0] ____cacheline_aligned; }; diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 71b5c75a0995..07b04c6e5c64 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -26,6 +26,11 @@ #include +/* class 11 */ +#define MMC_CMDQ_TASK_MGMT 48 /* ac [31:0] task ID R1b */ +#define DISCARD_QUEUE 0x1 +#define DISCARD_TASK 0x2 + static inline bool mmc_op_multi(u32 opcode) { return opcode == MMC_WRITE_MULTIPLE_BLOCK || From f446730be194afa78404fd8d1e474305218a000c Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 27 Feb 2015 00:02:02 +0530 Subject: [PATCH 256/472] mmc: sdhci: propagate error to command queue engine eMMC controller detects error, generates error interrupt and notifies the CQE. Hence, parse the correct error and propagate it to CQE handler for further processing. Command queue interrupt handler may return IRQ_NONE or IRQ_HANDLED. Check for the return value and either proceed or return from handler. Change-Id: I29c7b22848d3d728a79ba215f8d2f594d19fba63 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 1522405f5567..d964ced872f7 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2920,9 +2920,37 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } #ifdef CONFIG_MMC_CQ_HCI +static int sdhci_get_cmd_err(u32 intmask) +{ + if (intmask & SDHCI_INT_TIMEOUT) + return -ETIMEDOUT; + else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | + SDHCI_INT_INDEX)) + return -EILSEQ; + return 0; +} + +static int sdhci_get_data_err(u32 intmask) +{ + if (intmask & SDHCI_INT_DATA_TIMEOUT) + return -ETIMEDOUT; + else if (intmask & (SDHCI_INT_DATA_END_BIT | SDHCI_INT_DATA_CRC)) + return -EILSEQ; + else if (intmask & SDHCI_INT_ADMA_ERROR) + return -EIO; + return 0; +} + static irqreturn_t sdhci_cmdq_irq(struct mmc_host *mmc, u32 intmask) { - return cmdq_irq(mmc, intmask); + int err = 0; + + if (intmask & SDHCI_INT_CMD_MASK) + err = sdhci_get_cmd_err(intmask); + else if (intmask & SDHCI_INT_DATA_MASK) + err = sdhci_get_data_err(intmask); + + return cmdq_irq(mmc, err); } #else @@ -2960,7 +2988,8 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) mmc_hostname(host->mmc), intmask); result = sdhci_cmdq_irq(host->mmc, intmask); - goto out; + if (result == IRQ_HANDLED) + goto out; } if (intmask & SDHCI_INT_AUTO_CMD_ERR) From eb2587ce536bffd12bc2cd2a59e3331ba34d4d68 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 2 Mar 2015 23:14:05 +0530 Subject: [PATCH 257/472] mmc: queue: add timeout capability to requests Individual requests can have timeouts defined. The default timeout is set to 120 seconds. On a timeout, the block layer notifies the driver and error can be handled thereafter. Change-Id: Ifcd690411770ab266c12a01bb0993f92b0f18bc6 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 21 +++++++++++++++++++++ drivers/mmc/card/queue.c | 12 ++++++++++++ drivers/mmc/card/queue.h | 1 + drivers/mmc/host/cmdq_hci.c | 8 ++++++++ include/linux/mmc/host.h | 1 + 5 files changed, 43 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index fc98eea77fe7..daccedcb7f58 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2735,6 +2735,26 @@ reset: clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); } +static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) +{ + struct mmc_queue *mq = req->q->queuedata; + struct mmc_host *host = mq->card->host; + struct mmc_queue_req *mq_rq = req->special; + struct mmc_request *mrq = &mq_rq->cmdq_req.mrq; + struct mmc_cmdq_req *cmdq_req = &mq_rq->cmdq_req; + + host->cmdq_ops->dumpstate(host); + if (cmdq_req->cmdq_req_flags & DCMD) + mrq->cmd->error = -ETIMEDOUT; + else + mrq->data->error = -ETIMEDOUT; + + host->err_mrq = mrq; + mrq->done(mrq); + + return BLK_EH_NOT_HANDLED; +} + static void mmc_blk_cmdq_err(struct mmc_queue *mq) { int err; @@ -3319,6 +3339,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, md->queue.cmdq_complete_fn = mmc_blk_cmdq_complete_rq; md->queue.cmdq_issue_fn = mmc_blk_cmdq_issue_rq; md->queue.cmdq_error_fn = mmc_blk_cmdq_err; + md->queue.cmdq_req_timed_out = mmc_blk_cmdq_req_timed_out; } if (mmc_card_mmc(card) && !card->cmdq_init && diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 676e080550f8..be417461f44b 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -577,6 +577,16 @@ static void mmc_cmdq_error_work(struct work_struct *work) mq->cmdq_error_fn(mq); } +enum blk_eh_timer_return mmc_cmdq_rq_timed_out(struct request *req) +{ + struct mmc_queue *mq = req->q->queuedata; + + pr_err("%s: request with tag: %d flags: 0x%llx timed out\n", + mmc_hostname(mq->card->host), req->tag, req->cmd_flags); + + return mq->cmdq_req_timed_out(req); +} + int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) { int i, ret = 0; @@ -619,6 +629,8 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work); + blk_queue_rq_timed_out(mq->queue, mmc_cmdq_rq_timed_out); + blk_queue_rq_timeout(mq->queue, 120 * HZ); card->cmdq_init = true; goto out; diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index c4acab9bff27..388f099a06f5 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -58,6 +58,7 @@ struct mmc_queue { struct request *); void (*cmdq_complete_fn)(struct request *); void (*cmdq_error_fn)(struct mmc_queue *); + enum blk_eh_timer_return (*cmdq_req_timed_out)(struct request *); void *data; struct request_queue *queue; struct mmc_queue_req mqrq[2]; diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index affc92756c0b..08ba2546cf37 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -715,6 +715,13 @@ static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, } } +static void cmdq_dumpstate(struct mmc_host *mmc) +{ + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + + cmdq_dumpregs(cq_host); +} + static const struct mmc_cmdq_host_ops cmdq_host_ops = { .enable = cmdq_enable, .disable = cmdq_disable, @@ -722,6 +729,7 @@ static const struct mmc_cmdq_host_ops cmdq_host_ops = { .post_req = cmdq_post_req, .halt = cmdq_halt, .reset = cmdq_reset, + .dumpstate = cmdq_dumpstate, }; struct cmdq_host *cmdq_pltfm_init(struct platform_device *pdev) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 18d041f85f95..6c6a699abea9 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -96,6 +96,7 @@ struct mmc_cmdq_host_ops { int err); int (*halt)(struct mmc_host *host, bool halt); void (*reset)(struct mmc_host *host, bool soft); + void (*dumpstate)(struct mmc_host *host); }; struct mmc_host_ops { From d10ca4a6adbf5ad663f565a7f7fe0356647beab0 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 4 Mar 2015 14:39:01 -0800 Subject: [PATCH 258/472] mmc: sdhci-msm: increase number of testbuses to 60 Dump out testbus values till 60. This would facilitate triaging command-queue issues. Change-Id: Icac865629b21ce0207c46c1907cf25401d6e8339 Signed-off-by: Venkat Gopalakrishnan Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index dbcada1583bb..247650e96b6f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2681,7 +2681,7 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, } -#define MAX_TEST_BUS 20 +#define MAX_TEST_BUS 60 void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) { @@ -2716,7 +2716,7 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) * to select test bus 14, write 0x1E to CORE_TESTBUS_CONFIG register * i.e., tbsel2[7:4] = 0001, tbsel[2:0] = 110. */ - for (tbsel2 = 0; tbsel2 < 3; tbsel2++) { + for (tbsel2 = 0; tbsel2 < 7; tbsel2++) { for (tbsel = 0; tbsel < 8; tbsel++) { if (index >= MAX_TEST_BUS) break; From 5bec5e8420699f14d23f0e9589ff10f916dc213b Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 23 Apr 2015 16:01:57 +0530 Subject: [PATCH 259/472] mmc: core: fix shutdown in cmdq mode During the shutdown process, - Stop the queue - Flush all the pending requests - Halt the Controller - Put the card in legacy mode Change-Id: I5fe4e998bc04bfd8f50de63f3beae3cb25f1c8ba Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 32 ++++++++++++++++++++++++++++++++ drivers/mmc/card/queue.c | 29 +++++++++++++++++++++++++++++ drivers/mmc/card/queue.h | 2 ++ 3 files changed, 63 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index daccedcb7f58..4a0dba010cfd 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2735,6 +2735,34 @@ reset: clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); } +static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) +{ + int err; + struct mmc_card *card = mq->card; + struct mmc_host *host = card->host; + + err = mmc_cmdq_halt(host, true); + if (err) { + pr_err("%s: halt: failed: %d\n", __func__, err); + return; + } + + mmc_claim_host(card->host); + /* disable CQ mode in card */ + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CMDQ, 0, + card->ext_csd.generic_cmd6_time); + if (err) { + pr_err("%s: failed to switch card to legacy mode: %d\n", + __func__, err); + goto out; + } else { + host->card->cmdq_init = false; + } +out: + mmc_release_host(card->host); +} + static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) { struct mmc_queue *mq = req->q->queuedata; @@ -2880,6 +2908,9 @@ out: test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); mmc_release_host(host); + + if (blk_queue_stopped(mq->queue) && !ctx_info->active_reqs) + complete(&mq->cmdq_shutdown_complete); return; } @@ -3340,6 +3371,7 @@ static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card, md->queue.cmdq_issue_fn = mmc_blk_cmdq_issue_rq; md->queue.cmdq_error_fn = mmc_blk_cmdq_err; md->queue.cmdq_req_timed_out = mmc_blk_cmdq_req_timed_out; + md->queue.cmdq_shutdown = mmc_blk_cmdq_shutdown; } if (mmc_card_mmc(card) && !card->cmdq_init && diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index be417461f44b..d47b18c20e71 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -628,6 +628,7 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work); + init_completion(&mq->cmdq_shutdown_complete); blk_queue_rq_timed_out(mq->queue, mmc_cmdq_rq_timed_out); blk_queue_rq_timeout(mq->queue, 120 * HZ); @@ -659,6 +660,12 @@ void mmc_cmdq_clean(struct mmc_queue *mq, struct mmc_card *card) mq->mqrq_cmdq = NULL; } +static void mmc_wait_for_pending_reqs(struct mmc_queue *mq) +{ + wait_for_completion(&mq->cmdq_shutdown_complete); + mq->cmdq_shutdown(mq); +} + /** * mmc_queue_suspend - suspend a MMC request queue * @mq: MMC queue to suspend @@ -673,6 +680,27 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) struct request_queue *q = mq->queue; unsigned long flags; int rc = 0; + struct mmc_card *card = mq->card; + + if (card->cmdq_init) { + struct mmc_host *host = card->host; + unsigned long flags; + + spin_lock_irqsave(q->queue_lock, flags); + blk_stop_queue(q); + spin_unlock_irqrestore(q->queue_lock, flags); + + if (host->cmdq_ctx.active_reqs) { + if (!wait) + rc = -EBUSY; + else + mmc_wait_for_pending_reqs(mq); + } else { + mq->cmdq_shutdown(mq); + } + + goto out; + } if (!(test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags))) { spin_lock_irqsave(q->queue_lock, flags); @@ -695,6 +723,7 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) rc = 0; } } +out: return rc; } diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 388f099a06f5..ab89102a4baa 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -70,9 +70,11 @@ struct mmc_queue { int num_wr_reqs_to_start_packing; bool no_pack_for_random; struct work_struct cmdq_err_work; + struct completion cmdq_shutdown_complete; int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); + void (*cmdq_shutdown)(struct mmc_queue *); }; extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *, From 4d105602c48b145b4871ec951d2d51e587033e26 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 1 May 2015 14:22:16 +0530 Subject: [PATCH 260/472] mmc: queue: issue requests when dcmd is in progress Requests can still be issued when dcmd is in progress. Only when discard is in progress, should the requests not be issued. Change-Id: Ief70cf2f86e9eb3817f8a390626be8433180ed87 Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/queue.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index d47b18c20e71..98afbee58302 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -56,11 +56,14 @@ static int mmc_prep_request(struct request_queue *q, struct request *req) } static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, - struct mmc_cmdq_context_info *ctx) + struct mmc_cmdq_context_info *ctx, + struct request *req) + { - if (test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state) || - mmc_host_halt(host) || - test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { + if (((req->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && + test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) || + mmc_host_halt(host) || + test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { pr_debug("%s: %s: skip pulling reqs: state: %lu\n", mmc_hostname(host), __func__, ctx->curr_state); return false; @@ -87,11 +90,6 @@ static int mmc_cmdq_thread(void *d) while (1) { int ret = 0; - if (!mmc_cmdq_should_pull_reqs(host, ctx)) { - test_and_set_bit(0, &ctx->req_starved); - schedule(); - } - spin_lock_irqsave(q->queue_lock, flags); req = blk_peek_request(q); if (req) { @@ -101,6 +99,16 @@ static int mmc_cmdq_thread(void *d) test_and_set_bit(0, &ctx->req_starved); schedule(); } else { + if (!mmc_cmdq_should_pull_reqs(host, ctx, + req)) { + spin_lock_irqsave(q->queue_lock, flags); + blk_requeue_request(q, req); + spin_unlock_irqrestore(q->queue_lock, + flags); + test_and_set_bit(0, &ctx->req_starved); + schedule(); + continue; + } ret = mq->cmdq_issue_fn(mq, req); if (ret) { pr_err("%s: failed (%d) to issue req, requeue\n", From b099847e11f8229b68c307bae6198f65ca556973 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 18 May 2015 10:55:01 +0530 Subject: [PATCH 261/472] mmc: host: Fix spinbug in performance sysfs nodes In CMDQ, the mmc_release_host runs in softirq context. There's a potential race condition between show/set_perf since it doesn't disable pre-emption. Change this to disable preemption. Change-Id: I20918a459e8b35ac666971b8ebf179f44aa9c40f Signed-off-by: Asutosh Das Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/core/host.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 2871f21803bd..e30261c1d9d2 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -776,9 +776,9 @@ show_perf(struct device *dev, struct device_attribute *attr, char *buf) { struct mmc_host *host = cls_dev_to_mmc_host(dev); int64_t rtime_drv, wtime_drv; - unsigned long rbytes_drv, wbytes_drv; + unsigned long rbytes_drv, wbytes_drv, flags; - spin_lock(&host->lock); + spin_lock_irqsave(&host->lock, flags); rbytes_drv = host->perf.rbytes_drv; wbytes_drv = host->perf.wbytes_drv; @@ -786,7 +786,7 @@ show_perf(struct device *dev, struct device_attribute *attr, char *buf) rtime_drv = ktime_to_us(host->perf.rtime_drv); wtime_drv = ktime_to_us(host->perf.wtime_drv); - spin_unlock(&host->lock); + spin_unlock_irqrestore(&host->lock, flags); return snprintf(buf, PAGE_SIZE, "Write performance at driver Level:" "%lu bytes in %lld microseconds\n" @@ -802,16 +802,17 @@ set_perf(struct device *dev, struct device_attribute *attr, { struct mmc_host *host = cls_dev_to_mmc_host(dev); int64_t value; + unsigned long flags; sscanf(buf, "%lld", &value); - spin_lock(&host->lock); + spin_lock_irqsave(&host->lock, flags); if (!value) { memset(&host->perf, 0, sizeof(host->perf)); host->perf_enable = false; } else { host->perf_enable = true; } - spin_unlock(&host->lock); + spin_unlock_irqrestore(&host->lock, flags); return count; } From 1c6a2272ff8b20a4b342580eeac056dfd6f52b84 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 29 May 2015 13:27:38 +0530 Subject: [PATCH 262/472] mmc: cmdq_hci: Fix timing and response type for busy commands The response type and command timing for busy commands are set incorrectly due to wrong cmd->flags check. Correct this to check for MMC_RSP_BUSY flag. Change-Id: I4498c302914a81bf19f61d20b30bd4426e21d3d0 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 08ba2546cf37..8a48d73fcacd 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -508,7 +508,7 @@ static void cmdq_prep_dcmd_desc(struct mmc_host *mmc, resp_type = 0x0; timing = 0x1; } else { - if (mrq->cmd->flags & MMC_RSP_R1B) { + if (mrq->cmd->flags & MMC_RSP_BUSY) { resp_type = 0x3; timing = 0x0; } else { From 9f3305d698337bbd497c8815bc05b20c0c6fb47b Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 6 May 2015 10:41:16 +0530 Subject: [PATCH 263/472] mmc: cmdq_hci: Add Inline Crypto Engine (ICE) support Add changes to configure ICE for data encryption/decryption using CMDQ. Change-Id: I9a10d18d617102916526e994e9f9d22d2aa5446b Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 9 +++++++++ drivers/mmc/host/cmdq_hci.h | 2 ++ drivers/mmc/host/sdhci.c | 17 ++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 8a48d73fcacd..00a065cfab8d 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -557,6 +557,15 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) return 0; } + if (cq_host->ops->crypto_cfg) { + err = cq_host->ops->crypto_cfg(mmc, mrq, tag); + if (err) { + pr_err("%s: failed to configure crypto: err %d tag %d\n", + mmc_hostname(mmc), err, tag); + goto out; + } + } + task_desc = (__le64 __force *)get_desc(cq_host, tag); cmdq_prep_task_desc(mrq, &data, 1, diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index 6786b38f953c..05c622c057e8 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -199,6 +199,8 @@ struct cmdq_host_ops { u32 (*read_l)(struct cmdq_host *host, int reg); void (*clear_set_dumpregs)(struct mmc_host *mmc, bool set); int (*reset)(struct mmc_host *mmc); + int (*crypto_cfg)(struct mmc_host *mmc, struct mmc_request *mrq, + u32 slot); }; static inline void cmdq_writel(struct cmdq_host *host, u32 val, int reg) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d964ced872f7..6292dcddd1ef 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3413,6 +3413,16 @@ static void sdhci_cmdq_clear_set_dumpregs(struct mmc_host *mmc, bool set) if (host->ops->clear_set_dumpregs) host->ops->clear_set_dumpregs(host, set); } +static int sdhci_cmdq_crypto_cfg(struct mmc_host *mmc, + struct mmc_request *mrq, u32 slot) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (!host->is_crypto_en) + return 0; + + return sdhci_crypto_cfg(host, mrq, slot); +} #else static void sdhci_cmdq_clear_set_irqs(struct mmc_host *mmc, bool clear) { @@ -3444,7 +3454,11 @@ static void sdhci_cmdq_clear_set_dumpregs(struct mmc_host *mmc, bool set) { } - +static int sdhci_cmdq_crypto_cfg(struct mmc_host *mmc, + struct mmc_request *mrq, u32 slot) +{ + return 0; +} #endif static const struct cmdq_host_ops sdhci_cmdq_ops = { @@ -3453,6 +3467,7 @@ static const struct cmdq_host_ops sdhci_cmdq_ops = { .dump_vendor_regs = sdhci_cmdq_dump_vendor_regs, .set_block_size = sdhci_cmdq_set_block_size, .clear_set_dumpregs = sdhci_cmdq_clear_set_dumpregs, + .crypto_cfg = sdhci_cmdq_crypto_cfg, }; int sdhci_add_host(struct sdhci_host *host) From cdb1137c3b1abc7a59172b7fb42d3b7e332cc237 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 27 May 2015 17:51:53 +0300 Subject: [PATCH 264/472] mmc: sd: add support for UHS card to get max frequency This change adds UHS cards to mmc_sd_get_max_clock() API. Cards that support UHS can set timing of SDR104 which supports frequency up to 208Mhz. Change-Id: I25bcb35aa2cecd98f6f04cd98a616a76c75b6784 Signed-off-by: Talel Shenhar --- drivers/mmc/core/sd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 44bc036bf2d0..88d0790b6b91 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -965,7 +965,10 @@ unsigned mmc_sd_get_max_clock(struct mmc_card *card) { unsigned max_dtr = (unsigned int)-1; - if (mmc_card_hs(card)) { + if (mmc_card_uhs(card)) { + if (max_dtr > card->sw_caps.uhs_max_dtr) + max_dtr = card->sw_caps.uhs_max_dtr; + } else if (mmc_card_hs(card)) { if (max_dtr > card->sw_caps.hs_max_dtr) max_dtr = card->sw_caps.hs_max_dtr; } else if (max_dtr > card->csd.max_dtr) { From 0d897b8fc4b68df29b8eaf3de38ce54ab77e441d Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 13 May 2015 14:08:39 +0300 Subject: [PATCH 265/472] mmc: sdhci-msm: enable clock scaling capability This change enables clock scaling capability for sdhci-msm platform driver. Change-Id: I9582bbb4b37594d4d59a7398a00656846516d675 Signed-off-by: Talel Shenhar --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 247650e96b6f..61492cc7a13d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3199,6 +3199,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; + msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; if (msm_host->pdata->nonremovable) From 6b78c6e6d12e995fda7664e1741ab9f12fbb9850 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 29 May 2015 15:41:18 +0530 Subject: [PATCH 266/472] mmc: cmdq_hci: Add DCMD response to mrq This will be needed by core layers to check the status for CMD13 that is sent in CMDQ mode. Change-Id: If3d062bad4cf87c2543e6d5345f9ab6a0afa23bf Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 00a065cfab8d..6c5951b51afa 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -597,6 +597,9 @@ static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); mrq = get_req_by_tag(cq_host, tag); + if (tag == cq_host->dcmd_slot) + mrq->cmd->resp[0] = cmdq_readl(cq_host, CQCRDCT); + mrq->done(mrq); } From 10fc703640d923a86b56b8703dbf6862b9acfd35 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 9 Jun 2015 09:38:36 +0530 Subject: [PATCH 267/472] mmc: block: add discard and secdiscard support for CMDQ mode Discard is supported in CMDQ mode only when device queue is empty. Hence, discard commands should be sent using DCMD slot with QBR (Queue Barrier) flag set. Change-Id: I630091cbd94ffcdcec71626257f912c15fd2e21e Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 167 ++++++++++++++++++++++++++- drivers/mmc/core/core.c | 238 ++++++++++++++++++++++++++++++++++----- include/linux/mmc/core.h | 5 + 3 files changed, 378 insertions(+), 32 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 4a0dba010cfd..cbf798babe5c 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -88,6 +88,8 @@ MODULE_ALIAS("mmc:block"); #define PCKD_TRGR_LOWER_BOUND 5 #define PCKD_TRGR_PRECISION_MULTIPLIER 100 +static struct mmc_cmdq_req *mmc_cmdq_prep_dcmd( + struct mmc_queue_req *mqrq, struct mmc_queue *mq); static DEFINE_MUTEX(block_mutex); /* @@ -1508,6 +1510,87 @@ int mmc_access_rpmb(struct mmc_queue *mq) return false; } +static struct mmc_cmdq_req *mmc_blk_cmdq_prep_discard_req(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + struct mmc_cmdq_req *cmdq_req; + struct mmc_queue_req *active_mqrq; + + BUG_ON(req->tag > card->ext_csd.cmdq_depth); + BUG_ON(test_and_set_bit(req->tag, &host->cmdq_ctx.active_reqs)); + + set_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); + + active_mqrq = &mq->mqrq_cmdq[req->tag]; + active_mqrq->req = req; + + cmdq_req = mmc_cmdq_prep_dcmd(active_mqrq, mq); + cmdq_req->cmdq_req_flags |= QBR; + cmdq_req->mrq.cmd = &cmdq_req->cmd; + cmdq_req->tag = req->tag; + return cmdq_req; +} + +static int mmc_blk_cmdq_issue_discard_rq(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + struct mmc_cmdq_req *cmdq_req = NULL; + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + unsigned int from, nr, arg; + int err = 0; + + if (!mmc_can_erase(card)) { + err = -EOPNOTSUPP; + goto out; + } + + from = blk_rq_pos(req); + nr = blk_rq_sectors(req); + + if (mmc_can_discard(card)) + arg = MMC_DISCARD_ARG; + else if (mmc_can_trim(card)) + arg = MMC_TRIM_ARG; + else + arg = MMC_ERASE_ARG; + + cmdq_req = mmc_blk_cmdq_prep_discard_req(mq, req); + if (card->quirks & MMC_QUIRK_INAND_CMD38) { + __mmc_switch_cmdq_mode(cmdq_req->mrq.cmd, + EXT_CSD_CMD_SET_NORMAL, + INAND_CMD38_ARG_EXT_CSD, + arg == MMC_TRIM_ARG ? + INAND_CMD38_ARG_TRIM : + INAND_CMD38_ARG_ERASE, + 0, true, false); + err = mmc_cmdq_wait_for_dcmd(card->host, cmdq_req); + if (err) + goto clear_dcmd; + } + err = mmc_cmdq_erase(cmdq_req, card, from, nr, arg); +clear_dcmd: + /* clear pending request */ + if (cmdq_req) { + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->active_reqs)); + clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); + } +out: + blk_end_request(req, err, blk_rq_bytes(req)); + + if (test_and_clear_bit(0, &ctx_info->req_starved)) + blk_run_queue(mq->queue); + mmc_release_host(host); + return err ? 1 : 0; +} + static int mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) { struct mmc_blk_data *md = mq->data; @@ -1551,6 +1634,79 @@ out: return err ? 0 : 1; } +static int mmc_blk_cmdq_issue_secdiscard_rq(struct mmc_queue *mq, + struct request *req) +{ + struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + struct mmc_cmdq_req *cmdq_req = NULL; + unsigned int from, nr, arg; + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + int err = 0; + + if (!(mmc_can_secure_erase_trim(card))) { + err = -EOPNOTSUPP; + goto out; + } + + from = blk_rq_pos(req); + nr = blk_rq_sectors(req); + + if (mmc_can_trim(card) && !mmc_erase_group_aligned(card, from, nr)) + arg = MMC_SECURE_TRIM1_ARG; + else + arg = MMC_SECURE_ERASE_ARG; + + cmdq_req = mmc_blk_cmdq_prep_discard_req(mq, req); + if (card->quirks & MMC_QUIRK_INAND_CMD38) { + __mmc_switch_cmdq_mode(cmdq_req->mrq.cmd, + EXT_CSD_CMD_SET_NORMAL, + INAND_CMD38_ARG_EXT_CSD, + arg == MMC_SECURE_TRIM1_ARG ? + INAND_CMD38_ARG_SECTRIM1 : + INAND_CMD38_ARG_SECERASE, + 0, true, false); + err = mmc_cmdq_wait_for_dcmd(card->host, cmdq_req); + if (err) + goto clear_dcmd; + } + + err = mmc_cmdq_erase(cmdq_req, card, from, nr, arg); + if (err) + goto clear_dcmd; + + if (arg == MMC_SECURE_TRIM1_ARG) { + if (card->quirks & MMC_QUIRK_INAND_CMD38) { + __mmc_switch_cmdq_mode(cmdq_req->mrq.cmd, + EXT_CSD_CMD_SET_NORMAL, + INAND_CMD38_ARG_EXT_CSD, + INAND_CMD38_ARG_SECTRIM2, + 0, true, false); + err = mmc_cmdq_wait_for_dcmd(card->host, cmdq_req); + if (err) + goto clear_dcmd; + } + + err = mmc_cmdq_erase(cmdq_req, card, from, nr, + MMC_SECURE_TRIM2_ARG); + } +clear_dcmd: + /* clear pending request */ + if (cmdq_req) { + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->active_reqs)); + clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); + } +out: + blk_end_request(req, err, blk_rq_bytes(req)); + + if (test_and_clear_bit(0, &ctx_info->req_starved)) + blk_run_queue(mq->queue); + mmc_release_host(host); + return err ? 1 : 0; +} + static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, struct request *req) { @@ -3180,10 +3336,17 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) } if (req) { - if (cmd_flags & REQ_FLUSH) + if (cmd_flags & REQ_DISCARD) { + if (cmd_flags & REQ_SECURE && + !(card->quirks & MMC_QUIRK_SEC_ERASE_TRIM_BROKEN)) + ret = mmc_blk_cmdq_issue_secdiscard_rq(mq, req); + else + ret = mmc_blk_cmdq_issue_discard_rq(mq, req); + } else if (cmd_flags & REQ_FLUSH) { ret = mmc_blk_cmdq_issue_flush_rq(mq, req); - else + } else { ret = mmc_blk_cmdq_issue_rw_rq(mq, req); + } } switch_failure: diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 21d3968626b4..fd9f1878c742 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1267,6 +1267,35 @@ int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req) } EXPORT_SYMBOL(mmc_cmdq_start_req); +static void mmc_cmdq_dcmd_req_done(struct mmc_request *mrq) +{ + complete(&mrq->completion); +} + +int mmc_cmdq_wait_for_dcmd(struct mmc_host *host, + struct mmc_cmdq_req *cmdq_req) +{ + struct mmc_request *mrq = &cmdq_req->mrq; + struct mmc_command *cmd = mrq->cmd; + int err = 0; + + init_completion(&mrq->completion); + mrq->done = mmc_cmdq_dcmd_req_done; + err = mmc_cmdq_start_req(host, cmdq_req); + if (err) + return err; + + wait_for_completion_io(&mrq->completion); + if (cmd->error) { + pr_err("%s: DCMD %d failed with err %d\n", + mmc_hostname(host), cmd->opcode, + cmd->error); + err = cmd->error; + } + return err; +} +EXPORT_SYMBOL(mmc_cmdq_wait_for_dcmd); + int mmc_cmdq_prepare_flush(struct mmc_command *cmd) { return __mmc_switch_cmdq_mode(cmd, EXT_CSD_CMD_SET_NORMAL, @@ -2911,20 +2940,9 @@ static unsigned int mmc_erase_timeout(struct mmc_card *card, return mmc_mmc_erase_timeout(card, arg, qty); } -static int mmc_do_erase(struct mmc_card *card, unsigned int from, - unsigned int to, unsigned int arg) +static u32 mmc_get_erase_qty(struct mmc_card *card, u32 from, u32 to) { - struct mmc_command cmd = {0}; - unsigned int qty = 0; - unsigned long timeout; - unsigned int fr, nr; - int err; - - fr = from; - nr = to - from + 1; - trace_mmc_blk_erase_start(arg, fr, nr); - - mmc_retune_hold(card->host); + u32 qty = 0; /* * qty is used to calculate the erase timeout which depends on how many @@ -2950,12 +2968,122 @@ static int mmc_do_erase(struct mmc_card *card, unsigned int from, else qty += ((to / card->erase_size) - (from / card->erase_size)) + 1; + return qty; +} + +static int mmc_cmdq_send_erase_cmd(struct mmc_cmdq_req *cmdq_req, + struct mmc_card *card, u32 opcode, u32 arg, u32 qty) +{ + struct mmc_command *cmd = cmdq_req->mrq.cmd; + int err; + + memset(cmd, 0, sizeof(struct mmc_command)); + + cmd->opcode = opcode; + cmd->arg = arg; + if (cmd->opcode == MMC_ERASE) { + cmd->flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; + cmd->busy_timeout = mmc_erase_timeout(card, arg, qty); + } else { + cmd->flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; + } + + err = mmc_cmdq_wait_for_dcmd(card->host, cmdq_req); + if (err) { + pr_err("mmc_erase: group start error %d, status %#x\n", + err, cmd->resp[0]); + return -EIO; + } + return 0; +} + +static int mmc_cmdq_do_erase(struct mmc_cmdq_req *cmdq_req, + struct mmc_card *card, unsigned int from, + unsigned int to, unsigned int arg) +{ + struct mmc_command *cmd = cmdq_req->mrq.cmd; + unsigned int qty = 0; + unsigned long timeout; + unsigned int fr, nr; + int err; + + fr = from; + nr = to - from + 1; + trace_mmc_blk_erase_start(arg, fr, nr); + + qty = mmc_get_erase_qty(card, from, to); if (!mmc_card_blockaddr(card)) { from <<= 9; to <<= 9; } + err = mmc_cmdq_send_erase_cmd(cmdq_req, card, MMC_ERASE_GROUP_START, + from, qty); + if (err) + goto out; + + err = mmc_cmdq_send_erase_cmd(cmdq_req, card, MMC_ERASE_GROUP_END, + to, qty); + if (err) + goto out; + + err = mmc_cmdq_send_erase_cmd(cmdq_req, card, MMC_ERASE, + arg, qty); + if (err) + goto out; + + timeout = jiffies + msecs_to_jiffies(MMC_CORE_TIMEOUT_MS); + do { + memset(cmd, 0, sizeof(struct mmc_command)); + cmd->opcode = MMC_SEND_STATUS; + cmd->arg = card->rca << 16; + cmd->flags = MMC_RSP_R1 | MMC_CMD_AC; + /* Do not retry else we can't see errors */ + err = mmc_cmdq_wait_for_dcmd(card->host, cmdq_req); + if (err || (cmd->resp[0] & 0xFDF92000)) { + pr_err("error %d requesting status %#x\n", + err, cmd->resp[0]); + err = -EIO; + goto out; + } + /* Timeout if the device never becomes ready for data and + * never leaves the program state. + */ + if (time_after(jiffies, timeout)) { + pr_err("%s: Card stuck in programming state! %s\n", + mmc_hostname(card->host), __func__); + err = -EIO; + goto out; + } + } while (!(cmd->resp[0] & R1_READY_FOR_DATA) || + (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG)); +out: + trace_mmc_blk_erase_end(arg, fr, nr); + return err; +} + +static int mmc_do_erase(struct mmc_card *card, unsigned int from, + unsigned int to, unsigned int arg) +{ + struct mmc_command cmd = {0}; + unsigned int qty = 0; + unsigned long timeout; + unsigned int fr, nr; + int err; + + fr = from; + nr = to - from + 1; + trace_mmc_blk_erase_start(arg, fr, nr); + + qty = mmc_get_erase_qty(card, from, to); + + if (!mmc_card_blockaddr(card)) { + from <<= 9; + to <<= 9; + } + + mmc_retune_hold(card->host); if (mmc_card_sd(card)) cmd.opcode = SD_ERASE_WR_BLK_START; else @@ -3034,21 +3162,9 @@ out: return err; } -/** - * mmc_erase - erase sectors. - * @card: card to erase - * @from: first sector to erase - * @nr: number of sectors to erase - * @arg: erase command argument (SD supports only %MMC_ERASE_ARG) - * - * Caller must claim host before calling this function. - */ -int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr, - unsigned int arg) +int mmc_erase_sanity_check(struct mmc_card *card, unsigned int from, + unsigned int nr, unsigned int arg) { - unsigned int rem, to = from + nr; - int err; - if (!(card->host->caps & MMC_CAP_ERASE) || !(card->csd.cmdclass & CCC_ERASE)) return -EOPNOTSUPP; @@ -3071,6 +3187,68 @@ int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr, if (from % card->erase_size || nr % card->erase_size) return -EINVAL; } + return 0; +} + +int mmc_cmdq_erase(struct mmc_cmdq_req *cmdq_req, + struct mmc_card *card, unsigned int from, unsigned int nr, + unsigned int arg) +{ + unsigned int rem, to = from + nr; + int ret; + + ret = mmc_erase_sanity_check(card, from, nr, arg); + if (ret) + return ret; + + if (arg == MMC_ERASE_ARG) { + rem = from % card->erase_size; + if (rem) { + rem = card->erase_size - rem; + from += rem; + if (nr > rem) + nr -= rem; + else + return 0; + } + rem = nr % card->erase_size; + if (rem) + nr -= rem; + } + + if (nr == 0) + return 0; + + to = from + nr; + + if (to <= from) + return -EINVAL; + + /* 'from' and 'to' are inclusive */ + to -= 1; + + return mmc_cmdq_do_erase(cmdq_req, card, from, to, arg); +} +EXPORT_SYMBOL(mmc_cmdq_erase); + +/** + * mmc_erase - erase sectors. + * @card: card to erase + * @from: first sector to erase + * @nr: number of sectors to erase + * @arg: erase command argument (SD supports only %MMC_ERASE_ARG) + * + * Caller must claim host before calling this function. + */ +int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr, + unsigned int arg) +{ + unsigned int rem, to = from + nr; + int ret; + + ret = mmc_erase_sanity_check(card, from, nr, arg); + if (ret) + return ret; if (arg == MMC_ERASE_ARG) { rem = from % card->erase_size; @@ -3108,10 +3286,10 @@ int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr, */ rem = card->erase_size - (from % card->erase_size); if ((arg & MMC_TRIM_ARGS) && (card->eg_boundary) && (nr > rem)) { - err = mmc_do_erase(card, from, from + rem - 1, arg); + ret = mmc_do_erase(card, from, from + rem - 1, arg); from += rem; - if ((err) || (to <= from)) - return err; + if ((ret) || (to <= from)) + return ret; } return mmc_do_erase(card, from, to, arg); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 59d9196250c1..fcad0aa31b2e 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -123,6 +123,11 @@ extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, extern int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req); extern int mmc_cmdq_prepare_flush(struct mmc_command *cmd); +extern int mmc_cmdq_wait_for_dcmd(struct mmc_host *host, + struct mmc_cmdq_req *cmdq_req); +extern int mmc_cmdq_erase(struct mmc_cmdq_req *cmdq_req, + struct mmc_card *card, unsigned int from, unsigned int nr, + unsigned int arg); extern int mmc_stop_bkops(struct mmc_card *); extern int mmc_read_bkops_status(struct mmc_card *); From b8fc176b8136c764e5e6e7cac798cf932f25a3ce Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 20 May 2015 12:15:35 +0530 Subject: [PATCH 268/472] mmc: block: support RPMB with CMDQ framework CMDQ is not supported for RPMB partition. Hence, for RPMB requests the controller is kept in HALT state and then CMDQ is disabled in the card. Change-Id: I1242841d5fa063b542e35dcff95694ef5e88737a Signed-off-by: Sahitya Tummala --- drivers/mmc/card/block.c | 12 ++++++++++++ drivers/mmc/card/queue.c | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index cbf798babe5c..5e29a8df8f3a 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1046,6 +1046,8 @@ static int mmc_blk_cmdq_switch(struct mmc_card *card, { int ret = 0; bool cmdq_mode = !!mmc_card_cmdq(card); + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; if (!(card->host->caps2 & MMC_CAP2_CMD_QUEUE) || !card->ext_csd.cmdq_support || @@ -1060,6 +1062,16 @@ static int mmc_blk_cmdq_switch(struct mmc_card *card, __func__, ret, MMC_CARD_CMDQ_BLK_SIZE); goto out; } + + } else { + if (!test_bit(CMDQ_STATE_HALT, &ctx->curr_state)) { + ret = mmc_cmdq_halt(host, true); + if (ret) { + pr_err("%s: halt: failed: %d\n", + mmc_hostname(host), ret); + goto out; + } + } } ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 98afbee58302..e99b1cf16f94 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -62,7 +62,7 @@ static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, { if (((req->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) || - mmc_host_halt(host) || + (!host->card->part_curr && mmc_host_halt(host)) || test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { pr_debug("%s: %s: skip pulling reqs: state: %lu\n", mmc_hostname(host), __func__, ctx->curr_state); From 51468e6b174304a17dc38036013b2352d985f365 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 15 Jun 2015 16:49:29 -0700 Subject: [PATCH 269/472] mmc: sdhci-msm: Free pltfm_host on probe defer The platform host has been initialized and should be freed in case we defer the probe. Change-Id: Iadce572fa4618b18f2fadf9e00812e75144af1c9 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 61492cc7a13d..f0670bf3eea9 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2918,7 +2918,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) */ dev_err(&pdev->dev, "%s: required ICE device not probed yet err = %d\n", __func__, ret); - goto out_host_free; + goto pltfm_free; } else if (ret == -ENODEV) { /* @@ -2930,7 +2930,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) } else if (ret) { dev_err(&pdev->dev, "%s: sdhci_msm_ice_get_dev failed %d\n", __func__, ret); - goto out_host_free; + goto pltfm_free; } /* Extract platform data */ From 047440f975fd9f855cf7e7304146c8968b2b2644 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 15 Jun 2015 17:02:52 -0700 Subject: [PATCH 270/472] mmc: host: Reorder MMC_SDHCI_MSM_ICE config MMC_SDHCI_MSM_ICE is enabled only if MMC_SDHCI_MSM config is enabled. Reorder this so that it shows up appropriately in the kernel config menu. Change-Id: Ic6af2979e7c8c56841d509f3f25fadd4dd2fe9d2 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/Kconfig | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 63c67e0e1aaf..b167ef4aa865 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -137,17 +137,6 @@ config MMC_SDHCI_OF_AT91 help This selects the Atmel SDMMC driver -config MMC_SDHCI_MSM_ICE - bool "Qualcomm Technologies, Inc Inline Crypto Engine for SDHCI core" - depends on MMC_SDHCI_MSM && CRYPTO_DEV_QCOM_ICE - help - This selects the QTI specific additions to support Inline Crypto - Engine (ICE). ICE accelerates the crypto operations and maintains - the high SDHCI performance. - - Select this if you have ICE supported for SDHCI on QTI chipset. - If unsure, say N. - config MMC_SDHCI_OF_ESDHC tristate "SDHCI OF support for the Freescale eSDHC controller" depends on MMC_SDHCI_PLTFM @@ -428,6 +417,17 @@ config MMC_SDHCI_MSM If unsure, say N. +config MMC_SDHCI_MSM_ICE + bool "Qualcomm Technologies, Inc Inline Crypto Engine for SDHCI core" + depends on MMC_SDHCI_MSM && CRYPTO_DEV_QCOM_ICE + help + This selects the QTI specific additions to support Inline Crypto + Engine (ICE). ICE accelerates the crypto operations and maintains + the high SDHCI performance. + + Select this if you have ICE supported for SDHCI on QTI chipset. + If unsure, say N. + config MMC_MSM tristate "Qualcomm SDCC Controller Support" depends on MMC && (ARCH_MSM7X00A || ARCH_MSM7X30 || ARCH_QSD8X50) From c3475c87343ca5e78e1033612c604489d2fd6566 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Tue, 19 May 2015 14:27:34 +0530 Subject: [PATCH 271/472] mmc: Enable clock gating for cmdq Enable clock gating for cmdq by adding respective API to clk hold/release. CMDQ also uses the same legacy clock gating API. Change-Id: I3d4f28cedb24ff2292ab08bdd7470358cf134dd5 Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 3 +++ drivers/mmc/core/core.c | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 5e29a8df8f3a..eaee850b7d37 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2899,7 +2899,9 @@ static void mmc_blk_cmdq_reset(struct mmc_host *host, bool clear_all) mmc_cmdq_discard_queue(host, 0); reset: mmc_hw_reset(host); + mmc_host_clk_hold(host); host->cmdq_ops->reset(host, true); + mmc_host_clk_release(host); clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); } @@ -3090,6 +3092,7 @@ void mmc_blk_cmdq_req_done(struct mmc_request *mrq) { struct request *req = mrq->req; + mmc_host_clk_release(mrq->host); blk_complete_request(req); } EXPORT_SYMBOL(mmc_blk_cmdq_req_done); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index fd9f1878c742..54f9459e4303 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1216,7 +1216,6 @@ void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) { if (likely(host->cmdq_ops->post_req)) host->cmdq_ops->post_req(host, mrq, err); - mmc_host_clk_release(host); } EXPORT_SYMBOL(mmc_cmdq_post_req); @@ -1238,7 +1237,7 @@ int mmc_cmdq_halt(struct mmc_host *host, bool halt) if ((halt && mmc_host_halt(host)) || (!halt && !mmc_host_halt(host))) return -EINVAL; - + mmc_host_clk_hold(host); if (host->cmdq_ops->halt) { err = host->cmdq_ops->halt(host, halt); if (!err && halt) @@ -1248,7 +1247,7 @@ int mmc_cmdq_halt(struct mmc_host *host, bool halt) } else { err = -ENOSYS; } - + mmc_host_clk_release(host); return err; } EXPORT_SYMBOL(mmc_cmdq_halt); @@ -1269,6 +1268,7 @@ EXPORT_SYMBOL(mmc_cmdq_start_req); static void mmc_cmdq_dcmd_req_done(struct mmc_request *mrq) { + mmc_host_clk_release(mrq->host); complete(&mrq->completion); } From 83ed2ae95e1dc8e07faa82f570cb7cbbc58754ff Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 28 May 2015 16:54:19 +0530 Subject: [PATCH 272/472] mmc: block: Add cache barrier support eMMC cache barrier provides a way to perform delayed and in-order flushing of cached data. Host can use this to avoid flushing delays but still maintain the order between the data requests in cache. In case the device gets more barrier requests than it supports, then a barrier request is treated as a normal flush request in the device. If the eMMC cache flushing policy is set to 1, then the device inherently flushes all the cached requests in FIFO order. For such devices, as per spec, it is redundant to send any barrier requests to the device. This may add unnecessary overhead to both host and the device. So make sure to not send barrier requests in such cases. Change-Id: Ia7af316800a6895942d3cabcd64600d56fab25a6 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 28 ++++++++++++++++++++++++++-- drivers/mmc/core/core.c | 34 ++++++++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 31 +++++++++++++++++++++++++++++++ include/linux/mmc/card.h | 3 +++ include/linux/mmc/core.h | 1 + include/linux/mmc/mmc.h | 3 +++ 6 files changed, 98 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index eaee850b7d37..d1f95b57deab 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1792,7 +1792,30 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) struct mmc_card *card = md->queue.card; int ret = 0; - ret = mmc_flush_cache(card); + if (!req) + return 0; + + if (req->cmd_flags & REQ_BARRIER) { + /* + * If eMMC cache flush policy is set to 1, then the device + * shall flush the requests in First-In-First-Out (FIFO) order. + * In this case, as per spec, the host must not send any cache + * barrier requests as they are redundant and add unnecessary + * overhead to both device and host. + */ + if (card->ext_csd.cache_flush_policy & 1) + goto end_req; + + /* + * In case barrier is not supported or enabled in the device, + * use flush as a fallback option. + */ + ret = mmc_cache_barrier(card); + if (ret) + ret = mmc_flush_cache(card); + } else if (req->cmd_flags & REQ_FLUSH) { + ret = mmc_flush_cache(card); + } if (ret == -ENODEV) { pr_err("%s: %s: restart mmc card", req->rq_disk->disk_name, __func__); @@ -1809,6 +1832,7 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) ret = -EIO; } +end_req: blk_end_request_all(req, ret); return ret ? 0 : 1; @@ -3402,7 +3426,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) ret = mmc_blk_issue_secdiscard_rq(mq, req); else ret = mmc_blk_issue_discard_rq(mq, req); - } else if (cmd_flags & REQ_FLUSH) { + } else if (cmd_flags & (REQ_FLUSH | REQ_BARRIER)) { /* complete ongoing async transfer before issuing flush */ if (card->host->areq) mmc_blk_issue_rw_rq(mq, NULL); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 54f9459e4303..e108a234dd07 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3780,6 +3780,40 @@ int mmc_power_restore_host(struct mmc_host *host) } EXPORT_SYMBOL(mmc_power_restore_host); +/* + * Add barrier request to the requests in cache + */ +int mmc_cache_barrier(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + int err = 0; + + if (!card->ext_csd.cache_ctrl || + (card->quirks & MMC_QUIRK_CACHE_DISABLE)) + goto out; + + if (!mmc_card_mmc(card)) + goto out; + + if (!card->ext_csd.barrier_en) + return -ENOTSUPP; + + /* + * If a device receives maximum supported barrier + * requests, a barrier command is treated as a + * flush command. Hence, it is betetr to use + * flush timeout instead a generic CMD6 timeout + */ + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_FLUSH_CACHE, 0x2, 0); + if (err) + pr_err("%s: cache barrier error %d\n", + mmc_hostname(host), err); +out: + return err; +} +EXPORT_SYMBOL(mmc_cache_barrier); + /* * Flush the cache to the non-volatile storage. */ diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 9ab7b7c74796..ed6af9d1d883 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -625,9 +625,19 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) mmc_hostname(card->host), card->ext_csd.cmdq_depth); } + card->ext_csd.barrier_support = + ext_csd[EXT_CSD_BARRIER_SUPPORT]; + card->ext_csd.cache_flush_policy = + ext_csd[EXT_CSD_CACHE_FLUSH_POLICY]; + pr_info("%s: cache barrier support %d flush policy %d\n", + mmc_hostname(card->host), + card->ext_csd.barrier_support, + card->ext_csd.cache_flush_policy); } else { card->ext_csd.cmdq_support = 0; card->ext_csd.cmdq_depth = 0; + card->ext_csd.barrier_support = 0; + card->ext_csd.cache_flush_policy = 0; } /* eMMC v5 or later */ @@ -1975,6 +1985,27 @@ reinit: } else { card->ext_csd.cache_ctrl = 1; } + /* enable cache barrier if supported by the device */ + if (card->ext_csd.cache_ctrl && + card->ext_csd.barrier_support) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BARRIER_CTRL, 1, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) { + pr_err("%s: %s: mmc_switch() for BARRIER_CTRL fails %d\n", + mmc_hostname(host), __func__, + err); + goto free_card; + } + if (err) { + pr_warn("%s: Barrier is supported but failed to turn on (%d)\n", + mmc_hostname(card->host), err); + card->ext_csd.barrier_en = 0; + err = 0; + } else { + card->ext_csd.barrier_en = 1; + } + } } else { /* * mmc standard doesn't say what is the card default diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 609a792e9e5c..0f4e22db31ab 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -119,10 +119,13 @@ struct mmc_ext_csd { u8 raw_pwr_cl_ddr_52_195; /* 238 */ u8 raw_pwr_cl_ddr_52_360; /* 239 */ u8 raw_pwr_cl_ddr_200_360; /* 253 */ + u8 cache_flush_policy; /* 240 */ u8 raw_bkops_status; /* 246 */ u8 raw_sectors[4]; /* 212 - 4 bytes */ u8 cmdq_depth; /* 307 */ u8 cmdq_support; /* 308 */ + u8 barrier_support; /* 486 */ + u8 barrier_en; unsigned int feature_support; #define MMC_DISCARD_FEATURE BIT(0) /* CMD38 feature */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index fcad0aa31b2e..22c610609a25 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -185,6 +185,7 @@ extern void mmc_put_card(struct mmc_card *card); extern void mmc_set_ios(struct mmc_host *host); extern int mmc_flush_cache(struct mmc_card *); +extern int mmc_cache_barrier(struct mmc_card *); extern int mmc_detect_card_removed(struct mmc_host *host); diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 07b04c6e5c64..5612781ef522 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -221,6 +221,7 @@ struct _mmc_csd { */ #define EXT_CSD_CMDQ 15 /* R/W */ +#define EXT_CSD_BARRIER_CTRL 31 /* R/W */ #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ @@ -273,6 +274,7 @@ 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_CACHE_FLUSH_POLICY 240 /* 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 */ @@ -281,6 +283,7 @@ struct _mmc_csd { #define EXT_CSD_FIRMWARE_VERSION 254 /* RO, 8 bytes */ #define EXT_CSD_CMDQ_DEPTH 307 /* RO */ #define EXT_CSD_CMDQ_SUPPORT 308 /* RO */ +#define EXT_CSD_BARRIER_SUPPORT 486 /* RO */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_SUPPORTED_MODE 493 /* RO */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ From 6739d003471b392dad7964e1399d7b8200cd7403 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 11 Jun 2015 11:33:23 +0300 Subject: [PATCH 273/472] mmc: cmdq: configure task descriptor list base register on enable When enabling cmdq mode for the first time, need to allocate descriptors table. After controller reset and enabling cmdq next time, still need to update CQTDLBA and CQTDLBAU registers with already allocated descriptors table (desc_dma_base). Change-Id: Ide89eebbce5a193cd44a1ea4ec65403f98e2a7ab Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/cmdq_hci.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 6c5951b51afa..dc8e19fd1441 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -298,12 +298,11 @@ static int cmdq_enable(struct mmc_host *mmc) err = cmdq_host_alloc_tdl(cq_host); if (err) goto out; - cmdq_writel(cq_host, lower_32_bits(cq_host->desc_dma_base), - CQTDLBA); - cmdq_writel(cq_host, upper_32_bits(cq_host->desc_dma_base), - CQTDLBAU); } + cmdq_writel(cq_host, lower_32_bits(cq_host->desc_dma_base), CQTDLBA); + cmdq_writel(cq_host, upper_32_bits(cq_host->desc_dma_base), CQTDLBAU); + /* * disable all vendor interrupts * enable CMDQ interrupts From 108d3eb6e65f8c5147369ec94d2a43ada37cbbd7 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 31 May 2015 10:10:13 +0300 Subject: [PATCH 274/472] mmc: cmdq-host: add post halt helper after halt ack interrupt After the CQE halt on some controllers need additional actions, before wakeup of waiting thread. Change-Id: I742baa48eb3f6eccf782d77a03aafe16ab7188f1 Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/cmdq_hci.c | 2 ++ drivers/mmc/host/cmdq_hci.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index dc8e19fd1441..2bf9330399a2 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -674,6 +674,8 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) } if (status & CQIS_HAC) { + if (cq_host->ops->post_cqe_halt) + cq_host->ops->post_cqe_halt(mmc); /* halt is completed, wakeup waiting thread */ complete(&cq_host->halt_comp); } diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index 05c622c057e8..100b3177e4c7 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -201,6 +201,7 @@ struct cmdq_host_ops { int (*reset)(struct mmc_host *mmc); int (*crypto_cfg)(struct mmc_host *mmc, struct mmc_request *mrq, u32 slot); + void (*post_cqe_halt)(struct mmc_host *mmc); }; static inline void cmdq_writel(struct cmdq_host *host, u32 val, int reg) From 22543d78b868ee8c1652d7375c3912ebf3c0442a Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 11 Jun 2015 11:41:53 +0300 Subject: [PATCH 275/472] mmc: cmdq: add platform device reference counting for runtime pm Access to the registers of CQE HCI wrapped by increment/decrement of pm core usage counter for the platform device. Change-Id: I9da4aa7d28dbf8e1d2bf62f6d5fa0875bd5b6064 Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/cmdq_hci.c | 52 +++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 2bf9330399a2..7e4b528eadcb 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "cmdq_hci.h" @@ -32,6 +33,26 @@ /* 1 sec */ #define HALT_TIMEOUT_MS 1000 +#ifdef CONFIG_PM_RUNTIME +static int cmdq_runtime_pm_get(struct cmdq_host *host) +{ + return pm_runtime_get_sync(host->mmc->parent); +} +static int cmdq_runtime_pm_put(struct cmdq_host *host) +{ + pm_runtime_mark_last_busy(host->mmc->parent); + return pm_runtime_put_autosuspend(host->mmc->parent); +} +#else +static inline int cmdq_runtime_pm_get(struct cmdq_host *host) +{ + return 0; +} +static inline int cmdq_runtime_pm_put(struct cmdq_host *host) +{ + return 0; +} +#endif static inline struct mmc_request *get_req_by_tag(struct cmdq_host *cq_host, unsigned int tag) { @@ -272,6 +293,7 @@ static int cmdq_enable(struct mmc_host *mmc) if (cq_host->enabled) goto out; + cmdq_runtime_pm_get(cq_host); cqcfg = cmdq_readl(cq_host, CQCFG); if (cqcfg & 0x1) { pr_info("%s: %s: cq_host is already enabled\n", @@ -335,6 +357,7 @@ static int cmdq_enable(struct mmc_host *mmc) cq_host->ops->clear_set_dumpregs(mmc, 1); out: + cmdq_runtime_pm_put(cq_host); return err; } @@ -342,12 +365,13 @@ static void cmdq_disable(struct mmc_host *mmc, bool soft) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + cmdq_runtime_pm_get(cq_host); if (soft) { cmdq_writel(cq_host, cmdq_readl( cq_host, CQCFG) & ~(CQ_ENABLE), CQCFG); } - + cmdq_runtime_pm_put(cq_host); cq_host->enabled = false; } @@ -360,6 +384,7 @@ static void cmdq_reset(struct mmc_host *mmc, bool soft) unsigned int rca; int ret; + cmdq_runtime_pm_get(cq_host); cqcfg = cmdq_readl(cq_host, CQCFG); tdlba = cmdq_readl(cq_host, CQTDLBA); tdlbau = cmdq_readl(cq_host, CQTDLBAU); @@ -391,6 +416,7 @@ static void cmdq_reset(struct mmc_host *mmc, bool soft) mb(); cmdq_writel(cq_host, cqcfg, CQCFG); + cmdq_runtime_pm_put(cq_host); cq_host->enabled = true; } @@ -536,7 +562,7 @@ static void cmdq_prep_dcmd_desc(struct mmc_host *mmc, static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) { - int err; + int err = 0; u64 data = 0; u64 *task_desc = NULL; u32 tag = mrq->cmdq_req->tag; @@ -549,11 +575,13 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) goto out; } + cmdq_runtime_pm_get(cq_host); + if (mrq->cmdq_req->cmdq_req_flags & DCMD) { cmdq_prep_dcmd_desc(mmc, mrq); cq_host->mrq_slot[DCMD_SLOT] = mrq; cmdq_writel(cq_host, 1 << DCMD_SLOT, CQTDBR); - return 0; + goto out; } if (cq_host->ops->crypto_cfg) { @@ -575,7 +603,7 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) if (err) { pr_err("%s: %s: failed to setup tx desc: %d\n", mmc_hostname(mmc), __func__, err); - return err; + goto out; } BUG_ON(cmdq_readl(cq_host, CQTDBR) & (1 << tag)); @@ -587,6 +615,7 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) cmdq_writel(cq_host, 1 << tag, CQTDBR); out: + cmdq_runtime_pm_put(cq_host); return err; } @@ -689,26 +718,26 @@ EXPORT_SYMBOL(cmdq_irq); static int cmdq_halt(struct mmc_host *mmc, bool halt) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); - u32 val; + u32 ret = 0; + cmdq_runtime_pm_get(cq_host); if (halt) { cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) | HALT, CQCTL); - val = wait_for_completion_timeout(&cq_host->halt_comp, + ret = wait_for_completion_timeout(&cq_host->halt_comp, msecs_to_jiffies(HALT_TIMEOUT_MS)); /* halt done: re-enable legacy interrupts */ if (cq_host->ops->clear_set_irqs) cq_host->ops->clear_set_irqs(mmc, false); - - return val ? 0 : -ETIMEDOUT; + ret = ret ? 0 : -ETIMEDOUT; } else { if (cq_host->ops->clear_set_irqs) cq_host->ops->clear_set_irqs(mmc, true); cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) & ~HALT, CQCTL); } - - return 0; + cmdq_runtime_pm_put(cq_host); + return ret; } static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, @@ -731,8 +760,9 @@ static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, static void cmdq_dumpstate(struct mmc_host *mmc) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); - + cmdq_runtime_pm_get(cq_host); cmdq_dumpregs(cq_host); + cmdq_runtime_pm_put(cq_host); } static const struct mmc_cmdq_host_ops cmdq_host_ops = { From 55c03d8d085edb6d50deb86d5193ca179056fbd8 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Mon, 22 Jun 2015 12:11:12 +0300 Subject: [PATCH 276/472] mmc: core: halt CQE before suspend and un-halt CQE on resume To execute legacy SDHCi suspend/resume flow, CQE should be halted, because suspend flow uses legacy SDHCi commands (cmd6 flush and cmd5 sleep) commands. On resume, there is full card initialization and both the card and the controller will be in CQ mode after resume. Still need to update CQE state by cleaning halt bit. This change implemented in helpers, that are used by both system and runtime suspend/resume callbacks. Change-Id: I2ae9b19e2cbde04366e7ecf06377a5efd81e3f26 Signed-off-by: Konstantin Dorfman [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index ed6af9d1d883..7bfd47513eab 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2269,6 +2269,19 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (mmc_card_suspended(host->card)) goto out; + if (mmc_card_cmdq(host->card)) { + BUG_ON(host->cmdq_ctx.active_reqs); + + err = mmc_cmdq_halt(host, true); + if (err) { + pr_err("%s: halt: failed: %d\n", __func__, err); + goto out; + } + mmc_host_clk_hold(host); + host->cmdq_ops->disable(host, true); + mmc_host_clk_release(host); + } + if (mmc_card_doing_bkops(host->card)) { err = mmc_stop_bkops(host->card); if (err) @@ -2347,6 +2360,11 @@ static int _mmc_resume(struct mmc_host *host) } break; } + if (!err && mmc_card_cmdq(host->card)) { + err = mmc_cmdq_halt(host, false); + if (err) + pr_err("%s: un-halt: failed: %d\n", __func__, err); + } mmc_card_clr_suspended(host->card); mmc_release_host(host); @@ -2446,7 +2464,7 @@ static int mmc_runtime_resume(struct mmc_host *host) trace_mmc_runtime_resume(mmc_hostname(host), err, ktime_to_us(ktime_sub(ktime_get(), start))); - return 0; + return err; } int mmc_can_reset(struct mmc_card *card) From 3b7cc278b03915a44645676adf948a79965b6fbf Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 31 May 2015 10:08:29 +0300 Subject: [PATCH 277/472] mmc: sdhci: add post halt helper to support command queue hosts After halting the CQE, an unexpected legacy irq may be received by the controller. The post halt helper function acknowledges this irq and handles it. Change-Id: Iaaa4e57fba830d626fad693ff33dd966994bc50f Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/sdhci.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 6292dcddd1ef..b6a62ec35417 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3423,6 +3423,15 @@ static int sdhci_cmdq_crypto_cfg(struct mmc_host *mmc, return sdhci_crypto_cfg(host, mrq, slot); } + +static void sdhci_cmdq_post_cqe_halt(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_writel(host, sdhci_readl(host, SDHCI_INT_ENABLE) | + SDHCI_INT_RESPONSE, SDHCI_INT_ENABLE); + sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS); +} #else static void sdhci_cmdq_clear_set_irqs(struct mmc_host *mmc, bool clear) { @@ -3459,6 +3468,10 @@ static int sdhci_cmdq_crypto_cfg(struct mmc_host *mmc, { return 0; } + +static void sdhci_cmdq_post_cqe_halt(struct mmc_host *mmc) +{ +} #endif static const struct cmdq_host_ops sdhci_cmdq_ops = { @@ -3468,6 +3481,7 @@ static const struct cmdq_host_ops sdhci_cmdq_ops = { .set_block_size = sdhci_cmdq_set_block_size, .clear_set_dumpregs = sdhci_cmdq_clear_set_dumpregs, .crypto_cfg = sdhci_cmdq_crypto_cfg, + .post_cqe_halt = sdhci_cmdq_post_cqe_halt, }; int sdhci_add_host(struct sdhci_host *host) From c6bb958c9b3c87f7a0f1015aefc1bd2583ddd0e4 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 9 Jun 2015 13:07:50 +0300 Subject: [PATCH 278/472] mmc: queue: Fix pull new requests condition, when in cmdq mode There are three cases, request should not be pulled from the queue: - it is dcmd request, while dcmd request is in progress - the CQE is halted, but not because of the runtime suspend - the cmdq is in error state When the card is suspended, the CQE is halted. New request coming should be pulled from the request queue and the runtime resume will be triggered by mmc_get_card(). There is no race between the pulling request condition and the runtime suspend flow, because the card marked as suspended after the CQE is halted. Change-Id: I126ae689f7fea2e7545dfda7c4c6abda286a0f11 Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/queue.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index e99b1cf16f94..322be51f28fe 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -60,16 +60,22 @@ static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, struct request *req) { - if (((req->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && - test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) || - (!host->card->part_curr && mmc_host_halt(host)) || - test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) { - pr_debug("%s: %s: skip pulling reqs: state: %lu\n", - mmc_hostname(host), __func__, ctx->curr_state); - return false; - } else { - return true; - } + bool ret = true; + + if ((req->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && + test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) + ret = false; + else if (!host->card->part_curr && + mmc_host_halt(host) && !mmc_card_suspended(host->card)) + ret = false; + else if (test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) + ret = false; + + if (!ret) + pr_debug("%s: %s: skip pulling reqs: state: %lu, cmd_flags: 0x%x\n", + mmc_hostname(host), __func__, + ctx->curr_state, (unsigned int)req->cmd_flags); + return ret; } static int mmc_cmdq_thread(void *d) From c85443aa9797c2f2dfc52b9004fa14704ce1a479 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 9 Jun 2015 10:56:41 +0300 Subject: [PATCH 279/472] mmc: block: add card device reference counting for CQ mode Since error handling could race with runtime suspend, increase usage_count for the card device will prevent this race. Change-Id: Ie95a3c631f519c7993b0032f0b674871b64e4cb6 Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/block.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index d1f95b57deab..5d95402c5fb2 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1599,7 +1599,7 @@ out: if (test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); - mmc_release_host(host); + mmc_put_card(card); return err ? 1 : 0; } @@ -1715,7 +1715,7 @@ out: if (test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); - mmc_release_host(host); + mmc_put_card(card); return err ? 1 : 0; } @@ -2941,7 +2941,7 @@ static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) return; } - mmc_claim_host(card->host); + mmc_get_card(card); /* disable CQ mode in card */ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CMDQ, 0, @@ -2954,7 +2954,7 @@ static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) host->card->cmdq_init = false; } out: - mmc_release_host(card->host); + mmc_put_card(card); } static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) @@ -2989,6 +2989,7 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) struct mmc_card *card = mq->card; struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + pm_runtime_get_sync(&card->dev); err = mmc_cmdq_halt(host, true); if (err) { pr_err("halt: failed: %d\n", err); @@ -3050,6 +3051,9 @@ unhalt: mmc_cmdq_halt(host, false); out: + pm_runtime_mark_last_busy(&card->dev); + pm_runtime_put_autosuspend(&card->dev); + if (test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mrq->req->q); } @@ -3101,8 +3105,8 @@ out: if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state) && test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); - mmc_release_host(host); + mmc_put_card(host->card); if (blk_queue_stopped(mq->queue) && !ctx_info->active_reqs) complete(&mq->cmdq_shutdown_complete); return; @@ -3363,14 +3367,14 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) struct mmc_card *card = md->queue.card; unsigned int cmd_flags = req ? req->cmd_flags : 0; - mmc_claim_host(card->host); + mmc_get_card(card); ret = mmc_blk_cmdq_part_switch(card, md); if (ret) { pr_err("%s: %s: partition switch failed %d\n", md->disk->disk_name, __func__, ret); if (req) blk_end_request_all(req, ret); - mmc_release_host(card->host); + mmc_put_card(card); goto switch_failure; } From e04475b829271bcbfcfb096b2511ad89cf736203 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 11 Jun 2015 10:05:18 +0300 Subject: [PATCH 280/472] mmc: sdhci-msm: fix tracepoint for pm debugging Tracepoint measures time that takes runtime suspend, suspending crypto engine time included into measurement. Change-Id: I6108a9dc5b188e2086aa5e6d2fe87414bb2a2539 Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/sdhci-msm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f0670bf3eea9..4d2ba991285d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3395,8 +3395,6 @@ static int sdhci_msm_runtime_suspend(struct device *dev) if (msm_host->msm_bus_vote.client_handle) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); } - trace_sdhci_msm_runtime_suspend(mmc_hostname(host->mmc), 0, - ktime_to_us(ktime_sub(ktime_get(), start))); if (host->is_crypto_en) { ret = sdhci_msm_ice_suspend(host); @@ -3404,6 +3402,8 @@ static int sdhci_msm_runtime_suspend(struct device *dev) pr_err("%s: failed to suspend crypto engine %d\n", mmc_hostname(host->mmc), ret); } + trace_sdhci_msm_runtime_suspend(mmc_hostname(host->mmc), 0, + ktime_to_us(ktime_sub(ktime_get(), start))); return 0; } From 7f8ab6548b20d17c4c4c0212ea8e5ccfcf902ad8 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 23 Jun 2015 13:15:54 +0300 Subject: [PATCH 281/472] mmc: core: Add clock hold and release pair for cmdq_ops mmc_host_clk_hold/release pair should be added for relevant cmdq_ops. Since it enables host->clock which is needed for register access as well (apart from MCLK). Change-Id: I6d9d15a421225c5b4179cb19e467a17d01ad176f Signed-off-by: Ritesh Harjani Signed-off-by: Talel Shenhar --- drivers/mmc/core/mmc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 7bfd47513eab..3e0510b597b6 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1465,8 +1465,10 @@ static int mmc_select_cmdq(struct mmc_card *card) goto out; mmc_card_set_cmdq(card); + mmc_host_clk_hold(card->host); ret = host->cmdq_ops->enable(card->host); if (ret) { + mmc_host_clk_release(card->host); pr_err("%s: failed (%d) enabling CMDQ on host\n", mmc_hostname(host), ret); mmc_card_clr_cmdq(card); @@ -1476,6 +1478,7 @@ static int mmc_select_cmdq(struct mmc_card *card) goto out; } + mmc_host_clk_release(card->host); pr_info("%s: CMDQ enabled on card\n", mmc_hostname(host)); out: return ret; From 77bb196cf5a4afea007794716e41f4b4c303856e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 24 Jun 2015 14:46:49 -0700 Subject: [PATCH 282/472] mmc: sdhci-msm: Reset vendor specific func register on probe The vendor specific func register doesn't get reset when using the software reset register. The various bootloader's could leave this in an unknown state, hence reset this register to it's power on reset value during probe. Change-Id: I3258eedf87b1bc945f43b85627948e6c92b74686 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4d2ba991285d..33cb44a36a5f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -113,6 +113,7 @@ #define CORE_HC_SELECT_IN_EN (1 << 18) #define CORE_HC_SELECT_IN_HS400 (6 << 19) #define CORE_HC_SELECT_IN_MASK (7 << 19) +#define CORE_VENDOR_SPEC_POR_VAL 0xA1C #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 @@ -2886,7 +2887,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) struct resource *core_memres = NULL; int ret = 0, dead = 0; u16 host_version; - u32 pwr, irq_status, irq_ctl; + u32 irq_status, irq_ctl; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -3074,25 +3075,12 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto vreg_deinit; } - /* Unset HC_MODE_EN bit in HC_MODE register */ - writel_relaxed(0, (msm_host->core_mem + CORE_HC_MODE)); - - /* Set SW_RST bit in POWER register (Offset 0x0) */ - writel_relaxed(readl_relaxed(msm_host->core_mem + CORE_POWER) | - CORE_SW_RST, msm_host->core_mem + CORE_POWER); /* - * SW reset can take upto 10HCLK + 15MCLK cycles. - * Calculating based on min clk rates (hclk = 27MHz, - * mclk = 400KHz) it comes to ~40us. Let's poll for - * max. 1ms for reset completion. + * Reset the vendor spec register to power on reset state. */ - ret = readl_poll_timeout(msm_host->core_mem + CORE_POWER, - pwr, !(pwr & CORE_SW_RST), 10, 1000); + writel_relaxed(CORE_VENDOR_SPEC_POR_VAL, + host->ioaddr + CORE_VENDOR_SPEC); - if (ret) { - dev_err(&pdev->dev, "reset failed (%d)\n", ret); - goto vreg_deinit; - } /* Set HC_MODE_EN bit in HC_MODE register */ writel_relaxed(HC_MODE_EN, (msm_host->core_mem + CORE_HC_MODE)); From 57a1c4c67eab157f6f6519b79862a09ead9e3fba Mon Sep 17 00:00:00 2001 From: Yi Sun Date: Wed, 10 Jun 2015 16:40:06 -0700 Subject: [PATCH 283/472] mmc: enable Enhance Strobe for HS400 Enhance Strobe is defined in v5.1 eMMC spec. This commit is to implement it. Normal Strobe signal for HS400 is only provided during Data Out and CRC Response. While Enhance Strobe is enabled, Strobe signal is provided during Data Out, CRC Response and CMD Response. While enabling Enhance Strobe, the initialization of HS400 does not need enabling HS200 and executing tuning anymore. This simplifies the HS400 initialization process much. Per spec, there is a STROBE_SUPPORT added in EXT_CSD register to indicate that card supports Enhance Strobe or not. If it is supported, host can enable this feature by enabling the most significant bit of BUS_WIDTH before set HS_TIMING to HS400. Change-Id: I1003352cb31dfaec01fde352da7b879a13c94b3f Signed-off-by: Yi Sun [venkatg@codeaurora.org: Fix minor conflicts & compilation failure] Patch-mainline: linux-mmc @ 06/04/15, 19:50 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 62 ++++++++++++++++++++++++++++++++++++---- include/linux/mmc/card.h | 1 + include/linux/mmc/mmc.h | 2 ++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 3e0510b597b6..bbac42cbe6aa 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -613,6 +613,11 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) } if (card->ext_csd.rev >= 7) { + /* Enhance Strobe is supported since v5.1 which rev should be + * 8 but some eMMC devices can support it with rev 7. So handle + * Enhance Strobe here. + */ + card->ext_csd.strobe_support = ext_csd[EXT_CSD_STROBE_SUPPORT]; card->ext_csd.cmdq_support = ext_csd[EXT_CSD_CMDQ_SUPPORT]; if (card->ext_csd.cmdq_support) { /* @@ -1131,9 +1136,28 @@ static int mmc_select_hs400(struct mmc_card *card) /* * HS400 mode requires 8-bit bus width */ - if (!(card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400 && - host->ios.bus_width == MMC_BUS_WIDTH_8)) - return 0; + if (card->ext_csd.strobe_support) { + if (!(card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400 && + host->caps & MMC_CAP_8_BIT_DATA)) + return 0; + + /* For Enhance Strobe flow. For non Enhance Strobe, signal + * voltage will not be set. + */ + if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200_1_2V) + err = __mmc_set_signal_voltage(host, + MMC_SIGNAL_VOLTAGE_120); + + if (err && card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200_1_8V) + err = __mmc_set_signal_voltage(host, + MMC_SIGNAL_VOLTAGE_180); + if (err) + return err; + } else { + if (!(card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400 && + host->ios.bus_width == MMC_BUS_WIDTH_8)) + return 0; + } if (host->caps & MMC_CAP_WAIT_WHILE_BUSY) send_status = false; @@ -1164,10 +1188,14 @@ static int mmc_select_hs400(struct mmc_card *card) goto out_err; } + val = EXT_CSD_DDR_BUS_WIDTH_8; + if (card->ext_csd.strobe_support) + val |= EXT_CSD_BUS_WIDTH_STROBE; + /* Switch card to DDR */ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, - EXT_CSD_DDR_BUS_WIDTH_8, + val, card->ext_csd.generic_cmd6_time); if (err) { pr_err("%s: switch to bus width for hs400 failed, err:%d\n", @@ -1175,6 +1203,25 @@ static int mmc_select_hs400(struct mmc_card *card) return err; } + if (card->ext_csd.strobe_support) { + mmc_set_bus_width(host, MMC_BUS_WIDTH_8); + /* + * If controller can't handle bus width test, + * compare ext_csd previously read in 1 bit mode + * against ext_csd at new bus width + */ + if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST)) + err = mmc_compare_ext_csds(card, MMC_BUS_WIDTH_8); + else + err = mmc_bus_test(card, MMC_BUS_WIDTH_8); + + if (err) { + pr_warn("%s: switch to bus width %d failed\n", + mmc_hostname(host), MMC_BUS_WIDTH_8); + return err; + } + } + /* Switch card to HS400 */ val = EXT_CSD_TIMING_HS400 | card->drive_strength << EXT_CSD_DRV_STR_SHIFT; @@ -1400,7 +1447,12 @@ static int mmc_select_timing(struct mmc_card *card) if (!mmc_can_ext_csd(card)) goto bus_speed; - if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200) + /* For Enhance Strobe HS400 flow */ + if (card->ext_csd.strobe_support && + card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400 && + card->host->caps & MMC_CAP_8_BIT_DATA) + err = mmc_select_hs400(card); + else if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200) err = mmc_select_hs200(card); else if (card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS) err = mmc_select_hs(card); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 0f4e22db31ab..8289add95151 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -98,6 +98,7 @@ struct mmc_ext_csd { u8 raw_partition_support; /* 160 */ u8 raw_rpmb_size_mult; /* 168 */ u8 raw_erased_mem_count; /* 181 */ + u8 strobe_support; /* 184 */ u8 raw_ext_csd_structure; /* 194 */ u8 raw_card_type; /* 196 */ u8 raw_driver_strength; /* 197 */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 5612781ef522..aa1db902745f 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -247,6 +247,7 @@ struct _mmc_csd { #define EXT_CSD_PART_CONFIG 179 /* R/W */ #define EXT_CSD_ERASED_MEM_CONT 181 /* RO */ #define EXT_CSD_BUS_WIDTH 183 /* R/W */ +#define EXT_CSD_STROBE_SUPPORT 184 /* RO */ #define EXT_CSD_HS_TIMING 185 /* R/W */ #define EXT_CSD_POWER_CLASS 187 /* R/W */ #define EXT_CSD_REV 192 /* RO */ @@ -341,6 +342,7 @@ struct _mmc_csd { #define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */ #define EXT_CSD_DDR_BUS_WIDTH_4 5 /* Card is in 4 bit DDR mode */ #define EXT_CSD_DDR_BUS_WIDTH_8 6 /* Card is in 8 bit DDR mode */ +#define EXT_CSD_BUS_WIDTH_STROBE 0x80 /* Card is in 8 bit DDR mode */ #define EXT_CSD_TIMING_BC 0 /* Backwards compatility */ #define EXT_CSD_TIMING_HS 1 /* High speed */ From 506b393bf6e69fb7dbad718e18f0d033ef19da8f Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 10 Jun 2015 17:20:00 -0700 Subject: [PATCH 284/472] mmc: Add host ops for enhanced strobe Some hosts may need additional steps to get HS400 functional in enhanced strobe mode. Add host ops to facilitate that. Change-Id: I9663830e7ccedf8bf7970d0724a4c7ce212073fd Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 9 +++++++-- include/linux/mmc/host.h | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index bbac42cbe6aa..f4b6ca033487 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1245,7 +1245,12 @@ static int mmc_select_hs400(struct mmc_card *card) goto out_err; } - if ((host->caps2 & MMC_CAP2_HS400_POST_TUNING) && host->ops->execute_tuning) { + if (host->ops->enhanced_strobe) { + mmc_host_clk_hold(host); + err = host->ops->enhanced_strobe(host); + mmc_host_clk_release(host); + } else if ((host->caps2 & MMC_CAP2_HS400_POST_TUNING) && + host->ops->execute_tuning) { mmc_host_clk_hold(host); err = host->ops->execute_tuning(host, MMC_SEND_TUNING_BLOCK_HS200); @@ -1609,7 +1614,7 @@ static int mmc_scale_high(struct mmc_host *host) return err; } - return 0; + return err; } static int mmc_set_clock_bus_speed(struct mmc_card *card, unsigned long freq) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 6c6a699abea9..42e7b412e6f1 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -158,6 +158,7 @@ struct mmc_host_ops { /* Prepare HS400 target operating frequency depending host driver */ int (*prepare_hs400_tuning)(struct mmc_host *host, struct mmc_ios *ios); + int (*enhanced_strobe)(struct mmc_host *host); int (*select_drive_strength)(struct mmc_card *card, unsigned int max_dtr, int host_drv, int card_drv, int *drv_type); From ac15746a7b1f9aed83fb8eee70a13b6ff43a519e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 11 Jun 2015 18:27:27 -0700 Subject: [PATCH 285/472] mmc: Fix bus width setting in enhanced strobe mode The 8-bit bus width needs to be set first before switching to DDR bus width when entering HS400 in enhanced strobe mode. Also use the mmc_select_bus_width() for doing this instead of rewriting portion of that code. Change-Id: If9bec799de77714d7183c812a0ba04a9a4ac48f5 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index f4b6ca033487..e47e4658e754 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1189,8 +1189,12 @@ static int mmc_select_hs400(struct mmc_card *card) } val = EXT_CSD_DDR_BUS_WIDTH_8; - if (card->ext_csd.strobe_support) + if (card->ext_csd.strobe_support) { + err = mmc_select_bus_width(card); + if (IS_ERR_VALUE(err)) + return err; val |= EXT_CSD_BUS_WIDTH_STROBE; + } /* Switch card to DDR */ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, @@ -1203,25 +1207,6 @@ static int mmc_select_hs400(struct mmc_card *card) return err; } - if (card->ext_csd.strobe_support) { - mmc_set_bus_width(host, MMC_BUS_WIDTH_8); - /* - * If controller can't handle bus width test, - * compare ext_csd previously read in 1 bit mode - * against ext_csd at new bus width - */ - if (!(host->caps & MMC_CAP_BUS_WIDTH_TEST)) - err = mmc_compare_ext_csds(card, MMC_BUS_WIDTH_8); - else - err = mmc_bus_test(card, MMC_BUS_WIDTH_8); - - if (err) { - pr_warn("%s: switch to bus width %d failed\n", - mmc_hostname(host), MMC_BUS_WIDTH_8); - return err; - } - } - /* Switch card to HS400 */ val = EXT_CSD_TIMING_HS400 | card->drive_strength << EXT_CSD_DRV_STR_SHIFT; From 5254590eb277ed9d9822bfe97dc6798e2663c32e Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 27 May 2015 15:32:40 +0530 Subject: [PATCH 286/472] mmc: sdhci: Add hs400 enhanced strobe mode support to host This adds hs400 enhanced strobe mode(emmc 5.1 feature) support to sdhci host. Change-Id: Ib65905fa263553842cd55f38edccc135cac4bcf9 Signed-off-by: Ritesh Harjani Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 15 +++++++++++++++ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 16 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index b6a62ec35417..d1f804064082 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -59,6 +59,7 @@ static void sdhci_finish_data(struct sdhci_host *); static void sdhci_finish_command(struct sdhci_host *); static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode); +static int sdhci_enhanced_strobe(struct mmc_host *mmc); static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); static int sdhci_pre_dma_transfer(struct sdhci_host *host, struct mmc_data *data); @@ -2221,6 +2222,19 @@ static int sdhci_prepare_hs400_tuning(struct mmc_host *mmc, struct mmc_ios *ios) return 0; } +static int sdhci_enhanced_strobe(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + int err = 0; + + sdhci_runtime_pm_get(host); + if (host->ops->enhanced_strobe) + err = host->ops->enhanced_strobe(host); + sdhci_runtime_pm_put(host); + + return err; +} + static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) { struct sdhci_host *host = mmc_priv(mmc); @@ -2558,6 +2572,7 @@ static const struct mmc_host_ops sdhci_ops = { .start_signal_voltage_switch = sdhci_start_signal_voltage_switch, .prepare_hs400_tuning = sdhci_prepare_hs400_tuning, .execute_tuning = sdhci_execute_tuning, + .enhanced_strobe = sdhci_enhanced_strobe, .select_drive_strength = sdhci_select_drive_strength, .card_event = sdhci_card_event, .card_busy = sdhci_card_busy, diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 5e99cf53e3d2..304ca9533bf1 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -656,6 +656,7 @@ struct sdhci_ops { #define REQ_IO_LOW (1 << 2) #define REQ_IO_HIGH (1 << 3) void (*card_event)(struct sdhci_host *host); + int (*enhanced_strobe)(struct sdhci_host *host); void (*platform_bus_voting)(struct sdhci_host *host, u32 enable); void (*check_power_status)(struct sdhci_host *host, u32 req_type); int (*config_auto_tuning_cmd)(struct sdhci_host *host, From e925dcbfb5c794edde43ca6d42809c01bdcbc7d5 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 27 May 2015 15:40:24 +0530 Subject: [PATCH 287/472] mmc: sdhci-msm: Add hs400 enhanced strobe mode support to host This adds hs400 enhanced strobe mode(emmc 5.1 feature) support to sdhci-msm platform host. Change-Id: Id35e0b9e47ea725202c8e4a3ca479d52cc872920 Signed-off-by: Ritesh Harjani Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 52 ++++++++++++++++++++++++++++++++++-- drivers/mmc/host/sdhci-msm.h | 1 + 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 33cb44a36a5f..6633eec821cb 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -152,7 +152,9 @@ #define CORE_DDR_200_CFG 0x184 #define CORE_CDC_T4_DLY_SEL (1 << 0) +#define CORE_CMDIN_RCLK_EN (1 << 1) #define CORE_START_CDC_TRAFFIC (1 << 6) + #define CORE_VENDOR_SPEC3 0x1B0 #define CORE_PWRSAVE_DLL (1 << 3) @@ -772,6 +774,11 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) */ writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr + CORE_DDR_CONFIG); + if (msm_host->enhanced_strobe) + writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) + | CORE_CMDIN_RCLK_EN), + host->ioaddr + CORE_DDR_200_CFG); + /* Write 1 to DDR_CAL_EN field in CORE_DLL_CONFIG_2 */ writel_relaxed((readl_relaxed(host->ioaddr + CORE_DLL_CONFIG_2) | CORE_DDR_CAL_EN), @@ -806,6 +813,42 @@ out: return ret; } +static int sdhci_msm_enhanced_strobe(struct sdhci_host *host) +{ + int ret = 0; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct mmc_host *mmc = host->mmc; + + pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); + + if (!msm_host->enhanced_strobe) { + pr_debug("%s: host does not support hs400 enhanced strobe\n", + mmc_hostname(mmc)); + return -EINVAL; + } + + if (msm_host->calibration_done || + !(mmc->ios.timing == MMC_TIMING_MMC_HS400)) { + return 0; + } + + /* + * Reset the tuning block. + */ + ret = msm_init_cm_dll(host); + if (ret) + goto out; + + ret = sdhci_msm_cm_dll_sdc4_calibration(host); +out: + if (!ret) + msm_host->calibration_done = true; + pr_debug("%s: Exit %s, ret:%d\n", mmc_hostname(host->mmc), + __func__, ret); + return ret; +} + static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host) { int ret = 0; @@ -2544,7 +2587,8 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) * Select HS400 mode using the HC_SELECT_IN from VENDOR SPEC * register */ - if (msm_host->tuning_done && !msm_host->calibration_done) { + if ((msm_host->tuning_done || msm_host->enhanced_strobe) && + !msm_host->calibration_done) { /* * Write 0x6 to HC_SELECT_IN and 1 to HC_SELECT_IN_EN * field in VENDOR_SPEC_FUNC @@ -2773,6 +2817,7 @@ static struct sdhci_ops sdhci_msm_ops = { .set_uhs_signaling = sdhci_msm_set_uhs_signaling, .check_power_status = sdhci_msm_check_power_status, .platform_execute_tuning = sdhci_msm_execute_tuning, + .enhanced_strobe = sdhci_msm_enhanced_strobe, .toggle_cdr = sdhci_msm_toggle_cdr, .get_max_segments = sdhci_msm_max_segs, .set_clock = sdhci_msm_set_clock, @@ -2830,9 +2875,12 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, /* * SDCC 5 controller with major version 1, minor version 0x42 and later * will require additional steps when resetting DLL. + * It also supports HS400 enhanced strobe mode. */ - if ((major == 1) && (minor >= 0x42)) + if ((major == 1) && (minor >= 0x42)) { msm_host->use_updated_dll_reset = true; + msm_host->enhanced_strobe = true; + } /* * SDCC 5 controller with major version 1 and minor version 0x42, diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 708ab8ba3c3f..d61c50e1491f 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -154,6 +154,7 @@ struct sdhci_msm_host { bool use_cdclp533; bool use_updated_dll_reset; bool use_14lpp_dll; + bool enhanced_strobe; u32 caps_0; struct sdhci_msm_ice_data ice; u32 ice_clk_rate; From edb0c661e7c4d6acc611a1da950cd35024ea0db7 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 15 Jun 2015 10:12:53 -0700 Subject: [PATCH 288/472] mmc: Fix clock scaling for HS400 with enhanced strobe Much of the sequence to scale up to HS400 is not required when enhanced strobe mode for HS400 is supported. Handle scale up to HS400 appropriately in this case. Change-Id: I733f98bdb99e54d6cd3a074fcece2c89aaaee12f Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/core/mmc.c | 48 ++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index e47e4658e754..fe96eda51d72 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1563,33 +1563,35 @@ static int mmc_scale_high(struct mmc_host *host) { int err = 0; - if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200)) { - pr_err("%s: %s: card does not support HS200\n", - mmc_hostname(host), __func__); - WARN_ON(1); - return -EPERM; - } + if (!host->card->ext_csd.strobe_support) { + if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS200)) { + pr_err("%s: %s: card does not support HS200\n", + mmc_hostname(host), __func__); + WARN_ON(1); + return -EPERM; + } - err = mmc_select_hs200(host->card); - if (err) { - pr_err("%s: %s: selecting HS200 failed (%d)\n", - mmc_hostname(host), __func__, err); - return err; - } + err = mmc_select_hs200(host->card); + if (err) { + pr_err("%s: %s: selecting HS200 failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } - mmc_set_bus_speed(host->card); + mmc_set_bus_speed(host->card); - err = mmc_hs200_tuning(host->card); - if (err) { - pr_err("%s: %s: hs200 tuning failed (%d)\n", - mmc_hostname(host), __func__, err); - return err; - } + err = mmc_hs200_tuning(host->card); + if (err) { + pr_err("%s: %s: hs200 tuning failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } - if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400)) { - pr_debug("%s: card does not support HS400\n", - mmc_hostname(host)); - return 0; + if (!(host->card->mmc_avail_type & EXT_CSD_CARD_TYPE_HS400)) { + pr_debug("%s: card does not support HS400\n", + mmc_hostname(host)); + return 0; + } } err = mmc_select_hs400(host->card); From 37927a22fe1c03a9ec3f410af4ffae1707330918 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Thu, 25 Jun 2015 09:30:04 +0300 Subject: [PATCH 289/472] mmc: clk-scaling: change locking from irq_save to bh This commit changes the spinlock used by clock scaling logic to use the bh flavor instead of irqsave. This is done in order to avoid the unnecessary irq disable while going into clock scaling critical sections. Change-Id: Id660db89dc531336621f40908fd3a4ad4777a12d Signed-off-by: Talel Shenhar --- drivers/mmc/core/core.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index e108a234dd07..ed4e383c0482 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -141,32 +141,30 @@ static bool mmc_is_data_request(struct mmc_request *mmc_request) static void mmc_clk_scaling_start_busy(struct mmc_host *host, bool lock_needed) { - unsigned long flags; struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; if (!clk_scaling->enable) return; if (lock_needed) - spin_lock_irqsave(&clk_scaling->lock, flags); + spin_lock_bh(&clk_scaling->lock); clk_scaling->start_busy = ktime_get(); clk_scaling->is_busy_started = true; if (lock_needed) - spin_unlock_irqrestore(&clk_scaling->lock, flags); + spin_unlock_bh(&clk_scaling->lock); } static void mmc_clk_scaling_stop_busy(struct mmc_host *host, bool lock_needed) { - unsigned long flags; struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; if (!clk_scaling->enable) return; if (lock_needed) - spin_lock_irqsave(&clk_scaling->lock, flags); + spin_lock_bh(&clk_scaling->lock); if (!clk_scaling->is_busy_started) { WARN_ON(1); @@ -182,7 +180,7 @@ static void mmc_clk_scaling_stop_busy(struct mmc_host *host, bool lock_needed) out: if (lock_needed) - spin_unlock_irqrestore(&clk_scaling->lock, flags); + spin_unlock_bh(&clk_scaling->lock); } /** @@ -223,7 +221,6 @@ EXPORT_SYMBOL(mmc_can_scale_clk); static int mmc_devfreq_get_dev_status(struct device *dev, struct devfreq_dev_status *status) { - unsigned long flags; struct mmc_host *host = container_of(dev, struct mmc_host, class_dev); struct mmc_devfeq_clk_scaling *clk_scaling; @@ -238,7 +235,7 @@ static int mmc_devfreq_get_dev_status(struct device *dev, if (!clk_scaling->enable) return 0; - spin_lock_irqsave(&clk_scaling->lock, flags); + spin_lock_bh(&clk_scaling->lock); /* accumulate the busy time of ongoing work */ memset(status, 0, sizeof(*status)); @@ -260,7 +257,7 @@ static int mmc_devfreq_get_dev_status(struct device *dev, status->total_time, status->busy_time, status->current_frequency); - spin_unlock_irqrestore(&clk_scaling->lock, flags); + spin_unlock_bh(&clk_scaling->lock); return 0; } @@ -358,7 +355,6 @@ static int mmc_devfreq_set_target(struct device *dev, struct mmc_devfeq_clk_scaling *clk_scaling; int err = 0; int abort; - unsigned long flags; if (!(host && freq)) { pr_err("%s: unexpected host/freq parameter\n", __func__); @@ -381,18 +377,18 @@ static int mmc_devfreq_set_target(struct device *dev, if (!host->ios.clock) goto out; - spin_lock_irqsave(&clk_scaling->lock, flags); + spin_lock_bh(&clk_scaling->lock); if (clk_scaling->clk_scaling_in_progress) { pr_debug("%s: clocks scaling is already in-progress by mmc thread\n", mmc_hostname(host)); - spin_unlock_irqrestore(&clk_scaling->lock, flags); + spin_unlock_bh(&clk_scaling->lock); goto out; } clk_scaling->need_freq_change = true; clk_scaling->target_freq = *freq; clk_scaling->state = *freq < clk_scaling->curr_freq ? MMC_LOAD_LOW : MMC_LOAD_HIGH; - spin_unlock_irqrestore(&clk_scaling->lock, flags); + spin_unlock_bh(&clk_scaling->lock); abort = __mmc_claim_host(host, &clk_scaling->devfreq_abort); if (abort) @@ -422,18 +418,17 @@ out: static void mmc_deferred_scaling(struct mmc_host *host) { - unsigned long flags; unsigned long target_freq; int err; if (!host->clk_scaling.enable) return; - spin_lock_irqsave(&host->clk_scaling.lock, flags); + spin_lock_bh(&host->clk_scaling.lock); if (host->clk_scaling.clk_scaling_in_progress || !(host->clk_scaling.need_freq_change)) { - spin_unlock_irqrestore(&host->clk_scaling.lock, flags); + spin_unlock_bh(&host->clk_scaling.lock); return; } @@ -442,7 +437,7 @@ static void mmc_deferred_scaling(struct mmc_host *host) target_freq = host->clk_scaling.target_freq; host->clk_scaling.clk_scaling_in_progress = true; host->clk_scaling.need_freq_change = false; - spin_unlock_irqrestore(&host->clk_scaling.lock, flags); + spin_unlock_bh(&host->clk_scaling.lock); pr_debug("%s: doing deferred frequency change (%lu) (%s)\n", mmc_hostname(host), target_freq, current->comm); @@ -659,8 +654,6 @@ EXPORT_SYMBOL(mmc_exit_clk_scaling); */ void mmc_reset_clk_scale_stats(struct mmc_host *host) { - unsigned long flags; - if (!host) { pr_err("bad host parameter\n"); WARN_ON(1); @@ -668,9 +661,9 @@ void mmc_reset_clk_scale_stats(struct mmc_host *host) } if (!host->clk_scaling.enable) return; - spin_lock_irqsave(&host->clk_scaling.lock, flags); + spin_lock_bh(&host->clk_scaling.lock); host->clk_scaling.total_busy_time_us = 0; - spin_unlock_irqrestore(&host->clk_scaling.lock, flags); + spin_unlock_bh(&host->clk_scaling.lock); } EXPORT_SYMBOL(mmc_reset_clk_scale_stats); From fda56078bbf3b05a9bb1c2ffe3d3616dd15714d5 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Wed, 24 Jun 2015 19:51:58 +0300 Subject: [PATCH 290/472] mmc: add support for bkops Add support for manual and auto bkops for legacy (not command-queue) mode. Change-Id: I1333e1d4330393e446ba48a9474c83295744c050 Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed merge conflicts and compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 20 ++++- drivers/mmc/core/bus.c | 12 ++- drivers/mmc/core/core.c | 156 ++++++++++++++++++++++++++++----------- drivers/mmc/core/core.h | 16 ---- drivers/mmc/core/mmc.c | 54 +++++++++++++- include/linux/mmc/card.h | 29 +++++++- include/linux/mmc/core.h | 21 +++++- include/linux/mmc/host.h | 2 + include/linux/mmc/mmc.h | 3 + 9 files changed, 247 insertions(+), 66 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 5d95402c5fb2..006d112f11db 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -43,6 +43,7 @@ #include #include +#include #include #include #include @@ -3405,10 +3406,17 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) unsigned long flags; unsigned int cmd_flags = req ? req->cmd_flags : 0; - if (req && !mq->mqrq_prev->req) + if (req && !mq->mqrq_prev->req) { /* claim host only for the first request */ mmc_get_card(card); + if (mmc_card_doing_bkops(host->card)) { + ret = mmc_stop_bkops(host->card); + if (ret) + goto out; + } + } + ret = mmc_blk_part_switch(card, md); if (ret) { if (req) { @@ -3951,8 +3959,16 @@ static int mmc_blk_probe(struct mmc_card *card) goto out; } - pm_runtime_set_autosuspend_delay(&card->dev, 3000); pm_runtime_use_autosuspend(&card->dev); + pm_runtime_set_autosuspend_delay(&card->dev, MMC_AUTOSUSPEND_DELAY_MS); + /* + * If there is a runtime_idle function, it should take care of + * suspending the card + */ + if (card->host->bus_ops->runtime_idle) + pm_runtime_dont_use_autosuspend(&card->dev); + else + pm_runtime_use_autosuspend(&card->dev); /* * Don't enable runtime PM for SD-combo cards here. Leave that diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index af884a552d5e..be7773f40a90 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -200,10 +200,20 @@ static int mmc_runtime_resume(struct device *dev) return host->bus_ops->runtime_resume(host); } + +static int mmc_runtime_idle(struct device *dev) +{ + struct mmc_card *card = mmc_dev_to_card(dev); + struct mmc_host *host = card->host; + + return host->bus_ops->runtime_idle(host); +} + #endif /* !CONFIG_PM */ static const struct dev_pm_ops mmc_bus_pm_ops = { - SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, NULL) + SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, + mmc_runtime_idle) SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume) }; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index ed4e383c0482..8db21b61a2ae 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -913,24 +913,70 @@ static void mmc_start_cmdq_request(struct mmc_host *host, } /** - * mmc_start_bkops - start BKOPS for supported cards + * mmc_set_auto_bkops - set auto 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 + * @enable: enable/disable flag + * Configure the card to run automatic BKOPS. * - * Start background operations whenever requested. - * When the urgent BKOPS bit is set in a R1 command response - * then background operations should be started immediately. + * Should be called when host is claimed. */ -void mmc_start_bkops(struct mmc_card *card, bool from_exception) +int mmc_set_auto_bkops(struct mmc_card *card, bool enable) +{ + int ret = 0; + u8 bkops_en; + + BUG_ON(!card); + enable = !!enable; + + if (unlikely(!mmc_card_support_auto_bkops(card))) { + pr_err("%s: %s: card doesn't support auto bkops\n", + mmc_hostname(card->host), __func__); + return -EPERM; + } + + if (enable) { + if (mmc_card_doing_auto_bkops(card)) + goto out; + bkops_en = card->ext_csd.man_bkops_en | EXT_CSD_BKOPS_AUTO_EN; + } else { + if (!mmc_card_doing_auto_bkops(card)) + goto out; + bkops_en = card->ext_csd.man_bkops_en & ~EXT_CSD_BKOPS_AUTO_EN; + } + + ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BKOPS_EN, + bkops_en, 0); + if (ret) { + pr_err("%s: %s: error in setting auto bkops to %d (%d)\n", + mmc_hostname(card->host), __func__, enable, ret); + } else { + if (enable) + mmc_card_set_auto_bkops(card); + else + mmc_card_clr_auto_bkops(card); + card->ext_csd.man_bkops_en = bkops_en; + } +out: + return ret; +} +EXPORT_SYMBOL(mmc_set_auto_bkops); + +/** + * mmc_check_bkops - check BKOPS for supported cards + * @card: MMC card to check BKOPS + * + * Read the BKOPS status in order to determine whether the + * card requires bkops to be started. +*/ +void mmc_check_bkops(struct mmc_card *card) { int err; - int timeout; - bool use_busy_signal; BUG_ON(!card); - if (!card->ext_csd.man_bkops_en || mmc_card_doing_bkops(card)) + if (unlikely(!mmc_card_configured_manual_bkops(card))) + return; + if (mmc_card_doing_bkops(card) || mmc_card_doing_auto_bkops(card)) return; err = mmc_read_bkops_status(card); @@ -940,47 +986,48 @@ void mmc_start_bkops(struct mmc_card *card, bool from_exception) return; } - if (!card->ext_csd.raw_bkops_status) + if (card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2) return; - if (card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2 && - from_exception) + card->bkops.needs_manual = true; +} +EXPORT_SYMBOL(mmc_check_bkops); + +/** + * mmc_start_manual_bkops - start BKOPS for supported cards + * @card: MMC card to start BKOPS + * + * Send START_BKOPS to the card. +*/ +void mmc_start_manual_bkops(struct mmc_card *card) +{ + int err; + + BUG_ON(!card); + + if (unlikely(!mmc_card_configured_manual_bkops(card))) + return; + + if (mmc_card_doing_bkops(card)) 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; - } - mmc_retune_hold(card->host); - err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_BKOPS_START, 1, timeout, - use_busy_signal, true, false); + err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BKOPS_START, + 1, 0, false, true, false); if (err) { - pr_warn("%s: Error %d starting bkops\n", - mmc_hostname(card->host), err); - mmc_retune_release(card->host); - goto out; + pr_err("%s: Error %d starting manual bkops\n", + mmc_hostname(card->host), err); + } else { + mmc_card_set_doing_bkops(card); + card->bkops.needs_manual = false; } - /* - * 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); - else - mmc_retune_release(card->host); -out: + mmc_retune_release(card->host); mmc_release_host(card->host); } -EXPORT_SYMBOL(mmc_start_bkops); +EXPORT_SYMBOL(mmc_start_manual_bkops); /* * mmc_wait_data_done() - done callback for data request @@ -1347,7 +1394,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (areq) mmc_post_req(host, areq->mrq, -EINVAL); - mmc_start_bkops(host->card, true); + mmc_check_bkops(host->card); /* prepare the request again */ if (areq) @@ -1509,6 +1556,11 @@ int mmc_stop_bkops(struct mmc_card *card) int err = 0; BUG_ON(!card); + if (unlikely(!mmc_card_configured_manual_bkops(card))) + goto out; + if (!mmc_card_doing_bkops(card)) + goto out; + err = mmc_interrupt_hpi(card); /* @@ -1520,7 +1572,7 @@ int mmc_stop_bkops(struct mmc_card *card) mmc_retune_release(card->host); err = 0; } - +out: return err; } EXPORT_SYMBOL(mmc_stop_bkops); @@ -1776,6 +1828,19 @@ void mmc_get_card(struct mmc_card *card) } EXPORT_SYMBOL(mmc_get_card); +/* + * This is a helper function, which drops the runtime + * pm reference for the card device. + */ +static void __mmc_put_card(struct mmc_card *card) +{ + /* In case of runtime_idle, it will handle the suspend */ + if (card->host->bus_ops->runtime_idle) + pm_runtime_put(&card->dev); + else + pm_runtime_put_autosuspend(&card->dev); +} + /* * This is a helper function, which releases the host and drops the runtime * pm reference for the card device. @@ -1784,7 +1849,7 @@ void mmc_put_card(struct mmc_card *card) { mmc_release_host(card->host); pm_runtime_mark_last_busy(&card->dev); - pm_runtime_put_autosuspend(&card->dev); + __mmc_put_card(card); } EXPORT_SYMBOL(mmc_put_card); @@ -1796,6 +1861,13 @@ void mmc_set_ios(struct mmc_host *host) { struct mmc_ios *ios = &host->ios; + if (unlikely(ios->power_mode == MMC_POWER_OFF && + host->card && mmc_card_doing_auto_bkops(host->card))) { + pr_err("%s: %s: illegal to disable power to card when running auto bkops\n", + mmc_hostname(host), __func__); + return; + } + pr_debug("%s: clock %uHz busmode %u powermode %u cs %u Vdd %u " "width %u timing %u\n", mmc_hostname(host), ios->clock, ios->bus_mode, diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index c4ab8ce9fd90..af0b13a492a5 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -15,22 +15,6 @@ #define MMC_CMD_RETRIES 3 -struct mmc_bus_ops { - void (*remove)(struct mmc_host *); - void (*detect)(struct mmc_host *); - int (*pre_suspend)(struct mmc_host *); - int (*suspend)(struct mmc_host *); - int (*resume)(struct mmc_host *); - int (*runtime_suspend)(struct mmc_host *); - int (*runtime_resume)(struct mmc_host *); - int (*power_save)(struct mmc_host *); - int (*power_restore)(struct mmc_host *); - int (*alive)(struct mmc_host *); - int (*shutdown)(struct mmc_host *); - int (*reset)(struct mmc_host *); - int (*change_bus_speed)(struct mmc_host *, unsigned long *); -}; - void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops); void mmc_detach_bus(struct mmc_host *host); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index fe96eda51d72..3f68765323ea 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -518,6 +518,9 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) */ card->ext_csd.out_of_int_time = ext_csd[EXT_CSD_OUT_OF_INTERRUPT_TIME] * 10; + pr_info("%s: Out-of-interrupt timeout is %d[ms]\n", + mmc_hostname(card->host), + card->ext_csd.out_of_int_time); } if (card->ext_csd.rev >= 5) { @@ -535,8 +538,11 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.raw_bkops_status = ext_csd[EXT_CSD_BKOPS_STATUS]; if (!card->ext_csd.man_bkops_en) - pr_info("%s: MAN_BKOPS_EN bit is not set\n", - mmc_hostname(card->host)); + pr_info("%s: BKOPS_EN equals 0x%x\n", + mmc_hostname(card->host), + card->ext_csd.man_bkops_en); + + } /* check whether the eMMC card supports HPI */ @@ -2132,6 +2138,23 @@ reinit: } } + /* + * Start auto bkops, if supported. + * + * Note: This leaves the possibility of having both manual and + * auto bkops running in parallel. The runtime implementation + * will allow this, but ignore bkops exceptions on the premises + * that auto bkops will eventually kick in and the device will + * handle bkops without START_BKOPS from the host. + */ + if (mmc_card_support_auto_bkops(card)) { + /* + * Ignore the return value of setting auto bkops. + * If it failed, will run in backward compatible mode. + */ + (void)mmc_set_auto_bkops(card, true); + } + return 0; free_card: @@ -2335,6 +2358,12 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) goto out; } + if (mmc_card_doing_auto_bkops(host->card)) { + err = mmc_set_auto_bkops(host->card, false); + if (err) + goto out; + } + err = mmc_flush_cache(host->card); if (err) goto out; @@ -2547,6 +2576,26 @@ static int mmc_reset(struct mmc_host *host) return mmc_init_card(host, card->ocr, card); } +#define NO_AUTOSUSPEND (-1) +static int mmc_runtime_idle(struct mmc_host *host) +{ + BUG_ON(!host->card); + + if (host->card->bkops.needs_manual) + mmc_start_manual_bkops(host->card); + + pm_runtime_mark_last_busy(&host->card->dev); + /* + * TODO: consider prolonging suspend when bkops + * is running in order to allow a longer time for + * bkops to complete + * */ + pm_schedule_suspend(&host->card->dev, MMC_AUTOSUSPEND_DELAY_MS); + + /* return negative value in order to avoid autosuspend */ + return NO_AUTOSUSPEND; +} + static const struct mmc_bus_ops mmc_ops = { .remove = mmc_remove, .detect = mmc_detect, @@ -2554,6 +2603,7 @@ static const struct mmc_bus_ops mmc_ops = { .resume = mmc_resume, .runtime_suspend = mmc_runtime_suspend, .runtime_resume = mmc_runtime_resume, + .runtime_idle = mmc_runtime_idle, .alive = mmc_alive, .shutdown = mmc_shutdown, .change_bus_speed = mmc_change_bus_speed, diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 8289add95151..80ce6cc36b88 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -86,7 +87,7 @@ struct mmc_ext_csd { bool hpi; /* HPI support bit */ unsigned int hpi_cmd; /* cmd used as HPI */ bool bkops; /* background support bit */ - bool man_bkops_en; /* manual bkops enable bit */ + u8 man_bkops_en; /* manual bkops enable */ 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 */ @@ -274,6 +275,15 @@ struct mmc_part { #define MMC_BLK_DATA_AREA_RPMB (1<<3) }; +/** + * struct mmc_bkops_info - BKOPS data + * @need_manual: indication whether have to send START_BKOPS + * to the device + */ +struct mmc_bkops_info { + bool needs_manual; +}; + /* * MMC device */ @@ -297,9 +307,10 @@ struct mmc_card { #define MMC_STATE_BLOCKADDR (1<<2) /* card uses block-addressing */ #define MMC_CARD_SDXC (1<<3) /* card is SDXC */ #define MMC_CARD_REMOVED (1<<4) /* card has been removed */ -#define MMC_STATE_DOING_BKOPS (1<<5) /* card is doing BKOPS */ +#define MMC_STATE_DOING_BKOPS (1<<5) /* card is doing manual BKOPS */ #define MMC_STATE_SUSPENDED (1<<6) /* card is suspended */ #define MMC_STATE_CMDQ (1<<12) /* card is in cmd queue mode */ +#define MMC_STATE_AUTO_BKOPS (1<<13) /* card is doing auto 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 */ @@ -363,6 +374,7 @@ struct mmc_card { bool issue_long_pon; u8 *cached_ext_csd; bool cmdq_init; + struct mmc_bkops_info bkops; }; /* @@ -512,6 +524,7 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_doing_bkops(c) ((c)->state & MMC_STATE_DOING_BKOPS) #define mmc_card_suspended(c) ((c)->state & MMC_STATE_SUSPENDED) #define mmc_card_cmdq(c) ((c)->state & MMC_STATE_CMDQ) +#define mmc_card_doing_auto_bkops(c) ((c)->state & MMC_STATE_AUTO_BKOPS) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) @@ -524,6 +537,8 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_clr_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED) #define mmc_card_set_cmdq(c) ((c)->state |= MMC_STATE_CMDQ) #define mmc_card_clr_cmdq(c) ((c)->state &= ~MMC_STATE_CMDQ) +#define mmc_card_set_auto_bkops(c) ((c)->state |= MMC_STATE_AUTO_BKOPS) +#define mmc_card_clr_auto_bkops(c) ((c)->state &= ~MMC_STATE_AUTO_BKOPS) /* * Quirk add/remove for MMC products. @@ -594,6 +609,16 @@ static inline int mmc_card_broken_irq_polling(const struct mmc_card *c) return c->quirks & MMC_QUIRK_BROKEN_IRQ_POLLING; } +static inline bool mmc_card_support_auto_bkops(const struct mmc_card *c) +{ + return c->ext_csd.rev >= MMC_V5_1; +} + +static inline bool mmc_card_configured_manual_bkops(const struct mmc_card *c) +{ + return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_MANUAL_EN; +} + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 22c610609a25..446b56765dbb 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -112,6 +112,23 @@ struct mmc_request { struct request *req; }; +struct mmc_bus_ops { + void (*remove)(struct mmc_host *); + void (*detect)(struct mmc_host *); + int (*pre_suspend)(struct mmc_host *); + int (*suspend)(struct mmc_host *); + int (*resume)(struct mmc_host *); + int (*runtime_suspend)(struct mmc_host *); + int (*runtime_resume)(struct mmc_host *); + int (*runtime_idle)(struct mmc_host *); + int (*power_save)(struct mmc_host *); + int (*power_restore)(struct mmc_host *); + int (*alive)(struct mmc_host *); + int (*shutdown)(struct mmc_host *); + int (*reset)(struct mmc_host *); + int (*change_bus_speed)(struct mmc_host *, unsigned long *); +}; + struct mmc_card; struct mmc_async_req; struct mmc_cmdq_req; @@ -139,13 +156,15 @@ 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 void mmc_check_bkops(struct mmc_card *card); +extern void mmc_start_manual_bkops(struct mmc_card *card); extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); extern int __mmc_switch_cmdq_mode(struct mmc_command *cmd, u8 set, u8 index, u8 value, unsigned int timeout_ms, bool use_busy_signal, bool ignore_timeout); extern int mmc_send_tuning(struct mmc_host *host, u32 opcode, int *cmd_error); extern int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd); +extern int mmc_set_auto_bkops(struct mmc_card *card, bool enable); #define MMC_ERASE_ARG 0x00000000 #define MMC_SECURE_ERASE_ARG 0x80000000 diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 42e7b412e6f1..cb46cdf8dcf3 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -22,6 +22,8 @@ #include #include +#define MMC_AUTOSUSPEND_DELAY_MS 3000 + struct mmc_ios { unsigned int clock; /* clock rate */ unsigned int old_rate; /* saved clock rate */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index aa1db902745f..1cde2e5eb283 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -370,6 +370,9 @@ struct _mmc_csd { #define EXT_CSD_PACKED_EVENT_EN BIT(3) +#define EXT_CSD_BKOPS_MANUAL_EN BIT(0) +#define EXT_CSD_BKOPS_AUTO_EN BIT(1) + /* * EXCEPTION_EVENT_STATUS field */ From 398aedae58e9c5e7b5c712c8b148205d9cd2078e Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 25 Jun 2015 12:00:02 -0700 Subject: [PATCH 291/472] mmc: sdhci-msm: Fix compatible list termination Compatible list should be terminated with NULL entry, fix it. Change-Id: Id9be4fa95fd1cbbbe28a3c63b63bbd6c8c29fa85 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 6633eec821cb..c9ca4e0d4ef2 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3537,6 +3537,7 @@ static const struct dev_pm_ops sdhci_msm_pmops = { #endif static const struct of_device_id sdhci_msm_dt_match[] = { {.compatible = "qcom,sdhci-msm"}, + {}, }; MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); From 650d2e10860ce99b7430d04908c226ff2470c3ab Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 28 Jun 2015 10:25:55 +0300 Subject: [PATCH 292/472] mmc: core: ignore cmdq halt failure when no change is needed During system suspend CQE halted by queue suspend even before mmc host suspend. When mmc host suspend calls halt helper, CQE is already halted. This change ignores error value of halt helper. Change-Id: I2d1456333f1d04f0e7a77485443b80a0ccaa3b4e Signed-off-by: Konstantin Dorfman --- drivers/mmc/core/core.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 8db21b61a2ae..d317205648ff 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1275,8 +1275,12 @@ int mmc_cmdq_halt(struct mmc_host *host, bool halt) int err = 0; if ((halt && mmc_host_halt(host)) || - (!halt && !mmc_host_halt(host))) - return -EINVAL; + (!halt && !mmc_host_halt(host))) { + pr_debug("%s: %s: CQE is already %s\n", mmc_hostname(host), + __func__, halt ? "halted" : "un-halted"); + return 0; + } + mmc_host_clk_hold(host); if (host->cmdq_ops->halt) { err = host->cmdq_ops->halt(host, halt); From b98e6c2fa1dd4f4d3ec89f7a9674174b539f83c2 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Sun, 28 Jun 2015 16:45:41 +0300 Subject: [PATCH 293/472] mmc: core: Add MMC BKOPS statistics and debugfs ability to print them The BKOPS statistics are used for BKOPS unit tests and APT tests to determine test success or failure. The BKOPS statstics provide the following information: The number of times BKOPS were issued according to its severity level The number of times manual BKOPS was started/stopped (HPI) The number of times auto BKOPS was enabled/disabled In order to enable and reset the statistics: echo 1 > /sys/kernel/debug/mmc0/mmc0:0001/bkops_stats In order to disable the statistics: echo 0 > /sys/kernel/debug/mmc0/mmc0:0001/bkops_stats In order to view the statistics: cat /sys/kernel/debug/mmc0/mmc0:0001/bkops_stats Change-Id: Ib84319aedfb49dc022bc27efbda842a5db38c7e9 Signed-off-by: Yaniv Gardi Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/bus.c | 1 + drivers/mmc/core/core.c | 82 +++++++++++++++++++++++++++++++++- drivers/mmc/core/debugfs.c | 90 ++++++++++++++++++++++++++++++++++++++ include/linux/mmc/card.h | 36 +++++++++++++++ include/linux/mmc/core.h | 2 + 5 files changed, 209 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index be7773f40a90..c3d058313e43 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -294,6 +294,7 @@ struct mmc_card *mmc_alloc_card(struct mmc_host *host, struct device_type *type) card->dev.type = type; spin_lock_init(&card->wr_pack_stats.lock); + spin_lock_init(&card->bkops.stats.lock); return card; } diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d317205648ff..55ad10037a6c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -912,6 +912,77 @@ static void mmc_start_cmdq_request(struct mmc_host *host, __func__); } +/** + * mmc_blk_init_bkops_statistics - initialize bkops statistics + * @card: MMC card to start BKOPS + * + * Initialize and enable the bkops statistics + */ +void mmc_blk_init_bkops_statistics(struct mmc_card *card) +{ + int i; + struct mmc_bkops_stats *stats; + + if (!card) + return; + + stats = &card->bkops.stats; + spin_lock(&stats->lock); + + stats->manual_start = 0; + stats->hpi = 0; + stats->auto_start = 0; + stats->auto_stop = 0; + for (i = 0 ; i < MMC_BKOPS_NUM_SEVERITY_LEVELS ; i++) + stats->level[i] = 0; + stats->enabled = true; + + spin_unlock(&stats->lock); +} +EXPORT_SYMBOL(mmc_blk_init_bkops_statistics); + +static void mmc_update_bkops_hpi(struct mmc_bkops_stats *stats) +{ + spin_lock_irq(&stats->lock); + if (stats->enabled) + stats->hpi++; + spin_unlock_irq(&stats->lock); +} + +static void mmc_update_bkops_start(struct mmc_bkops_stats *stats) +{ + spin_lock_irq(&stats->lock); + if (stats->enabled) + stats->manual_start++; + spin_unlock_irq(&stats->lock); +} + +static void mmc_update_bkops_auto_on(struct mmc_bkops_stats *stats) +{ + spin_lock_irq(&stats->lock); + if (stats->enabled) + stats->auto_start++; + spin_unlock_irq(&stats->lock); +} + +static void mmc_update_bkops_auto_off(struct mmc_bkops_stats *stats) +{ + spin_lock_irq(&stats->lock); + if (stats->enabled) + stats->auto_stop++; + spin_unlock_irq(&stats->lock); +} + +static void mmc_update_bkops_level(struct mmc_bkops_stats *stats, + unsigned level) +{ + BUG_ON(level >= MMC_BKOPS_NUM_SEVERITY_LEVELS); + spin_lock_irq(&stats->lock); + if (stats->enabled) + stats->level[level]++; + spin_unlock_irq(&stats->lock); +} + /** * mmc_set_auto_bkops - set auto BKOPS for supported cards * @card: MMC card to start BKOPS @@ -950,10 +1021,13 @@ int mmc_set_auto_bkops(struct mmc_card *card, bool enable) pr_err("%s: %s: error in setting auto bkops to %d (%d)\n", mmc_hostname(card->host), __func__, enable, ret); } else { - if (enable) + if (enable) { mmc_card_set_auto_bkops(card); - else + mmc_update_bkops_auto_on(&card->bkops.stats); + } else { mmc_card_clr_auto_bkops(card); + mmc_update_bkops_auto_off(&card->bkops.stats); + } card->ext_csd.man_bkops_en = bkops_en; } out: @@ -986,6 +1060,8 @@ void mmc_check_bkops(struct mmc_card *card) return; } + mmc_update_bkops_level(&card->bkops.stats, + card->ext_csd.raw_bkops_status); if (card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2) return; @@ -1021,6 +1097,7 @@ void mmc_start_manual_bkops(struct mmc_card *card) mmc_hostname(card->host), err); } else { mmc_card_set_doing_bkops(card); + mmc_update_bkops_start(&card->bkops.stats); card->bkops.needs_manual = false; } @@ -1573,6 +1650,7 @@ int mmc_stop_bkops(struct mmc_card *card) */ if (!err || (err == -EINVAL)) { mmc_card_clr_doing_bkops(card); + mmc_update_bkops_hpi(&card->bkops.stats); mmc_retune_release(card->host); err = 0; } diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 9f75060d3b0c..7550db69f76f 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -637,6 +637,89 @@ static const struct file_operations mmc_dbg_wr_pack_stats_fops = { .write = mmc_wr_pack_stats_write, }; +static int mmc_bkops_stats_read(struct seq_file *file, void *data) +{ + struct mmc_card *card = file->private; + struct mmc_bkops_stats *stats; + int i; + + if (!card) + return -EINVAL; + + stats = &card->bkops.stats; + + if (!stats->enabled) { + pr_info("%s: bkops statistics are disabled\n", + mmc_hostname(card->host)); + goto exit; + } + + spin_lock(&stats->lock); + + seq_printf(file, "%s: bkops statistics:\n", + mmc_hostname(card->host)); + seq_printf(file, "%s: BKOPS: sent START_BKOPS to device: %u\n", + mmc_hostname(card->host), stats->manual_start); + seq_printf(file, "%s: BKOPS: stopped due to HPI: %u\n", + mmc_hostname(card->host), stats->hpi); + seq_printf(file, "%s: BKOPS: sent AUTO_EN set to 1: %u\n", + mmc_hostname(card->host), stats->auto_start); + seq_printf(file, "%s: BKOPS: sent AUTO_EN set to 0: %u\n", + mmc_hostname(card->host), stats->auto_stop); + + for (i = 0 ; i < MMC_BKOPS_NUM_SEVERITY_LEVELS ; ++i) + seq_printf(file, "%s: BKOPS: due to level %d: %u\n", + mmc_hostname(card->host), i, stats->level[i]); + + spin_unlock(&stats->lock); + +exit: + + return 0; +} + +static ssize_t mmc_bkops_stats_write(struct file *filp, + const char __user *ubuf, size_t cnt, + loff_t *ppos) +{ + struct mmc_card *card = filp->f_mapping->host->i_private; + int value; + struct mmc_bkops_stats *stats; + int err; + + if (!card) + return cnt; + + stats = &card->bkops.stats; + + err = kstrtoint_from_user(ubuf, cnt, 0, &value); + if (err) { + pr_err("%s: %s: error parsing input from user (%d)\n", + mmc_hostname(card->host), __func__, err); + return err; + } + if (value) { + mmc_blk_init_bkops_statistics(card); + } else { + spin_lock(&stats->lock); + stats->enabled = false; + spin_unlock(&stats->lock); + } + + return cnt; +} + +static int mmc_bkops_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, mmc_bkops_stats_read, inode->i_private); +} + +static const struct file_operations mmc_dbg_bkops_stats_fops = { + .open = mmc_bkops_stats_open, + .read = seq_read, + .write = mmc_bkops_stats_write, +}; + void mmc_add_card_debugfs(struct mmc_card *card) { struct mmc_host *host = card->host; @@ -675,6 +758,13 @@ void mmc_add_card_debugfs(struct mmc_card *card) &mmc_dbg_wr_pack_stats_fops)) goto err; + if (mmc_card_mmc(card) && (card->ext_csd.rev >= 5) && + (mmc_card_support_auto_bkops(card) || + mmc_card_configured_manual_bkops(card))) + if (!debugfs_create_file("bkops_stats", S_IRUSR, root, card, + &mmc_dbg_bkops_stats_fops)) + goto err; + return; err: diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 80ce6cc36b88..895fc0ceaf6f 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -275,12 +275,48 @@ struct mmc_part { #define MMC_BLK_DATA_AREA_RPMB (1<<3) }; +enum { + MMC_BKOPS_NO_OP, + MMC_BKOPS_NOT_CRITICAL, + MMC_BKOPS_PERF_IMPACT, + MMC_BKOPS_CRITICAL, + MMC_BKOPS_NUM_SEVERITY_LEVELS, +}; + +/** + * struct mmc_bkops_stats - BKOPS statistics + * @lock: spinlock used for synchronizing the debugfs and the runtime accesses + * to this structure. No need to call with spin_lock_irq api + * @manual_start: number of times START_BKOPS was sent to the device + * @hpi: number of times HPI was sent to the device + * @auto_start: number of times AUTO_EN was set to 1 + * @auto_stop: number of times AUTO_EN was set to 0 + * @level: number of times the device reported the need for each level of + * bkops handling + * @enabled: control over whether statistics should be gathered + * + * This structure is used to collect statistics regarding the bkops + * configuration and use-patterns. It is collected during runtime and can be + * shown to the user via a debugfs entry. + */ +struct mmc_bkops_stats { + spinlock_t lock; + unsigned int manual_start; + unsigned int hpi; + unsigned int auto_start; + unsigned int auto_stop; + unsigned int level[MMC_BKOPS_NUM_SEVERITY_LEVELS]; + bool enabled; +}; + /** * struct mmc_bkops_info - BKOPS data + * @stats: statistic information regarding bkops * @need_manual: indication whether have to send START_BKOPS * to the device */ struct mmc_bkops_info { + struct mmc_bkops_stats stats; bool needs_manual; }; diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 446b56765dbb..9a3e44f993c2 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -208,6 +208,8 @@ extern int mmc_cache_barrier(struct mmc_card *); extern int mmc_detect_card_removed(struct mmc_host *host); +extern void mmc_blk_init_bkops_statistics(struct mmc_card *card); + /** * mmc_claim_host - exclusively claim a host * @host: mmc host to claim From a9c940c044ad74de4d0fe3fdb41432703e61e23a Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Mon, 29 Jun 2015 10:50:19 +0300 Subject: [PATCH 294/472] mmc: cmdq: add clock scaling for CMDQ mode This change adds clock scaling ability to command-queueing mode, it does so by adding next logic: * Statistics collection for CMDQ data path * Empty the queue and Halt it before frequency change * Scale from data path in case host claiming is not possible Change-Id: I53a323b55df4d7c27e3ee3426ee4e856e533522c Signed-off-by: Talel Shenhar --- drivers/mmc/card/block.c | 20 ++++++ drivers/mmc/card/queue.c | 1 + drivers/mmc/core/core.c | 139 ++++++++++++++++++++++++++++++++++++++- include/linux/mmc/core.h | 6 ++ include/linux/mmc/host.h | 5 ++ 5 files changed, 168 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 006d112f11db..1e982e0349bc 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2855,6 +2855,12 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) struct mmc_cmdq_req *mc_rq; int ret = 0; + if (host->clk_scaling.enable) { + mmc_deferred_scaling(host); + mmc_cmdq_clk_scaling_start_busy(host, true); + BUG_ON(test_and_set_bit(req->tag, + &host->cmdq_ctx.data_active_reqs)); + } BUG_ON((req->tag < 0) || (req->tag > card->ext_csd.cmdq_depth)); BUG_ON(test_and_set_bit(req->tag, &host->cmdq_ctx.active_reqs)); @@ -3069,6 +3075,7 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) struct mmc_cmdq_req *cmdq_req = &mq_rq->cmdq_req; struct mmc_queue *mq = (struct mmc_queue *)rq->q->queuedata; int err = 0; + bool is_dcmd = false; if (mrq->cmd && mrq->cmd->error) err = mrq->cmd->error; @@ -3079,6 +3086,14 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) BUG_ON(!test_and_clear_bit(cmdq_req->tag, &ctx_info->active_reqs)); + if (host->clk_scaling.enable) { + if (cmdq_req->cmdq_req_flags & DCMD) + is_dcmd = true; + else + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->data_active_reqs)); + } + mmc_cmdq_post_req(host, mrq, err); if (err) { pr_err("%s: %s: txfr error: %d\n", mmc_hostname(mrq->host), @@ -3103,11 +3118,16 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) blk_end_request(rq, err, cmdq_req->data.bytes_xfered); out: + + mmc_cmdq_clk_scaling_stop_busy(host, true, is_dcmd); if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state) && test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mq->queue); mmc_put_card(host->card); + if (!ctx_info->active_reqs) + wake_up_interruptible(&host->cmdq_ctx.queue_empty_wq); + if (blk_queue_stopped(mq->queue) && !ctx_info->active_reqs) complete(&mq->cmdq_shutdown_complete); return; diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 322be51f28fe..2e68b1cbbaf8 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -613,6 +613,7 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) goto out; } + init_waitqueue_head(&card->host->cmdq_ctx.queue_empty_wq); mq->mqrq_cmdq = kzalloc( sizeof(struct mmc_queue_req) * q_depth, GFP_KERNEL); if (!mq->mqrq_cmdq) { diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 55ad10037a6c..e2925cf76fcb 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -183,6 +183,81 @@ out: spin_unlock_bh(&clk_scaling->lock); } +/** + * mmc_cmdq_clk_scaling_start_busy() - start busy timer for data requests + * @host: pointer to mmc host structure + * @lock_needed: flag indication if locking is needed + * + * This function starts the busy timer in case it was not already started. + */ +void mmc_cmdq_clk_scaling_start_busy(struct mmc_host *host, + bool lock_needed) +{ + if (!host->clk_scaling.enable) + return; + + if (lock_needed) + spin_lock_bh(&host->clk_scaling.lock); + + if (!host->clk_scaling.is_busy_started && + !test_bit(CMDQ_STATE_DCMD_ACTIVE, + &host->cmdq_ctx.curr_state)) { + host->clk_scaling.start_busy = ktime_get(); + host->clk_scaling.is_busy_started = true; + } + + if (lock_needed) + spin_unlock_bh(&host->clk_scaling.lock); +} +EXPORT_SYMBOL(mmc_cmdq_clk_scaling_start_busy); + +/** + * mmc_cmdq_clk_scaling_stop_busy() - stop busy timer for last data requests + * @host: pointer to mmc host structure + * @lock_needed: flag indication if locking is needed + * + * This function stops the busy timer in case it is the last data request. + * In case the current request is not the last one, the busy time till + * now will be accumulated and the counter will be restarted. + */ +void mmc_cmdq_clk_scaling_stop_busy(struct mmc_host *host, + bool lock_needed, bool is_cmdq_dcmd) +{ + if (!host->clk_scaling.enable) + return; + + if (lock_needed) + spin_lock_bh(&host->clk_scaling.lock); + + /* + * For CQ mode: In completion of DCMD request, start busy time in + * case of pending data requests + */ + if (is_cmdq_dcmd) { + if (host->cmdq_ctx.data_active_reqs) { + host->clk_scaling.is_busy_started = true; + host->clk_scaling.start_busy = ktime_get(); + } + goto out; + } + + host->clk_scaling.total_busy_time_us += + ktime_to_us(ktime_sub(ktime_get(), + host->clk_scaling.start_busy)); + + if (host->cmdq_ctx.data_active_reqs) { + host->clk_scaling.is_busy_started = true; + host->clk_scaling.start_busy = ktime_get(); + } else { + host->clk_scaling.is_busy_started = false; + } +out: + if (lock_needed) + spin_unlock_bh(&host->clk_scaling.lock); + +} +EXPORT_SYMBOL(mmc_cmdq_clk_scaling_stop_busy); + /** * mmc_disable_devfreq_clk_scaling() - Disable clock scaling * @host: pointer to mmc host structure @@ -240,8 +315,14 @@ static int mmc_devfreq_get_dev_status(struct device *dev, /* accumulate the busy time of ongoing work */ memset(status, 0, sizeof(*status)); if (clk_scaling->is_busy_started) { - mmc_clk_scaling_stop_busy(host, false); - mmc_clk_scaling_start_busy(host, false); + if (mmc_card_cmdq(host->card)) { + /* the "busy-timer" will be restarted in case there + * are pending data requests */ + mmc_cmdq_clk_scaling_stop_busy(host, false, false); + } else { + mmc_clk_scaling_stop_busy(host, false); + mmc_clk_scaling_start_busy(host, false); + } } status->busy_time = clk_scaling->total_busy_time_us; @@ -284,10 +365,35 @@ static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) return R1_CURRENT_STATE(status) == R1_STATE_TRAN; } +static int mmc_cmdq_halt_on_empty_queue(struct mmc_host *host) +{ + int err = 0; + + err = wait_event_interruptible(host->cmdq_ctx.queue_empty_wq, + (!host->cmdq_ctx.active_reqs)); + if (host->cmdq_ctx.active_reqs) { + pr_err("%s: %s: unexpected active requests (%lu)\n", + mmc_hostname(host), __func__, + host->cmdq_ctx.active_reqs); + return -EPERM; + } + + err = mmc_cmdq_halt(host, true); + if (err) { + pr_err("%s: %s: mmc_cmdq_halt failed (%d)\n", + mmc_hostname(host), __func__, err); + goto out; + } + +out: + return err; +} + int mmc_clk_update_freq(struct mmc_host *host, unsigned long freq, enum mmc_load state) { int err = 0; + bool cmdq_mode; if (!host) { pr_err("bad host parameter\n"); @@ -295,6 +401,9 @@ int mmc_clk_update_freq(struct mmc_host *host, return -EINVAL; } + mmc_host_clk_hold(host); + cmdq_mode = mmc_card_cmdq(host->card); + /* make sure the card supports the frequency we want */ if (unlikely(freq > host->card->clk_scaling_highest)) { freq = host->card->clk_scaling_highest; @@ -320,6 +429,15 @@ int mmc_clk_update_freq(struct mmc_host *host, } if (freq != host->clk_scaling.curr_freq) { + if (cmdq_mode) { + err = mmc_cmdq_halt_on_empty_queue(host); + if (err) { + pr_err("%s: %s: failed halting queue (%d)\n", + mmc_hostname(host), __func__, err); + goto error; + } + } + if (!mmc_is_vaild_state_for_clk_scaling(host)) { pr_debug("%s: invalid state for clock scaling - skipping", mmc_hostname(host)); @@ -333,6 +451,12 @@ int mmc_clk_update_freq(struct mmc_host *host, else pr_err("%s: %s: failed (%d) at freq=%lu\n", mmc_hostname(host), __func__, err, freq); + + if (cmdq_mode) { + if (mmc_cmdq_halt(host, false)) + pr_err("%s: %s: cmdq unhalt failed\n", + mmc_hostname(host), __func__); + } } error: if (err) { @@ -344,6 +468,7 @@ error: mmc_hostname(host), __func__); } out: + mmc_host_clk_release(host); return err; } EXPORT_SYMBOL(mmc_clk_update_freq); @@ -416,7 +541,14 @@ out: return err; } -static void mmc_deferred_scaling(struct mmc_host *host) +/** + * mmc_deferred_scaling() - scale clocks from data path (mmc thread context) + * @host: pointer to mmc host structure + * + * This function does clock scaling in case "need_freq_change" flag was set + * by the clock scaling logic. + */ +void mmc_deferred_scaling(struct mmc_host *host) { unsigned long target_freq; int err; @@ -454,6 +586,7 @@ static void mmc_deferred_scaling(struct mmc_host *host) host->clk_scaling.clk_scaling_in_progress = false; atomic_dec(&host->clk_scaling.devfreq_abort); } +EXPORT_SYMBOL(mmc_deferred_scaling); static int mmc_devfreq_create_freq_table(struct mmc_host *host) { diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 9a3e44f993c2..014eb7e272c6 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -210,6 +210,12 @@ extern int mmc_detect_card_removed(struct mmc_host *host); extern void mmc_blk_init_bkops_statistics(struct mmc_card *card); +extern void mmc_deferred_scaling(struct mmc_host *host); +extern void mmc_cmdq_clk_scaling_start_busy(struct mmc_host *host, + bool lock_needed); +extern void mmc_cmdq_clk_scaling_stop_busy(struct mmc_host *host, + bool lock_needed, bool is_cmdq_dcmd); + /** * 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 cb46cdf8dcf3..a0b472ba414a 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -237,19 +237,24 @@ struct mmc_slot { /** * mmc_cmdq_context_info - describes the contexts of cmdq * @active_reqs requests being processed + * @data_active_reqs data requests being processed * @curr_state state of cmdq engine * @req_starved completion should invoke the request_fn since * no tags were available * @cmdq_ctx_lock acquire this before accessing this structure + * @queue_empty_wq workqueue for waiting for all + * the outstanding requests to be completed */ struct mmc_cmdq_context_info { unsigned long active_reqs; /* in-flight requests */ + unsigned long data_active_reqs; /* in-flight data requests */ unsigned long curr_state; #define CMDQ_STATE_ERR 0 #define CMDQ_STATE_DCMD_ACTIVE 1 #define CMDQ_STATE_HALT 2 /* no free tag available */ unsigned long req_starved; + wait_queue_head_t queue_empty_wq; }; /** From 2941d9d5bdbabca9a8302aa1571bdf85d00300a0 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Thu, 25 Jun 2015 09:33:24 +0300 Subject: [PATCH 295/472] mmc: sdhci: cmdq: add notification for cmdq halt During halted CMDQ mode, HW expects descriptors sizes same as used in CMDQ mode. Our SW uses the legacy path (SDHCI) in case CMDQ is halted, thus, we need to update legacy path of the fact that we are in Halted CMDQ state so it can adjust the descriptors size accordingly. This change adds a notification mechanism for that purpose. Change-Id: Iabb473696fb11827dfcce9b137c26b2c5a5a879d Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed trivial merge conflicts & compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 2 ++ drivers/mmc/host/sdhci.c | 15 +++++++++++++++ drivers/mmc/host/sdhci.h | 1 + include/linux/mmc/host.h | 1 + 4 files changed, 19 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index e2925cf76fcb..57961a498a1d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1494,6 +1494,8 @@ int mmc_cmdq_halt(struct mmc_host *host, bool halt) mmc_host_clk_hold(host); if (host->cmdq_ops->halt) { err = host->cmdq_ops->halt(host, halt); + if (!err && host->ops->notify_halt) + host->ops->notify_halt(host, halt); if (!err && halt) mmc_host_set_halt(host); else if (!err && !halt) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d1f804064082..b6bba5141d21 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1515,6 +1515,20 @@ static int sdhci_disable(struct mmc_host *mmc) return 0; } +static void sdhci_notify_halt(struct mmc_host *mmc, bool halt) +{ + struct sdhci_host *host = mmc_priv(mmc); + + pr_debug("%s: halt notification was sent, halt=%d\n", + mmc_hostname(mmc), halt); + if (host->flags & SDHCI_USE_ADMA_64BIT) { + if (halt) + host->adma_desc_line_sz = 16; + else + host->adma_desc_line_sz = 12; + } +} + static inline void sdhci_update_power_policy(struct sdhci_host *host, enum sdhci_power_policy policy) { @@ -2579,6 +2593,7 @@ static const struct mmc_host_ops sdhci_ops = { .enable = sdhci_enable, .disable = sdhci_disable, .notify_load = sdhci_notify_load, + .notify_halt = sdhci_notify_halt, }; /*****************************************************************************\ diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 304ca9533bf1..9c34e61afcd6 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -564,6 +564,7 @@ struct sdhci_host { size_t align_buffer_sz; /* Bounce buffer size */ unsigned int adma_desc_sz; /* ADMA descriptor table size */ + unsigned int adma_desc_line_sz; /* ADMA descriptor line size */ unsigned int align_buf_sz; /* Bounce buffer size */ unsigned int adma_max_desc; /* Max ADMA descriptos (max sg segments) */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index a0b472ba414a..7f624c5338b5 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -178,6 +178,7 @@ struct mmc_host_ops { unsigned long (*get_min_frequency)(struct mmc_host *host); int (*notify_load)(struct mmc_host *, enum mmc_load); + void (*notify_halt)(struct mmc_host *mmc, bool halt); }; struct mmc_card; From 7f7fbf659c4fbd5a364d0e58efc1d1dcf88e2ac5 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 24 Jun 2015 15:49:30 +0300 Subject: [PATCH 296/472] mmc: sdhci-msm: avoid using NOP ADMA descriptor for EOT Qualcomm SDCC doesn't need NOP descriptor for End of transfer (EOT). This change insure that no NOP descriptor will be sent in order to notify EOT, instead, the EOT will be marked for the last descriptor. Change-Id: Ia670bc901af2196a415c2aa54357704949a7b112 Signed-off-by: Talel Shenhar --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c9ca4e0d4ef2..dfc6b0de3a61 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3175,6 +3175,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION; host->quirks |= SDHCI_QUIRK_SINGLE_POWER_WRITE; host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; + host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; From ed5008d44facbf349b56772e948affa8698ae901 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Tue, 30 Jun 2015 14:19:16 +0300 Subject: [PATCH 297/472] mmc: core: fix bkops init for cmdq It is needed to initialize auto bkops before enabling command queueing since the initialization uses CMD6 which may not be called when cmdq is enabled. Change-Id: Idecff761754fe394c01425e7fd4dd5f6a083135d Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 3f68765323ea..9a4b1a859cfa 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2126,18 +2126,6 @@ reinit: } } - if (card->ext_csd.cmdq_support && (card->host->caps2 & - MMC_CAP2_CMD_QUEUE)) { - err = mmc_select_cmdq(card); - if (err) { - pr_err("%s: selecting CMDQ mode: failed: %d\n", - mmc_hostname(card->host), err); - card->ext_csd.cmdq_support = 0; - oldcard = card; - goto reinit; - } - } - /* * Start auto bkops, if supported. * @@ -2155,6 +2143,18 @@ reinit: (void)mmc_set_auto_bkops(card, true); } + if (card->ext_csd.cmdq_support && (card->host->caps2 & + MMC_CAP2_CMD_QUEUE)) { + err = mmc_select_cmdq(card); + if (err) { + pr_err("%s: selecting CMDQ mode: failed: %d\n", + mmc_hostname(card->host), err); + card->ext_csd.cmdq_support = 0; + oldcard = card; + goto reinit; + } + } + return 0; free_card: From ea5bb4771101d383306e408ba36ce745cf091e94 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Tue, 30 Jun 2015 16:22:35 +0300 Subject: [PATCH 298/472] mmc: sd: fix crash with sd card Change ID I1333e1d4330393e446ba48a9474c83295744c050 added a call to the bus specific runtime_idle callback. This callback is only implemented for mmc thereby causing a crash when other types of memory (e.g. sd card) are used. This patch only calls the callback if it is implemented. Change-Id: Ie26b1b52fdcc0f4d5e3638801e28e965fae0cbeb Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/bus.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index c3d058313e43..b29097d7b3db 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -206,7 +206,9 @@ static int mmc_runtime_idle(struct device *dev) struct mmc_card *card = mmc_dev_to_card(dev); struct mmc_host *host = card->host; - return host->bus_ops->runtime_idle(host); + if (host->bus_ops->runtime_idle) + return host->bus_ops->runtime_idle(host); + return 0; } #endif /* !CONFIG_PM */ From 396e0ff6eb9c516e4d2986c4f9adae7aa3f785e5 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 30 Jun 2015 17:39:43 +0300 Subject: [PATCH 299/472] mmc: block: fix dump cmdq registers on request timeout Timeout handler executed on softirq context. cmdq dumpstate() api resumes platform device and it can't be executed on softirq context. Request completion callback schedules error handler work in case of timeout error. This change moves CQE registers dump to the error handler callback. Change-Id: Iea26ca5240f6031218dcf374cafcf2708df1f125 Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/block.c | 5 ++++- drivers/mmc/core/core.c | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 1e982e0349bc..0ed7d4024fac 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2972,7 +2972,6 @@ static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) struct mmc_request *mrq = &mq_rq->cmdq_req.mrq; struct mmc_cmdq_req *cmdq_req = &mq_rq->cmdq_req; - host->cmdq_ops->dumpstate(host); if (cmdq_req->cmdq_req_flags & DCMD) mrq->cmd->error = -ETIMEDOUT; else @@ -2997,6 +2996,10 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; pm_runtime_get_sync(&card->dev); + mmc_host_clk_hold(host); + host->cmdq_ops->dumpstate(host); + mmc_host_clk_release(host); + err = mmc_cmdq_halt(host, true); if (err) { pr_err("halt: failed: %d\n", err); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 57961a498a1d..a55facf12e1f 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1547,6 +1547,9 @@ int mmc_cmdq_wait_for_dcmd(struct mmc_host *host, mmc_hostname(host), cmd->opcode, cmd->error); err = cmd->error; + mmc_host_clk_hold(host); + host->cmdq_ops->dumpstate(host); + mmc_host_clk_release(host); } return err; } From d3e52fdfea833ef55f07ce6b135fa35c600aa8d6 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 29 Jun 2015 19:34:35 -0700 Subject: [PATCH 300/472] mmc: core: expose info about enhanced rpmb support Following eMMC JEDEC JESD84-B51 standard, an ehannced form of rpmb is supported. What this enhanced mode supports is in addition to be able to write one rpmb or two rpmb frames at a time, 32 frames can be written at a time. Expose this information present in ext csd field so that the user space application that wants to make use of this can do so. Change-Id: I53fd962fd7e04b5d2d7804c289d7865c2c5618d5 Signed-off-by: Krishna Konda [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 6 ++++++ include/linux/mmc/card.h | 1 + include/linux/mmc/mmc.h | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 9a4b1a859cfa..5a1493c19ade 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -644,6 +644,9 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) mmc_hostname(card->host), card->ext_csd.barrier_support, card->ext_csd.cache_flush_policy); + card->ext_csd.enhanced_rpmb_supported = + (card->ext_csd.rel_param & + EXT_CSD_WR_REL_PARAM_EN_RPMB_REL_WR); } else { card->ext_csd.cmdq_support = 0; card->ext_csd.cmdq_depth = 0; @@ -797,6 +800,8 @@ MMC_DEV_ATTR(enhanced_area_offset, "%llu\n", card->ext_csd.enhanced_area_offset); MMC_DEV_ATTR(enhanced_area_size, "%u\n", card->ext_csd.enhanced_area_size); MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult); +MMC_DEV_ATTR(enhanced_rpmb_supported, "%#x\n", + card->ext_csd.enhanced_rpmb_supported); MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors); static ssize_t mmc_fwrev_show(struct device *dev, @@ -832,6 +837,7 @@ static struct attribute *mmc_std_attrs[] = { &dev_attr_enhanced_area_offset.attr, &dev_attr_enhanced_area_size.attr, &dev_attr_raw_rpmb_size_mult.attr, + &dev_attr_enhanced_rpmb_supported.attr, &dev_attr_rel_sectors.attr, NULL, }; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 895fc0ceaf6f..5c6c10be3fd1 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -56,6 +56,7 @@ struct mmc_ext_csd { u8 sec_feature_support; u8 rel_sectors; u8 rel_param; + bool enhanced_rpmb_supported; u8 part_config; u8 cache_ctrl; u8 rst_n_function; diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 1cde2e5eb283..9cd5afaef626 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -298,7 +298,8 @@ struct _mmc_csd { * EXT_CSD field definitions */ -#define EXT_CSD_WR_REL_PARAM_EN (1<<2) +#define EXT_CSD_WR_REL_PARAM_EN (1<<2) +#define EXT_CSD_WR_REL_PARAM_EN_RPMB_REL_WR (1<<4) #define EXT_CSD_BOOT_WP_B_PWR_WP_DIS (0x40) #define EXT_CSD_BOOT_WP_B_PERM_WP_DIS (0x10) From b6c1c166809f7141efb13c2805027f189a75597a Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Wed, 1 Jul 2015 14:24:20 +0300 Subject: [PATCH 301/472] mmc: core: add support for bkops during cmdq Add support for handling both manual and auto bkops when command queuing is running. Change-Id: Ib967ca3c0420f4e54b3e93c497eb538d7347199a Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 2 +- drivers/mmc/core/core.c | 3 ++- drivers/mmc/core/mmc.c | 45 ++++++++++++++++++++++++++++++++++--- drivers/mmc/host/cmdq_hci.c | 16 ++++++++++++- drivers/mmc/host/cmdq_hci.h | 1 + include/linux/mmc/card.h | 12 +++++++++- include/linux/mmc/core.h | 1 + 7 files changed, 73 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 0ed7d4024fac..7c8e0a601cd4 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3062,7 +3062,7 @@ unhalt: out: pm_runtime_mark_last_busy(&card->dev); - pm_runtime_put_autosuspend(&card->dev); + __mmc_put_card(card); if (test_and_clear_bit(0, &ctx_info->req_starved)) blk_run_queue(mrq->req->q); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index a55facf12e1f..31f1822de554 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2052,7 +2052,7 @@ EXPORT_SYMBOL(mmc_get_card); * This is a helper function, which drops the runtime * pm reference for the card device. */ -static void __mmc_put_card(struct mmc_card *card) +void __mmc_put_card(struct mmc_card *card) { /* In case of runtime_idle, it will handle the suspend */ if (card->host->bus_ops->runtime_idle) @@ -2060,6 +2060,7 @@ static void __mmc_put_card(struct mmc_card *card) else pm_runtime_put_autosuspend(&card->dev); } +EXPORT_SYMBOL(__mmc_put_card); /* * This is a helper function, which releases the host and drops the runtime diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 5a1493c19ade..b0353f88f990 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2585,21 +2585,60 @@ static int mmc_reset(struct mmc_host *host) #define NO_AUTOSUSPEND (-1) static int mmc_runtime_idle(struct mmc_host *host) { + int err = 0; + bool halt_cmdq; + BUG_ON(!host->card); + halt_cmdq = mmc_card_cmdq(host->card) && + (host->card->bkops.needs_check || + host->card->bkops.needs_manual); + + /* + * If there are active requests, can't check for bkops. Postpone + * until the next time that runtime_idle is scheduled. + */ + if (host->cmdq_ctx.active_reqs) + goto no_suspend; + + if (halt_cmdq) { + err = mmc_cmdq_halt(host, true); + if (err) { + pr_err("%s: %s failed to halt cmdq (%d)\n", + mmc_hostname(host), __func__, err); + goto out; + } + } + + if (host->card->bkops.needs_manual) + host->card->bkops.needs_check = false; + + if (host->card->bkops.needs_check) { + mmc_claim_host(host); + mmc_check_bkops(host->card); + host->card->bkops.needs_check = false; + mmc_release_host(host); + } if (host->card->bkops.needs_manual) mmc_start_manual_bkops(host->card); - pm_runtime_mark_last_busy(&host->card->dev); + if (halt_cmdq) { + err = mmc_cmdq_halt(host, false); + if (err) + pr_err("%s: %s failed to unhalt cmdq (%d)\n", + mmc_hostname(host), __func__, err); + } +out: /* * TODO: consider prolonging suspend when bkops * is running in order to allow a longer time for * bkops to complete * */ pm_schedule_suspend(&host->card->dev, MMC_AUTOSUSPEND_DELAY_MS); - +no_suspend: + pm_runtime_mark_last_busy(&host->card->dev); /* return negative value in order to avoid autosuspend */ - return NO_AUTOSUSPEND; + return (err) ? err : NO_AUTOSUSPEND; } static const struct mmc_bus_ops mmc_ops = { diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 7e4b528eadcb..6a06036855ab 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -342,6 +342,11 @@ static int cmdq_enable(struct mmc_host *mmc) cmdq_writel(cq_host, cmdq_readl(cq_host, CQSSC1) | SEND_QSR_INTERVAL, CQSSC1); + /* enable bkops exception indication */ + if (mmc_card_configured_manual_bkops(mmc->card)) + cmdq_writel(cq_host, cmdq_readl(cq_host, CQRMEM) | CQ_EXCEPTION, + CQRMEM); + /* ensure the writes are done before enabling CQE */ mb(); @@ -675,9 +680,18 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) * In most cases, this would require a reset. */ if (status & CQIS_RED) { + /* + * will check if the RED error is due to a bkops + * exception once the queue is empty + */ + BUG_ON(!mmc->card); + if (mmc_card_configured_manual_bkops(mmc->card) && + !mmc_card_configured_auto_bkops(mmc->card)) + mmc->card->bkops.needs_check = true; + mrq->cmdq_req->resp_err = true; pr_err("%s: Response error (0x%08x) from card !!!", - mmc_hostname(mmc), status); + mmc_hostname(mmc), status); } else { mrq->cmdq_req->resp_idx = cmdq_readl(cq_host, CQCRI); mrq->cmdq_req->resp_arg = cmdq_readl(cq_host, CQCRA); diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index 100b3177e4c7..d91f542779a4 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -88,6 +88,7 @@ /* response mode error mask */ #define CQRMEM 0x50 +#define CQ_EXCEPTION (1 << 6) /* task error info */ #define CQTERRI 0x54 diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 5c6c10be3fd1..2430ba0e1182 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -313,11 +313,14 @@ struct mmc_bkops_stats { /** * struct mmc_bkops_info - BKOPS data * @stats: statistic information regarding bkops - * @need_manual: indication whether have to send START_BKOPS + * @needs_check: indication whether need to check with the device + * whether it requires handling of BKOPS (CMD8) + * @needs_manual: indication whether have to send START_BKOPS * to the device */ struct mmc_bkops_info { struct mmc_bkops_stats stats; + bool needs_check; bool needs_manual; }; @@ -656,6 +659,13 @@ static inline bool mmc_card_configured_manual_bkops(const struct mmc_card *c) return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_MANUAL_EN; } +/* + * By software design, auto_bkops will be enabled on the device if it supports + * it. Therefore, testing if a card is configured to run bkops is synonymous + * to checking if it supports bkops + */ +#define mmc_card_configured_auto_bkops(c) mmc_card_support_auto_bkops(c) + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 014eb7e272c6..2f797fb527b0 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -201,6 +201,7 @@ extern void mmc_release_host(struct mmc_host *host); extern void mmc_get_card(struct mmc_card *card); extern void mmc_put_card(struct mmc_card *card); +extern void __mmc_put_card(struct mmc_card *card); extern void mmc_set_ios(struct mmc_host *host); extern int mmc_flush_cache(struct mmc_card *); From 62c8dce6e1b42fe6ee356550a3092ca2db90ec22 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Mon, 29 Jun 2015 19:20:05 -0700 Subject: [PATCH 302/472] mmc: core: Update PON based on the system state As per eMMC specification, the PON (Power Off Notification) must be sent by host to the card before turning off the power. This will allow card to prepare itself for the power off and may even reduce the initialization of eMMC upon next boot-up. Send long PON during system power off and send short PON during system reboot to reduce the reboot latency. Change-Id: If4188b8b80aaa0e6c4e00e1807aa9589d5e7efdb Signed-off-by: Sahitya Tummala Signed-off-by: Krishna Konda [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 4 ++++ drivers/mmc/core/mmc.c | 22 ++++++++++++---------- include/linux/mmc/card.h | 9 +++++++-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7c8e0a601cd4..b9febc75c64e 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -4056,6 +4056,10 @@ static int _mmc_blk_suspend(struct mmc_card *card) static void mmc_blk_shutdown(struct mmc_card *card) { _mmc_blk_suspend(card); + + /* send power off notification */ + if (mmc_card_mmc(card)) + mmc_send_pon(card); } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index b0353f88f990..7ce9d3c01226 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1431,10 +1431,7 @@ static int mmc_reboot_notify(struct notifier_block *notify_block, struct mmc_card *card = container_of( notify_block, struct mmc_card, reboot_notify); - if (event != SYS_RESTART) - card->issue_long_pon = true; - else - card->issue_long_pon = false; + card->pon_type = (event != SYS_RESTART) ? MMC_LONG_PON : MMC_SHRT_PON; return NOTIFY_OK; } @@ -2255,19 +2252,24 @@ static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) return err; } -int mmc_send_long_pon(struct mmc_card *card) +int mmc_send_pon(struct mmc_card *card) { int err = 0; struct mmc_host *host = card->host; + if (!mmc_can_poweroff_notify(card)) + goto out; + mmc_claim_host(host); - if (card->issue_long_pon && mmc_can_poweroff_notify(card)) { + if (card->pon_type & MMC_LONG_PON) err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_LONG); - if (err) - pr_warning("%s: error %d sending Long PON", - mmc_hostname(host), err); - } + else if (card->pon_type & MMC_SHRT_PON) + err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_SHORT); + if (err) + pr_warn("%s: error %d sending PON type %u", + mmc_hostname(host), err, card->pon_type); mmc_release_host(host); +out: return err; } diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 2430ba0e1182..c8faf8b146ee 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -324,6 +324,11 @@ struct mmc_bkops_info { bool needs_manual; }; +enum mmc_pon_type { + MMC_LONG_PON = 1, + MMC_SHRT_PON, +}; + /* * MMC device */ @@ -411,7 +416,7 @@ struct mmc_card { struct mmc_wr_pack_stats wr_pack_stats; /* packed commands stats*/ struct notifier_block reboot_notify; - bool issue_long_pon; + enum mmc_pon_type pon_type; u8 *cached_ext_csd; bool cmdq_init; struct mmc_bkops_info bkops; @@ -691,6 +696,6 @@ extern void mmc_fixup_device(struct mmc_card *card, extern struct mmc_wr_pack_stats *mmc_blk_get_packed_statistics( struct mmc_card *card); extern void mmc_blk_init_packed_statistics(struct mmc_card *card); -extern int mmc_send_long_pon(struct mmc_card *card); +extern int mmc_send_pon(struct mmc_card *card); extern void mmc_blk_cmdq_req_done(struct mmc_request *mrq); #endif /* LINUX_MMC_CARD_H */ From d8c93c38d55548d3f463d8d8d7fb685111f82d74 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 6 Jul 2015 13:53:59 -0700 Subject: [PATCH 303/472] Revert "mmc: sdhci-msm: enable clock scaling capability" This reverts commit 9cc95fde7c2c ("mmc: sdhci-msm: enable clock scaling capability"). Clock scaling is breaking XO shutdown, keep the clock scaling disabled till that issue is addressed. Change-Id: Id4c530f8e2a8f565f9b957f46e5086078f808c96 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index dfc6b0de3a61..649ded3fd040 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3236,7 +3236,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; - msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; if (msm_host->pdata->nonremovable) From 91dd983c7da16afecba750e9add2510d9cc1e874 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 9 Jul 2015 14:39:07 -0700 Subject: [PATCH 304/472] mmc: sd: Error out on bus speed switch failure Treat bus speed switch failures as error and don't fall back on default speed. The card status could indicate that the current function is busy and cannot be switched. Attempt to reinit the bus speed switch again instead of falling back to default speed. Silently falling back to lower speed could cause performance issues. CRs-Fixed: 849567 Change-Id: I4fcd51b82e41746620a68a0a0eb5a18d630ccbea Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/core/sd.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 88d0790b6b91..efe6878479bd 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -375,9 +375,9 @@ int mmc_sd_switch_hs(struct mmc_card *card) goto out; if ((status[16] & 0xF) != 1) { - pr_warn("%s: Problem switching card into high-speed mode!\n", - mmc_hostname(card->host)); - err = 0; + pr_warn("%s: Problem switching card into high-speed mode!, status:%x\n", + mmc_hostname(card->host), (status[16] & 0xF)); + err = -EBUSY; } else { err = 1; } @@ -490,15 +490,17 @@ static int sd_set_bus_speed_mode(struct mmc_card *card, u8 *status) if (err) return err; - if ((status[16] & 0xF) != card->sd_bus_speed) - pr_warn("%s: Problem setting bus speed mode!\n", - mmc_hostname(card->host)); - else { + if ((status[16] & 0xF) != card->sd_bus_speed) { + pr_warn("%s: Problem setting bus speed mode(%u)! max_dtr:%u, timing:%u, status:%x\n", + mmc_hostname(card->host), card->sd_bus_speed, + card->sw_caps.uhs_max_dtr, timing, (status[16] & 0xF)); + err = -EBUSY; + } else { mmc_set_timing(card->host, timing); mmc_set_clock(card->host, card->sw_caps.uhs_max_dtr); } - return 0; + return err; } /* Get host's max current setting at its current voltage */ From 41eb870d51ccd6e180983f5f84a610f1b138e066 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 9 Jul 2015 17:28:42 -0700 Subject: [PATCH 305/472] mmc: sdhci-msm: skip eMMC slot probe if eMMC isn't a bootdevice If eMMC is not a primary bootdevice, there isn't any point of probing eMMC device hence disable the probing in such case. Change-Id: I92fa8c2ef373fd8a9140dbfb41356684aaa28e4e Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 27 +++++++++++++++++++++++++++ drivers/mmc/host/sdhci-msm.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 649ded3fd040..bd89274a5e3c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2927,6 +2927,28 @@ static void sdhci_msm_cmdq_init(struct sdhci_host *host, } #endif +static bool sdhci_msm_is_bootdevice(struct device *dev) +{ + if (strnstr(saved_command_line, "androidboot.bootdevice=", + strlen(saved_command_line))) { + char search_string[50]; + + snprintf(search_string, ARRAY_SIZE(search_string), + "androidboot.bootdevice=%s", dev_name(dev)); + if (strnstr(saved_command_line, search_string, + strlen(saved_command_line))) + return true; + else + return false; + } + + /* + * "androidboot.bootdevice=" argument is not present then + * return true as we don't know the boot device anyways. + */ + return true; +} + static int sdhci_msm_probe(struct platform_device *pdev) { struct sdhci_host *host; @@ -2990,6 +3012,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) ret); goto pltfm_free; } + + /* skip the probe if eMMC isn't a boot device */ + if ((ret == 1) && !sdhci_msm_is_bootdevice(&pdev->dev)) + goto pltfm_free; + if (disable_slots & (1 << (ret - 1))) { dev_info(&pdev->dev, "%s: Slot %d disabled\n", __func__, ret); diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index d61c50e1491f..e3e99c6f8097 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -159,4 +159,7 @@ struct sdhci_msm_host { struct sdhci_msm_ice_data ice; u32 ice_clk_rate; }; + +extern char *saved_command_line; + #endif /* __SDHCI_MSM_H__ */ From c328d924ab502c01b58c6849474ce242b2c6a873 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 14 Jul 2015 11:30:15 +0300 Subject: [PATCH 306/472] mmc: core: disable clock scaling before system suspend This change disables clock scaling in PM notification for "prepare for system suspend". This is needed because devfreq creates a dependency between it and mmc which causes an issue for system suspend. In this change we break this dependency in earlier stage. Change-Id: I86dad94c77607b4e8f8fa67035323716f5eb197d Signed-off-by: Talel Shenhar --- drivers/mmc/core/core.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 31f1822de554..45971ef9b74e 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -289,7 +289,8 @@ bool mmc_can_scale_clk(struct mmc_host *host) return false; } - return host->caps2 & MMC_CAP2_CLK_SCALE; + return (host->caps2 & MMC_CAP2_CLK_SCALE) && + (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)); } EXPORT_SYMBOL(mmc_can_scale_clk); @@ -747,7 +748,7 @@ int mmc_exit_clk_scaling(struct mmc_host *host) return -EINVAL; } - if (!mmc_can_scale_clk(host)) + if (!mmc_can_scale_clk(host) || !host->clk_scaling.enable) return 0; if (!host->clk_scaling.devfreq) { @@ -4155,6 +4156,7 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_unlock_irqrestore(&host->lock, flags); cancel_delayed_work_sync(&host->detect); + mmc_disable_clk_scaling(host); if (!host->bus_ops) break; From 420061a55cdf2c1860ee6c18fb36cebea66f7dbc Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Wed, 13 May 2015 14:08:39 +0300 Subject: [PATCH 307/472] mmc: sdhci-msm: enable clock scaling capability This change enables clock scaling capability for sdhci-msm platform driver. Change-Id: Ia78eb0416321755737438d28984ddabea6dbd527 Signed-off-by: Talel Shenhar --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index bd89274a5e3c..326e1512ec1c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3263,6 +3263,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; + msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; if (msm_host->pdata->nonremovable) From 6cfee5b81f096a0dd4cd08ce55a95be88e5c310d Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 14 Jul 2015 16:01:52 +0300 Subject: [PATCH 308/472] mmc: core: avoid returning error value for clk-scaling This change removes the error value (-EAGAIN) that is returned in case of invalid state for clock scaling. Invalid state for clock scaling doesn't mean its an error, it merely means that at the current time we won't scale. Devfreq will invoke scaling in next interval. Change-Id: Idbd785da83e6ed00ede2b1b09529f7c81714ccf8 Signed-off-by: Talel Shenhar --- drivers/mmc/core/core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 45971ef9b74e..b3319f588042 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -442,7 +442,6 @@ int mmc_clk_update_freq(struct mmc_host *host, if (!mmc_is_vaild_state_for_clk_scaling(host)) { pr_debug("%s: invalid state for clock scaling - skipping", mmc_hostname(host)); - err = -EAGAIN; goto error; } From 0d6559c521bed4e213049c0190af06fb3dfa4104 Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 14 Jul 2015 16:06:06 +0300 Subject: [PATCH 309/472] mmc: core: add runtime PM voting to devfreq context This change adds usage for runtime PM for devfreq context. In case clock scaling is done from devfreq context its best to vote for runtime PM to avoid race between scaling and PM. Change-Id: I6fc7e2236b44b1f3a322da36552a7f9237c4dac6 Signed-off-by: Talel Shenhar --- drivers/mmc/core/core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b3319f588042..aaaccd9d5a8c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -526,6 +526,7 @@ static int mmc_devfreq_set_target(struct device *dev, clk_scaling->need_freq_change = false; mmc_host_clk_hold(host); + mmc_get_card(host->card); err = mmc_clk_update_freq(host, *freq, clk_scaling->state); if (err && err != -EAGAIN) pr_err("%s: clock scale to %lu failed with error %d\n", @@ -534,6 +535,7 @@ static int mmc_devfreq_set_target(struct device *dev, pr_debug("%s: clock change to %lu finished successfully (%s)\n", mmc_hostname(host), *freq, current->comm); + mmc_put_card(host->card); mmc_host_clk_release(host); mmc_release_host(host); From f89573d958c60c65c568af63cca8359595fbf45e Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 8 Jul 2015 11:41:35 +0530 Subject: [PATCH 310/472] mmc: cmdq: Set the timeout before unhalt After halting CQE, if other commands are sent in legacy mode, the timeout would be modified as per the requirements of the command. Upon unhalting, this timeout would still persist for CQE too, which in some cases may lead to timeout. Hence, change the timeout to 0xf i.e. max during unhalt. Change-Id: Ifb0a1f4b9508c5884d381401216ae6c1373bb7de Signed-off-by: Asutosh Das --- drivers/mmc/host/cmdq_hci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 6a06036855ab..a1a40a70e659 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -745,6 +745,8 @@ static int cmdq_halt(struct mmc_host *mmc, bool halt) cq_host->ops->clear_set_irqs(mmc, false); ret = ret ? 0 : -ETIMEDOUT; } else { + if (cq_host->ops->set_data_timeout) + cq_host->ops->set_data_timeout(mmc, 0xf); if (cq_host->ops->clear_set_irqs) cq_host->ops->clear_set_irqs(mmc, true); cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) & ~HALT, From 48d05392f809b8245f3dce8eccd99bf2045393e6 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 15 Jul 2015 13:23:05 +0530 Subject: [PATCH 311/472] mmc: cmdq_hci: Notify sdhci for enhanced strobe Provide cmdq_host ops of enhanced strobe to notify sdhci on enabling/disabling cmdq. This is needed because of following: Before running CMDQ transfers in HS400 Enhanced Strobe mode, SW should write 3 to HC_VENDOR_SPECIFIC_FUNC3.CMDEN_HS400_INPUT_MASK_CNT register. Default reset value of this register is 2. Change-Id: I36ead91ca8c9aeed967f120f8bdc3d2180af7746 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/cmdq_hci.c | 5 +++++ drivers/mmc/host/cmdq_hci.h | 1 + 2 files changed, 6 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index a1a40a70e659..a0f71934f620 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -361,6 +361,8 @@ static int cmdq_enable(struct mmc_host *mmc) if (cq_host->ops->clear_set_dumpregs) cq_host->ops->clear_set_dumpregs(mmc, 1); + if (cq_host->ops->enhanced_strobe_mask) + cq_host->ops->enhanced_strobe_mask(mmc, true); out: cmdq_runtime_pm_put(cq_host); return err; @@ -376,6 +378,9 @@ static void cmdq_disable(struct mmc_host *mmc, bool soft) cq_host, CQCFG) & ~(CQ_ENABLE), CQCFG); } + if (cq_host->ops->enhanced_strobe_mask) + cq_host->ops->enhanced_strobe_mask(mmc, false); + cmdq_runtime_pm_put(cq_host); cq_host->enabled = false; } diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index d91f542779a4..cbae969f9f60 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -199,6 +199,7 @@ struct cmdq_host_ops { void (*write_l)(struct cmdq_host *host, u32 val, int reg); u32 (*read_l)(struct cmdq_host *host, int reg); void (*clear_set_dumpregs)(struct mmc_host *mmc, bool set); + void (*enhanced_strobe_mask)(struct mmc_host *mmc, bool set); int (*reset)(struct mmc_host *mmc); int (*crypto_cfg)(struct mmc_host *mmc, struct mmc_request *mrq, u32 slot); From 2be603cfa5f22f91a9ee4dcc41e47a33b9a9f26f Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 15 Jul 2015 13:31:06 +0530 Subject: [PATCH 312/472] mmc: sdhci: Notify sdhci-msm for enhanced strobe Provide sdhci host ops of enhanced strobe to notify sdhci-msm on enabling/disabling cmdq. This is needed because of following: Before running CMDQ transfers in HS400 Enhanced Strobe mode, SW should write 3 to HC_VENDOR_SPECIFIC_FUNC3.CMDEN_HS400_INPUT_MASK_CNT register. Default reset value of this register is 2. Change-Id: I987605cd21f137dea49ddf3e8db3f1f41b5b501f Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 14 ++++++++++++++ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 15 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index b6bba5141d21..94bfc9c49806 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3436,6 +3436,14 @@ static void sdhci_cmdq_set_block_size(struct mmc_host *mmc) sdhci_set_blk_size_reg(host, 512, 0); } +static void sdhci_enhanced_strobe_mask(struct mmc_host *mmc, bool set) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->ops->enhanced_strobe_mask) + host->ops->enhanced_strobe_mask(host, set); +} + static void sdhci_cmdq_clear_set_dumpregs(struct mmc_host *mmc, bool set) { struct sdhci_host *host = mmc_priv(mmc); @@ -3489,6 +3497,11 @@ static void sdhci_cmdq_set_block_size(struct mmc_host *mmc) } +static void sdhci_enhanced_strobe_mask(struct mmc_host *mmc, bool set) +{ + +} + static void sdhci_cmdq_clear_set_dumpregs(struct mmc_host *mmc, bool set) { @@ -3510,6 +3523,7 @@ static const struct cmdq_host_ops sdhci_cmdq_ops = { .dump_vendor_regs = sdhci_cmdq_dump_vendor_regs, .set_block_size = sdhci_cmdq_set_block_size, .clear_set_dumpregs = sdhci_cmdq_clear_set_dumpregs, + .enhanced_strobe_mask = sdhci_enhanced_strobe_mask, .crypto_cfg = sdhci_cmdq_crypto_cfg, .post_cqe_halt = sdhci_cmdq_post_cqe_halt, }; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 9c34e61afcd6..e8ff78f125c6 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -665,6 +665,7 @@ struct sdhci_ops { u32 type); int (*enable_controller_clock)(struct sdhci_host *host); void (*clear_set_dumpregs)(struct sdhci_host *host, bool set); + void (*enhanced_strobe_mask)(struct sdhci_host *host, bool set); void (*dump_vendor_regs)(struct sdhci_host *host); void (*toggle_cdr)(struct sdhci_host *host, bool enable); void (*voltage_switch)(struct sdhci_host *host); From 982df7cff45688d1a2415122bbfa310f3ded4412 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 15 Jul 2015 13:32:07 +0530 Subject: [PATCH 313/472] mmc: sdhci-msm: Configure CMDEN_HS400_INPUT_MASK_CNT for cmdq When sending CMD during data in HS400 Enhanced Strobe mode, the command that is being sent is also sampled internally on CMDIN line by the RCLK that is toggling due to the data traffic. To mask this CMDIN a new mask is introduced throughout the CMD transmission time. This mask is controlled by HC_VENDOR_SPECIFIC_FUNC3.CMDEN_HS400_INPUT_MASK_CNT register. The default reset value of this register is 2. Before running CMDQ transfers in HS400 Enhanced Strobe mode, SW should write 3 to HC_VENDOR_SPECIFIC_FUNC3.CMDEN_HS400_INPUT_MASK_CNT register. Change-Id: If0467855e23cb93e57a4581b375885136902835d Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 326e1512ec1c..ffc2d5b11326 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -157,6 +157,7 @@ #define CORE_VENDOR_SPEC3 0x1B0 #define CORE_PWRSAVE_DLL (1 << 3) +#define CORE_CMDEN_HS400_INPUT_MASK_CNT (1 << 13) #define CORE_DLL_CONFIG_2 0x1B4 #define CORE_DDR_CAL_EN (1 << 0) @@ -2795,6 +2796,35 @@ void sdhci_msm_reset(struct sdhci_host *host, u8 mask) sdhci_reset(host, mask); } +/* + * sdhci_msm_enhanced_strobe_mask :- + * Before running CMDQ transfers in HS400 Enhanced Strobe mode, + * SW should write 3 to + * HC_VENDOR_SPECIFIC_FUNC3.CMDEN_HS400_INPUT_MASK_CNT register. + * The default reset value of this register is 2. + */ +static void sdhci_msm_enhanced_strobe_mask(struct sdhci_host *host, bool set) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (!msm_host->enhanced_strobe) { + pr_debug("%s: host does not support hs400 enhanced strobe\n", + mmc_hostname(host->mmc)); + return; + } + + if (set) { + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC3) + | CORE_CMDEN_HS400_INPUT_MASK_CNT), + host->ioaddr + CORE_VENDOR_SPEC3); + } else { + writel_relaxed((readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC3) + & ~CORE_CMDEN_HS400_INPUT_MASK_CNT), + host->ioaddr + CORE_VENDOR_SPEC3); + } +} + static void sdhci_msm_clear_set_dumpregs(struct sdhci_host *host, bool set) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -2829,6 +2859,7 @@ static struct sdhci_ops sdhci_msm_ops = { .set_bus_width = sdhci_set_bus_width, .reset = sdhci_msm_reset, .clear_set_dumpregs = sdhci_msm_clear_set_dumpregs, + .enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, From 8984d4abf606e738fc0d749159abfc5cfb75e89b Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Mon, 22 Jun 2015 11:09:00 +0530 Subject: [PATCH 314/472] mmc: queue: fix race in mmc shutdown The shutdown handler waits for in-flight requests only. It stops the block queue, but doesn't issue the already queued requests to the LLD. Furthermore, a request which is in the process of being queued will timeout eventually because CQE would be halted by shutdown handler. mmc_cmdq_thread(mct) -> pulls request (r1) shutdown invoked stops block queue halts cqe (mct) -> issues r1 to LLD - pulls already issued requests - CQE halted -> requeues request - issued request (r1) times out Hence, in shutdown handler - stop the block layer queue - set a state indicating shutdown - wake up mmc_cmdqd - mmc_cmdqd: - pull requests and issue to LLD - complete the issuing of already queued requests - wake up shutdown handler - wait for completion from mmc_cmdqd - wait for all issued request to complete - proceed with shutdown Change-Id: I30a9664d8ba2f4b4f580c2cf71c5d01b735c9491 Signed-off-by: Asutosh Das --- drivers/mmc/card/queue.c | 32 +++++++++++++++++++++----------- drivers/mmc/card/queue.h | 1 + 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 2e68b1cbbaf8..ba3949adbe48 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -93,6 +93,7 @@ static int mmc_cmdq_thread(void *d) if (card->host->wakeup_on_idle) set_wake_up_idle(true); + down(&mq->thread_sem); while (1) { int ret = 0; @@ -131,9 +132,12 @@ static int mmc_cmdq_thread(void *d) set_current_state(TASK_RUNNING); break; } + up(&mq->thread_sem); schedule(); + down(&mq->thread_sem); } } /* loop */ + up(&mq->thread_sem); return 0; } @@ -339,11 +343,17 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, mmc_hostname(card->host), ret); blk_cleanup_queue(mq->queue); } else { + sema_init(&mq->thread_sem, 1); mq->queue->queuedata = mq; mq->thread = kthread_run(mmc_cmdq_thread, mq, "mmc-cmdqd/%d%s", host->index, subname ? subname : ""); + if (IS_ERR(mq->thread)) { + pr_err("%s: %d: cmdq: failed to start mmc-cmdqd thread\n", + mmc_hostname(card->host), ret); + ret = PTR_ERR(mq->thread); + } return ret; } @@ -644,6 +654,7 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work); init_completion(&mq->cmdq_shutdown_complete); + init_completion(&mq->cmdq_pending_req_done); blk_queue_rq_timed_out(mq->queue, mmc_cmdq_rq_timed_out); blk_queue_rq_timeout(mq->queue, 120 * HZ); @@ -697,23 +708,22 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) int rc = 0; struct mmc_card *card = mq->card; - if (card->cmdq_init) { + if (card->cmdq_init && blk_queue_tagged(q) && wait) { struct mmc_host *host = card->host; - unsigned long flags; spin_lock_irqsave(q->queue_lock, flags); blk_stop_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); - if (host->cmdq_ctx.active_reqs) { - if (!wait) - rc = -EBUSY; - else - mmc_wait_for_pending_reqs(mq); - } else { - mq->cmdq_shutdown(mq); - } - + /* + * Wait for already queued requests to be issued by + * mmc_cmdqd. + */ + down(&mq->thread_sem); + /* Wait for already issued requests to complete */ + if (host->cmdq_ctx.active_reqs) + mmc_wait_for_pending_reqs(mq); + mq->cmdq_shutdown(mq); goto out; } diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index ab89102a4baa..e13f3ceb0cc2 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -72,6 +72,7 @@ struct mmc_queue { struct work_struct cmdq_err_work; struct completion cmdq_shutdown_complete; + struct completion cmdq_pending_req_done; int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); void (*cmdq_shutdown)(struct mmc_queue *); From ee08ef262bc8be47ef76acb692aae09a804d7bf7 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 5 Feb 2015 14:05:27 +0530 Subject: [PATCH 315/472] mmc: sdhci-msm: Fix SD card detection issue The change in pull configs might not take into effect immediately and any value read before it is stabilized will mark incorrect card status. This causes SD card detection to fail when inserted for the first time. Fix this by adding enough delay after configuring the GPIO and before reading its value. Change-Id: I3a8455ce404988ab5eb3ed04c0f90ab6edf76d86 Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci-msm.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index ffc2d5b11326..3be6ade7b672 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3331,6 +3331,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) */ sdhci_msm_setup_pins(msm_host->pdata, true); + /* + * This delay is needed for stabilizing the card detect GPIO + * line after changing the pull configs. + */ + usleep_range(10000, 10500); ret = mmc_gpio_request_cd(msm_host->mmc, msm_host->pdata->status_gpio, 0); if (ret) { From 63d9f77d9f65b6291dff524e590169cb99ef176c Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Mon, 20 Jul 2015 11:59:52 +0300 Subject: [PATCH 316/472] mmc: cmdq: disable bkops exception for auto bkops The bkops exception handling will ignore cases when auto bkops is enabled. As such, limiting the enablement of the exception in cmdq mode to when manual bkops is enabled yet auto bkops is not. Change-Id: Icc158cf2c7a2dda6e8b9f1eee8a5924e14330d1f Signed-off-by: Dov Levenglick --- drivers/mmc/host/cmdq_hci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index a0f71934f620..77aa31522e1e 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -343,7 +343,8 @@ static int cmdq_enable(struct mmc_host *mmc) CQSSC1); /* enable bkops exception indication */ - if (mmc_card_configured_manual_bkops(mmc->card)) + if (mmc_card_configured_manual_bkops(mmc->card) && + !mmc_card_configured_auto_bkops(mmc->card)) cmdq_writel(cq_host, cmdq_readl(cq_host, CQRMEM) | CQ_EXCEPTION, CQRMEM); From 9ee678d1ddb6864c140d0442a958f195c149d78a Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Mon, 20 Jul 2015 16:12:31 +0300 Subject: [PATCH 317/472] mmc: clk-scaling: fix invalid state handling during cmdq This change fixes an issue that can happen for clock scaling sequence during command-queue. This is the sequence for scaling the clocks in case of command queuing: 1) Halt queue 2) Check if state invalid 3) Scale clocks 4) Un-halt scale In case step 2 fails (e.g. device state is different than R1_STATE_TRAN), we should avoid scaling the clocks and un-halt the queue. The issue was that step 4 was not happening in case of invalid state, this change fixes it. Change-Id: I308b0d6406631febe364d14de7551eb7f628cb40 Signed-off-by: Talel Shenhar --- drivers/mmc/core/core.c | 61 +++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index aaaccd9d5a8c..dfd6ea38d522 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -344,7 +344,7 @@ static int mmc_devfreq_get_dev_status(struct device *dev, return 0; } -static bool mmc_is_vaild_state_for_clk_scaling(struct mmc_host *host) +static bool mmc_is_valid_state_for_clk_scaling(struct mmc_host *host) { struct mmc_card *card = host->card; u32 status; @@ -420,6 +420,9 @@ int mmc_clk_update_freq(struct mmc_host *host, host->card->clk_scaling_lowest); } + if (freq == host->clk_scaling.curr_freq) + goto out; + if (host->ops->notify_load) { err = host->ops->notify_load(host, state); if (err) { @@ -429,36 +432,36 @@ int mmc_clk_update_freq(struct mmc_host *host, } } - if (freq != host->clk_scaling.curr_freq) { - if (cmdq_mode) { - err = mmc_cmdq_halt_on_empty_queue(host); - if (err) { - pr_err("%s: %s: failed halting queue (%d)\n", - mmc_hostname(host), __func__, err); - goto error; - } - } - - if (!mmc_is_vaild_state_for_clk_scaling(host)) { - pr_debug("%s: invalid state for clock scaling - skipping", - mmc_hostname(host)); - goto error; - } - - err = host->bus_ops->change_bus_speed(host, &freq); - if (!err) - host->clk_scaling.curr_freq = freq; - else - pr_err("%s: %s: failed (%d) at freq=%lu\n", - mmc_hostname(host), __func__, err, freq); - - if (cmdq_mode) { - if (mmc_cmdq_halt(host, false)) - pr_err("%s: %s: cmdq unhalt failed\n", - mmc_hostname(host), __func__); + if (cmdq_mode) { + err = mmc_cmdq_halt_on_empty_queue(host); + if (err) { + pr_err("%s: %s: failed halting queue (%d)\n", + mmc_hostname(host), __func__, err); + goto halt_failed; } } -error: + + if (!mmc_is_valid_state_for_clk_scaling(host)) { + pr_debug("%s: invalid state for clock scaling - skipping", + mmc_hostname(host)); + goto invalid_state; + } + + err = host->bus_ops->change_bus_speed(host, &freq); + if (!err) + host->clk_scaling.curr_freq = freq; + else + pr_err("%s: %s: failed (%d) at freq=%lu\n", + mmc_hostname(host), __func__, err, freq); + +invalid_state: + if (cmdq_mode) { + if (mmc_cmdq_halt(host, false)) + pr_err("%s: %s: cmdq unhalt failed\n", + mmc_hostname(host), __func__); + } + +halt_failed: if (err) { /* restore previous state */ if (host->ops->notify_load) From 348a140b51c072d00ca66d56e8d20e5fcab758ad Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Mon, 20 Jul 2015 16:50:03 +0300 Subject: [PATCH 318/472] mmc: host: add detect vops chain Add call from sdio to host_ops to sdhci_ops in order to indicate when a sdio card is detected. This will be used by hosts that require special handling for card detection. Change-Id: I65ec6ee464d658cd938d9254a0a748e6137f9223 Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/sdio.c | 3 +++ drivers/mmc/host/sdhci.c | 9 +++++++++ drivers/mmc/host/sdhci.h | 1 + include/linux/mmc/host.h | 1 + 4 files changed, 14 insertions(+) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index b0ce838f7ee6..b3b1526e5609 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -890,6 +890,9 @@ static void mmc_sdio_detect(struct mmc_host *host) */ err = _mmc_detect_card_removed(host); + if (host->ops && host->ops->detect) + host->ops->detect(host, err); + mmc_release_host(host); /* diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 94bfc9c49806..a3903184a5f8 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2574,6 +2574,14 @@ static void sdhci_card_event(struct mmc_host *mmc) spin_unlock_irqrestore(&host->lock, flags); } +static void sdhci_detect(struct mmc_host *mmc, bool detected) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->ops->detect) + host->ops->detect(host, detected); +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .post_req = sdhci_post_req, @@ -2594,6 +2602,7 @@ static const struct mmc_host_ops sdhci_ops = { .disable = sdhci_disable, .notify_load = sdhci_notify_load, .notify_halt = sdhci_notify_halt, + .detect = sdhci_detect, }; /*****************************************************************************\ diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index e8ff78f125c6..e6e818e89c39 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -666,6 +666,7 @@ struct sdhci_ops { int (*enable_controller_clock)(struct sdhci_host *host); void (*clear_set_dumpregs)(struct sdhci_host *host, bool set); void (*enhanced_strobe_mask)(struct sdhci_host *host, bool set); + void (*detect)(struct sdhci_host *host, bool detected); void (*dump_vendor_regs)(struct sdhci_host *host); void (*toggle_cdr)(struct sdhci_host *host, bool enable); void (*voltage_switch)(struct sdhci_host *host); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 7f624c5338b5..9bf53de32a07 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -179,6 +179,7 @@ struct mmc_host_ops { int (*notify_load)(struct mmc_host *, enum mmc_load); void (*notify_halt)(struct mmc_host *mmc, bool halt); + void (*detect)(struct mmc_host *host, bool detected); }; struct mmc_card; From d83bd544785109918936084ea90aee4f8e4420c2 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Mon, 20 Jul 2015 09:30:52 +0300 Subject: [PATCH 319/472] mmc: sdhci-msm: configure MMC_PM_KEEP_POWER for SDIO Add MMC_PM_KEEP_POWER specifically when connected to SDIO cards, rather than in general for the host. Change-Id: Idb666680f99277ae509c642595821448c21b6c90 Signed-off-by: Dov Levenglick --- drivers/mmc/host/sdhci-msm.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3be6ade7b672..b2ad35a91685 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2841,6 +2841,19 @@ static void sdhci_msm_clear_set_dumpregs(struct sdhci_host *host, bool set) } } +static void sdhci_msm_detect(struct sdhci_host *host, bool detected) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct mmc_host *mmc = msm_host->mmc; + struct mmc_card *card = mmc->card; + + if (detected && mmc_card_sdio(card)) + mmc->pm_caps |= MMC_PM_KEEP_POWER; + else + mmc->pm_caps &= ~MMC_PM_KEEP_POWER; +} + static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, .crypto_engine_reset = sdhci_msm_ice_reset, @@ -2860,6 +2873,7 @@ static struct sdhci_ops sdhci_msm_ops = { .reset = sdhci_msm_reset, .clear_set_dumpregs = sdhci_msm_clear_set_dumpregs, .enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask, + .detect = sdhci_msm_detect, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, @@ -3295,7 +3309,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; - msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; From 8ee51daf1c95730aefbc700d51abd147c4fac435 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Thu, 16 Jul 2015 11:58:38 +0300 Subject: [PATCH 320/472] mmc: schci: add support for MMC_PM_KEEP_POWER in eMMC There are eMMC cards that should not be powered off during suspend/resume cycles. This patch adds support for such cards and avoids powering the card off during suspend or powering it back on during resume when MMC_PM_KEEP_POWER is set. Change-Id: Iec1a0aac80ee41dff56f192e7253c2bd00c15694 Signed-off-by: Dov Levenglick --- drivers/mmc/core/mmc.c | 9 +++++++++ drivers/mmc/host/sdhci.c | 10 ++++++++++ include/linux/mmc/host.h | 1 + 3 files changed, 20 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 7ce9d3c01226..4ea2b2d44b90 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2347,6 +2347,9 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (mmc_card_suspended(host->card)) goto out; + if (is_suspend) + host->dev_status = DEV_SUSPENDING; + if (mmc_card_cmdq(host->card)) { BUG_ON(host->cmdq_ctx.active_reqs); @@ -2386,6 +2389,10 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) mmc_card_set_suspended(host->card); } out: + if (err) + host->dev_status = DEV_UNKNOWN; + else if (is_suspend) + host->dev_status = DEV_SUSPENDED; mmc_release_host(host); return err; } @@ -2461,6 +2468,8 @@ static int _mmc_resume(struct mmc_host *host) mmc_init_clk_scaling(host); out: + if (!err) + host->dev_status = DEV_RESUMED; return err; } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a3903184a5f8..8ea115e1d80b 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1387,6 +1387,16 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, struct mmc_host *mmc = host->mmc; u8 pwr = 0; + /* + * Don't disable/re-enable power to the card when running a + * suspend/resume sequence and the pm_flags are configured to preserve + * card power during suspend. + */ + if (mmc_card_keep_power(mmc) && + ((mmc->dev_status == DEV_SUSPENDED && mode == MMC_POWER_UP) || + (mmc->dev_status == DEV_SUSPENDING && mode == MMC_POWER_OFF))) + return; + if (!IS_ERR(mmc->supply.vmmc)) { spin_unlock_irq(&host->lock); mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 9bf53de32a07..c6d0ff9f1cbe 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -287,6 +287,7 @@ enum dev_state { DEV_SUSPENDING = 1, DEV_SUSPENDED, DEV_RESUMED, + DEV_UNKNOWN, /* Device is in an unknown state */ }; /** From d25b9c4733136c2f14d2fe2a1c130869e01f76a1 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Tue, 14 Jul 2015 14:10:57 +0300 Subject: [PATCH 321/472] mmc: core: set MMC_PM_KEEP_POWER for certain Hynix mmc cards Certain Hynix eMMC 5.0 cards might reach a fast EOL if the card's power is disabled between CMD5 and CMD0 (power off during reset). This patch sets the MMC_PM_KEEP_POWER flag to indicate that the card's power should be retained for suspend/resume sequences. Change-Id: I4bcc0f4bfd608745626816ca261369b432602c45 Signed-off-by: Dov Levenglick --- drivers/mmc/core/mmc.c | 12 ++++++++++++ include/linux/mmc/card.h | 1 + 2 files changed, 13 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 4ea2b2d44b90..2261d57af6a5 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -126,6 +126,12 @@ static void mmc_set_erase_size(struct mmc_card *card) mmc_init_erase(card); } +static void add_pm_flag_mmc(struct mmc_card *card, int data) +{ + if (mmc_card_mmc(card)) + card->host->pm_flags |= data; +} + static const struct mmc_fixup mmc_fixups[] = { /* avoid HPI for specific cards */ @@ -136,6 +142,9 @@ static const struct mmc_fixup mmc_fixups[] = { MMC_FIXUP("MMC16G", CID_MANFID_KINGSTON, CID_OEMID_ANY, add_quirk_mmc, MMC_QUIRK_CACHE_DISABLE), + MMC_FIXUP_EXT_CSD_REV(CID_NAME_ANY, CID_MANFID_HYNIX, CID_OEMID_ANY, + add_pm_flag_mmc, MMC_PM_KEEP_POWER, MMC_V5_0), + END_FIXUP }; @@ -382,6 +391,9 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) */ card->ext_csd.rev = ext_csd[EXT_CSD_REV]; + /* fixup device after ext_csd revision field is updated */ + mmc_fixup_device(card, mmc_fixups); + card->ext_csd.raw_sectors[0] = ext_csd[EXT_CSD_SEC_CNT + 0]; card->ext_csd.raw_sectors[1] = ext_csd[EXT_CSD_SEC_CNT + 1]; card->ext_csd.raw_sectors[2] = ext_csd[EXT_CSD_SEC_CNT + 2]; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index c8faf8b146ee..ce2a0aa79425 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -472,6 +472,7 @@ struct mmc_fixup { #define CID_MANFID_MICRON 0x13 #define CID_MANFID_SAMSUNG 0x15 #define CID_MANFID_KINGSTON 0x70 +#define CID_MANFID_HYNIX 0x90 #define CID_MANFID_ANY (-1u) #define CID_OEMID_ANY ((unsigned short) -1) From 42dd0264d115e4b70a4a044679dd121689e422f0 Mon Sep 17 00:00:00 2001 From: Dov Levenglick Date: Mon, 20 Jul 2015 17:33:03 +0300 Subject: [PATCH 322/472] mmc: core: change test of auto_bkops configured Changing the test of whether auto_bkops is configured allows for a case where the host tried to configure auto_bkops yet the CMD6 failed in configuring the device. This allows a case where manual bkops is configured on a eMMC 5.1 device Change-Id: Ia6c411f516bf27b8a5865109dcf4b290eb3d8e46 Signed-off-by: Dov Levenglick [subhashj@codeaurora.org: fixed trivial merge conflicts & fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/debugfs.c | 2 +- include/linux/mmc/card.h | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 7550db69f76f..374458c9fd6f 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -759,7 +759,7 @@ void mmc_add_card_debugfs(struct mmc_card *card) goto err; if (mmc_card_mmc(card) && (card->ext_csd.rev >= 5) && - (mmc_card_support_auto_bkops(card) || + (mmc_card_configured_auto_bkops(card) || mmc_card_configured_manual_bkops(card))) if (!debugfs_create_file("bkops_stats", S_IRUSR, root, card, &mmc_dbg_bkops_stats_fops)) diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index ce2a0aa79425..b652076be866 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -665,12 +665,10 @@ static inline bool mmc_card_configured_manual_bkops(const struct mmc_card *c) return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_MANUAL_EN; } -/* - * By software design, auto_bkops will be enabled on the device if it supports - * it. Therefore, testing if a card is configured to run bkops is synonymous - * to checking if it supports bkops - */ -#define mmc_card_configured_auto_bkops(c) mmc_card_support_auto_bkops(c) +static inline bool mmc_card_configured_auto_bkops(const struct mmc_card *c) +{ + return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_AUTO_EN; +} #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) From 2cfffa3bbe271f834b8b4d28332430eb809f7fc9 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 23 Jul 2015 10:16:57 +0300 Subject: [PATCH 323/472] mmc: queue: fix for system suspend flow for eMMC command queue (cmdq) System suspend executed on kernel PM core context. When there are active requests system suspend is rejected. Semaphore used to resolve race between mmc_cmdq_thread() and mmc_queue_suspend(). Change-Id: I88f668d7a6dd6403407ac8208265e4439b35173c Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/queue.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index ba3949adbe48..3fcd18353eca 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -686,12 +686,6 @@ void mmc_cmdq_clean(struct mmc_queue *mq, struct mmc_card *card) mq->mqrq_cmdq = NULL; } -static void mmc_wait_for_pending_reqs(struct mmc_queue *mq) -{ - wait_for_completion(&mq->cmdq_shutdown_complete); - mq->cmdq_shutdown(mq); -} - /** * mmc_queue_suspend - suspend a MMC request queue * @mq: MMC queue to suspend @@ -708,22 +702,36 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) int rc = 0; struct mmc_card *card = mq->card; - if (card->cmdq_init && blk_queue_tagged(q) && wait) { + if (card->cmdq_init && blk_queue_tagged(q)) { struct mmc_host *host = card->host; spin_lock_irqsave(q->queue_lock, flags); blk_stop_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); - /* - * Wait for already queued requests to be issued by - * mmc_cmdqd. - */ - down(&mq->thread_sem); - /* Wait for already issued requests to complete */ - if (host->cmdq_ctx.active_reqs) - mmc_wait_for_pending_reqs(mq); - mq->cmdq_shutdown(mq); + if (wait) { + /* + * Wait for already queued requests to be issued by + * mmc_cmdqd. + */ + down(&mq->thread_sem); + /* Wait for already issued requests to complete */ + if (host->cmdq_ctx.active_reqs) + wait_for_completion( + &mq->cmdq_shutdown_complete); + + mq->cmdq_shutdown(mq); + } else if (!test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags)) { + rc = down_trylock(&mq->thread_sem); + if (rc || host->cmdq_ctx.active_reqs) { + clear_bit(MMC_QUEUE_SUSPENDED, &mq->flags); + spin_lock_irqsave(q->queue_lock, flags); + blk_start_queue(q); + spin_unlock_irqrestore(q->queue_lock, flags); + rc = -EBUSY; + } + } + goto out; } From 7a14825b8e8e897b9f81107197b413ce09b298be Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 25 Feb 2015 14:24:52 +0530 Subject: [PATCH 324/472] mmc: sdhci-msm: Improvise tuning to check the card status Right now only certain amount of delay (146 MCLK cycles as per spec) is given for card to return back to transfer state upon any CMD error that host controller may receive. This delay seems to be insufficient for certain eMMC cards like Hynix. This patch tries to send CMD13 and also retry it with the same delay to make sure the card is back to transfer state before sending next command. Otherwise we may see auto cmd or illegal command failures to the read command sent right after tuning, especially if the last tuning phase has failed. Change-Id: I3ec2da150dc5ee656b8156040bf539812b0e4d2b Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula [subhashj@codeaurora.org: fixed trivial conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 39 ++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b2ad35a91685..80d1a7350bc3 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -932,6 +932,7 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) u8 drv_type = 0; bool drv_type_changed = false; struct mmc_card *card = host->mmc->card; + int sts_retry; /* * Tuning is required for SDR104, HS200 and HS400 cards and @@ -988,6 +989,7 @@ retry: .data = &data }; struct scatterlist sg; + struct mmc_command sts_cmd = {0}; /* set the phase in delay line hw block */ rc = msm_config_cm_dll_phase(host, phase); @@ -1008,14 +1010,35 @@ retry: memset(data_buf, 0, size); mmc_wait_for_req(mmc, &mrq); - /* - * wait for 146 MCLK cycles for the card to send out the data - * and thus move to TRANS state. As the MCLK would be minimum - * 200MHz when tuning is performed, we need maximum 0.73us - * delay. To be on safer side 1ms delay is given. - */ - if (cmd.error) - usleep_range(1000, 1200); + if (card && (cmd.error || data.error)) { + sts_cmd.opcode = MMC_SEND_STATUS; + sts_cmd.arg = card->rca << 16; + sts_cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + sts_retry = 5; + while (sts_retry) { + mmc_wait_for_cmd(mmc, &sts_cmd, 0); + + if (sts_cmd.error || + (R1_CURRENT_STATE(sts_cmd.resp[0]) + != R1_STATE_TRAN)) { + sts_retry--; + /* + * wait for at least 146 MCLK cycles for + * the card to move to TRANS state. As + * the MCLK would be min 200MHz for + * tuning, we need max 0.73us delay. To + * be on safer side 1ms delay is given. + */ + usleep_range(1000, 1200); + pr_debug("%s: phase %d sts cmd err %d resp 0x%x\n", + mmc_hostname(mmc), phase, + sts_cmd.error, sts_cmd.resp[0]); + continue; + } + break; + }; + } + if (!cmd.error && !data.error && !memcmp(data_buf, tuning_block_pattern, size)) { /* tuning is successful at this tuning point */ From b7b3cd3406a1b16194a8df3e999aef85cb7e91c4 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Wed, 22 Jul 2015 18:17:32 +0530 Subject: [PATCH 325/472] mmc: sdhci-msm: Setting coherent_dma_mask Set coherent_dma_mask to allocate consistent DMA memory during probe. Change-Id: Ie68c5b04096e77074fd8c91469d2173990fac9bc Signed-off-by: Pavan Anamula [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 2 ++ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 80d1a7350bc3..1e23ec2d043c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3385,9 +3385,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(64)))) { host->dma_mask = DMA_BIT_MASK(64); mmc_dev(host->mmc)->dma_mask = &host->dma_mask; + mmc_dev(host->mmc)->coherent_dma_mask = host->dma_mask; } else if (dma_supported(mmc_dev(host->mmc), DMA_BIT_MASK(32))) { host->dma_mask = DMA_BIT_MASK(32); mmc_dev(host->mmc)->dma_mask = &host->dma_mask; + mmc_dev(host->mmc)->coherent_dma_mask = host->dma_mask; } else { dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__); } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index e6e818e89c39..8ec61fb5fe76 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -510,6 +510,7 @@ struct sdhci_host { /* Internal data */ struct mmc_host *mmc; /* MMC structure */ u64 dma_mask; /* custom DMA mask */ + u64 coherent_dma_mask; #if defined(CONFIG_LEDS_CLASS) || defined(CONFIG_LEDS_CLASS_MODULE) struct led_classdev led; /* LED control */ From ed1bdbadff70bc32ae279b9fa967094c2d784b56 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 23 Jul 2015 11:23:44 +0530 Subject: [PATCH 326/472] mmc: queue: fix high power consumption The cmdqd thread was marked as uninterruptible. This did not allow the cmdqd thread to sleep when there is no traffic. Added interruptible attributes to the thread. Change-Id: I658eecabfff074044b23c7c270c9101a34d37a45 Signed-off-by: Asutosh Das --- drivers/mmc/card/queue.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 3fcd18353eca..ea5d7bf124ac 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -98,6 +98,7 @@ static int mmc_cmdq_thread(void *d) int ret = 0; spin_lock_irqsave(q->queue_lock, flags); + set_current_state(TASK_INTERRUPTIBLE); req = blk_peek_request(q); if (req) { ret = blk_queue_start_tag(q, req); @@ -116,6 +117,7 @@ static int mmc_cmdq_thread(void *d) schedule(); continue; } + set_current_state(TASK_RUNNING); ret = mq->cmdq_issue_fn(mq, req); if (ret) { pr_err("%s: failed (%d) to issue req, requeue\n", From f052410b0bd6f04effc8d2c9c9323dda55d9c7da Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Fri, 29 May 2015 15:39:37 +0530 Subject: [PATCH 327/472] mmc: cmdq: trigger get queue status after dcmd CMDQ spec defines periodic SEND_STATUS mechanism to poll on READY tasks in the device. When DAT lines are in IDLE the counter counts from its reset value to '0' and then triggers SEND_STATUS command. When CMD13 is completed and also the syncing of the device status to HCLK domain is done there is a 1 cycle pulse to reload the counter with timer reset value so that the counting can start over. In rare cases, when the 'done' pulse for reloading the counter happens in parallel to a BUSY state of direct command - the IDLE counter is not reloaded and can't trigger another CMD13. If this scenario happens when there are pending tasks which are not 'READY' yet - it can lead to a deadlock, since there is no other mechainsm to send CMD13, and CQE will never get READY on the pending tasks. Hence, trigger a send status command after DCMD is completed as a work-around to the above issue. Change-Id: I4e8530e72c8bf581ffaeed7d35d8b8c61d282ffa Signed-off-by: Asutosh Das --- drivers/mmc/host/cmdq_hci.c | 5 +++++ drivers/mmc/host/cmdq_hci.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 77aa31522e1e..9710abab69e2 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -185,6 +185,8 @@ static void cmdq_dumpregs(struct cmdq_host *cq_host) pr_err(DRV_NAME ": Resp idx 0x%08x | Resp arg: 0x%08x\n", cmdq_readl(cq_host, CQCRI), cmdq_readl(cq_host, CQCRA)); + pr_err(DRV_NAME": Vendor cfg 0x%08x\n", + cmdq_readl(cq_host, CQ_VENDOR_CFG)); pr_err(DRV_NAME ": ===========================================\n"); cmdq_dump_debug_ram(cq_host); @@ -639,6 +641,9 @@ static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) if (tag == cq_host->dcmd_slot) mrq->cmd->resp[0] = cmdq_readl(cq_host, CQCRDCT); + if (mrq->cmdq_req->cmdq_req_flags & DCMD) + cmdq_writel(cq_host, cmdq_readl(cq_host, CQ_VENDOR_CFG) | + CMDQ_SEND_STATUS_TRIGGER, CQCTL); mrq->done(mrq); } diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index cbae969f9f60..610715f1875c 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -146,6 +146,9 @@ #define DAT_ADDR_LO(x) ((x & 0xFFFFFFFF) << 32) #define DAT_ADDR_HI(x) ((x & 0xFFFFFFFF) << 0) +#define CQ_VENDOR_CFG 0x100 +#define CMDQ_SEND_STATUS_TRIGGER (1 << 31) + struct cmdq_host { const struct cmdq_host_ops *ops; void __iomem *mmio; From e0ee5ff565543bcc9f91635e27090c229c7e4750 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 13 Jul 2015 10:52:36 +0530 Subject: [PATCH 328/472] mmc: core: Fix debugfs and IOCTL calls in cmdq mode Currently getting status/ext_csd using debugfs or IOCTL calls in cmdq mode is not working. Fix it by halting the cmdq engine and making sure that card queue is empty before issuing these cmds. Change-Id: Idb89def9ff5c2fee6866759b9a8c652049552933 Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 17 +++++++++++++++++ drivers/mmc/core/core.c | 3 ++- drivers/mmc/core/debugfs.c | 39 ++++++++++++++++++++++++++++++++++++++ include/linux/mmc/core.h | 1 + 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index b9febc75c64e..b083cdd2b0e5 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -921,12 +921,29 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev, mmc_get_card(card); + if (mmc_card_cmdq(card)) { + err = mmc_cmdq_halt_on_empty_queue(card->host); + if (err) { + pr_err("%s: halt failed while doing %s err (%d)\n", + mmc_hostname(card->host), + __func__, err); + mmc_put_card(card); + goto cmd_done; + } + } + ioc_err = __mmc_blk_ioctl_cmd(card, md, idata); mmc_put_card(card); err = mmc_blk_ioctl_copy_to_user(ic_ptr, idata); + if (mmc_card_cmdq(card)) { + if (mmc_cmdq_halt(card->host, false)) + pr_err("%s: %s: cmdq unhalt failed\n", + mmc_hostname(card->host), __func__); + } + cmd_done: mmc_blk_put(md); cmd_err: diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index dfd6ea38d522..7531de320c31 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -366,7 +366,7 @@ static bool mmc_is_valid_state_for_clk_scaling(struct mmc_host *host) return R1_CURRENT_STATE(status) == R1_STATE_TRAN; } -static int mmc_cmdq_halt_on_empty_queue(struct mmc_host *host) +int mmc_cmdq_halt_on_empty_queue(struct mmc_host *host) { int err = 0; @@ -389,6 +389,7 @@ static int mmc_cmdq_halt_on_empty_queue(struct mmc_host *host) out: return err; } +EXPORT_SYMBOL(mmc_cmdq_halt_on_empty_queue); int mmc_clk_update_freq(struct mmc_host *host, unsigned long freq, enum mmc_load state) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 374458c9fd6f..98b9e90e667a 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -383,11 +383,26 @@ static int mmc_dbg_card_status_get(void *data, u64 *val) int ret; mmc_get_card(card); + if (mmc_card_cmdq(card)) { + ret = mmc_cmdq_halt_on_empty_queue(card->host); + if (ret) { + pr_err("%s: halt failed while doing %s err (%d)\n", + mmc_hostname(card->host), __func__, + ret); + goto out; + } + } ret = mmc_send_status(data, &status); if (!ret) *val = status; + if (mmc_card_cmdq(card)) { + if (mmc_cmdq_halt(card->host, false)) + pr_err("%s: %s: cmdq unhalt failed\n", + mmc_hostname(card->host), __func__); + } +out: mmc_put_card(card); return ret; @@ -410,6 +425,17 @@ static int mmc_ext_csd_open(struct inode *inode, struct file *filp) return -ENOMEM; mmc_get_card(card); + if (mmc_card_cmdq(card)) { + err = mmc_cmdq_halt_on_empty_queue(card->host); + if (err) { + pr_err("%s: halt failed while doing %s err (%d)\n", + mmc_hostname(card->host), __func__, + err); + mmc_put_card(card); + goto out_free_halt; + } + } + err = mmc_get_ext_csd(card, &ext_csd); mmc_put_card(card); if (err) @@ -421,10 +447,23 @@ static int mmc_ext_csd_open(struct inode *inode, struct file *filp) BUG_ON(n != EXT_CSD_STR_LEN); filp->private_data = buf; + + if (mmc_card_cmdq(card)) { + if (mmc_cmdq_halt(card->host, false)) + pr_err("%s: %s: cmdq unhalt failed\n", + mmc_hostname(card->host), __func__); + } + kfree(ext_csd); return 0; out_free: + if (mmc_card_cmdq(card)) { + if (mmc_cmdq_halt(card->host, false)) + pr_err("%s: %s: cmdq unhalt failed\n", + mmc_hostname(card->host), __func__); + } +out_free_halt: kfree(buf); return err; } diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 2f797fb527b0..87332656e0fd 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -135,6 +135,7 @@ struct mmc_cmdq_req; extern int mmc_cmdq_discard_queue(struct mmc_host *host, u32 tasks); extern int mmc_cmdq_halt(struct mmc_host *host, bool enable); +extern int mmc_cmdq_halt_on_empty_queue(struct mmc_host *host); extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err); extern int mmc_cmdq_start_req(struct mmc_host *host, From bdd297db78b28e8e9bb145934cb9dc00a56e0071 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 28 Jul 2015 13:44:53 -0700 Subject: [PATCH 329/472] mmc: sdhci-msm: Reenable cd gpio on system resume In 3.18 kernel mmc_gpiod_request_cd_irq() is not called as part of call to mmc_gpio_request_cd(). During probe this is taken care of by calling mmc_gpiod_request_cd_irq() from mmc_start_host(), but if mmc_gpio_request_cd() followed by a mmc_gpio_free_cd() is invoked after mmc_start_host() (such as in system suspend/resume path) then mmc_gpiod_request_cd_irq() needs to be called explicitly. Instead of free/request the card detect irq, just disable/enable the irq in system suspend/resume path. CRs-fixed: 876453 Change-Id: I976cd5061c2a7d8321e48ee23a44acfd552a37fc Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 1e23ec2d043c..635bcd755e09 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3583,8 +3583,9 @@ static int sdhci_msm_suspend(struct device *dev) int ret = 0; ktime_t start = ktime_get(); - if (gpio_is_valid(msm_host->pdata->status_gpio)) - mmc_gpio_free_cd(msm_host->mmc); + if (gpio_is_valid(msm_host->pdata->status_gpio) && + (msm_host->mmc->slot.cd_irq >= 0)) + disable_irq(msm_host->mmc->slot.cd_irq); if (pm_runtime_suspended(dev)) { pr_debug("%s: %s: already runtime suspended\n", @@ -3606,13 +3607,10 @@ static int sdhci_msm_resume(struct device *dev) int ret = 0; ktime_t start = ktime_get(); - if (gpio_is_valid(msm_host->pdata->status_gpio)) { - ret = mmc_gpio_request_cd(msm_host->mmc, - msm_host->pdata->status_gpio, 0); - if (ret) - pr_err("%s: %s: Failed to request card detection IRQ %d\n", - mmc_hostname(host->mmc), __func__, ret); - } + if (gpio_is_valid(msm_host->pdata->status_gpio) && + (msm_host->mmc->slot.cd_irq >= 0)) + enable_irq(msm_host->mmc->slot.cd_irq); + if (pm_runtime_suspended(dev)) { pr_debug("%s: %s: runtime suspended, defer system resume\n", mmc_hostname(host->mmc), __func__); From 49dd1ccd0d1409f5ac4fd6dc5ee620adf9297701 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 30 Jul 2015 17:19:48 -0700 Subject: [PATCH 330/472] mmc: mmc_ops: Add retry mechanism to mmc_switch Some devices do not honor the max switch timeout exposed via ext_csd, they take longer than that to complete the switch. Hence add a retry mechanism to wait longer for the card to get out of programming state before timing out. Change-Id: Ica46e30df24b4f95f457271087e1e03823ed2959 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc_ops.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index ec0e0095e03a..4a978898af84 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -528,6 +528,7 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, unsigned long timeout; u32 status = 0; bool use_r1b_resp = use_busy_signal; + int retries = 5; mmc_retune_hold(host); @@ -603,10 +604,17 @@ int __mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value, /* Timeout if the device never leaves the program state. */ if (time_after(jiffies, timeout)) { - pr_err("%s: Card stuck in programming state! %s\n", - mmc_hostname(host), __func__); - err = -ETIMEDOUT; - goto out; + pr_err("%s: Card stuck in programming state! %s, timeout:%ums, retries:%d\n", + mmc_hostname(host), __func__, + timeout_ms, retries); + if (retries) + timeout = jiffies + + msecs_to_jiffies(timeout_ms); + else { + err = -ETIMEDOUT; + goto out; + } + retries--; } } while (R1_CURRENT_STATE(status) == R1_STATE_PRG); From 004730036cb47288e4354ab4f3aaf3ec061338ed Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 2 Aug 2015 16:07:05 +0300 Subject: [PATCH 331/472] mmc: block: differentiate system suspend and shutdown flows Block layer queue on suspend flow gives up if there are active requests, while shutdown flow stops issuing thread and waits for completion of outstanding requests and never give up. This change propagates additional parameter to differentiate between suspend and shutdown flows Change-Id: I35a036c1a5585e3088301c07ade7d09ebd2cfc1b Signed-off-by: Konstantin Dorfman [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index b083cdd2b0e5..17d5c0289882 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -4043,18 +4043,18 @@ static void mmc_blk_remove(struct mmc_card *card) dev_set_drvdata(&card->dev, NULL); } -static int _mmc_blk_suspend(struct mmc_card *card) +static int _mmc_blk_suspend(struct mmc_card *card, bool wait) { struct mmc_blk_data *part_md; struct mmc_blk_data *md = dev_get_drvdata(&card->dev); int rc = 0; if (md) { - rc = mmc_queue_suspend(&md->queue, 0); + rc = mmc_queue_suspend(&md->queue, wait); if (rc) goto out; list_for_each_entry(part_md, &md->part, part) { - rc = mmc_queue_suspend(&part_md->queue, 0); + rc = mmc_queue_suspend(&part_md->queue, wait); if (rc) goto out_resume; } @@ -4072,7 +4072,7 @@ static int _mmc_blk_suspend(struct mmc_card *card) static void mmc_blk_shutdown(struct mmc_card *card) { - _mmc_blk_suspend(card); + _mmc_blk_suspend(card, 1); /* send power off notification */ if (mmc_card_mmc(card)) @@ -4084,7 +4084,7 @@ static int mmc_blk_suspend(struct device *dev) { struct mmc_card *card = mmc_dev_to_card(dev); - return _mmc_blk_suspend(card); + return _mmc_blk_suspend(card, 0); } static int mmc_blk_resume(struct device *dev) From 278a3d6e3fa8673cec054da4b82d0fdcb3141224 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 2 Aug 2015 16:10:44 +0300 Subject: [PATCH 332/472] mmc: block: fix cmdq shutdown flow This change fixes cmdq shutdown flow: - claims host (since devfreq scaling context could race) - holds clocks - disables cmdq controller mode - updates cmdq card state mode Change-Id: Ie25664516f782812fe39fba574be50e44ece815c Signed-off-by: Konstantin Dorfman --- drivers/mmc/card/block.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 17d5c0289882..a223cf4b5c44 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2959,13 +2959,14 @@ static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) struct mmc_card *card = mq->card; struct mmc_host *host = card->host; + mmc_get_card(card); + mmc_host_clk_hold(host); err = mmc_cmdq_halt(host, true); if (err) { pr_err("%s: halt: failed: %d\n", __func__, err); return; } - mmc_get_card(card); /* disable CQ mode in card */ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CMDQ, 0, @@ -2975,9 +2976,12 @@ static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) __func__, err); goto out; } else { + mmc_card_clr_cmdq(card); + host->cmdq_ops->disable(host, false); host->card->cmdq_init = false; } out: + mmc_host_clk_release(host); mmc_put_card(card); } From 6e4be0bac2555f84736170f486ecc325ff22fdcb Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 2 Aug 2015 17:06:18 +0300 Subject: [PATCH 333/472] mmc: cmdq_hci: fix platform device power management reference counting After issuing a request the usage_count is decremented. After idle time controller irq is disabled by platform device runtime pm and request complete irq is not handled. This change moves decrement of usage_count from the end of issuing request to the end of request completion. Change-Id: I1322e0d1ab4ffbf50956fec2921c778e0dcddf36 Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/cmdq_hci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 9710abab69e2..9a66420e4f87 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -628,7 +628,6 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) cmdq_writel(cq_host, 1 << tag, CQTDBR); out: - cmdq_runtime_pm_put(cq_host); return err; } @@ -644,6 +643,8 @@ static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) if (mrq->cmdq_req->cmdq_req_flags & DCMD) cmdq_writel(cq_host, cmdq_readl(cq_host, CQ_VENDOR_CFG) | CMDQ_SEND_STATUS_TRIGGER, CQCTL); + + cmdq_runtime_pm_put(cq_host); mrq->done(mrq); } From 9dbe5a9defd291a34130e6ccc17ff9a07fff276f Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 2 Jun 2015 17:41:53 +0300 Subject: [PATCH 334/472] mmc: sdhci: use pr_err_ratelimited for AUTO CMD command errors Switch to using pr_err_ratelimited in order to avoid flooding the logs in case of error function gets called repeatedly. CRs-Fixed: 837631 Change-Id: I08fffb7e77584ac7fe7ba7152677cc12293e1655 Signed-off-by: Konstantin Dorfman Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 8ea115e1d80b..6b3c90213dc3 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2751,7 +2751,7 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *mask) if (intmask & SDHCI_INT_AUTO_CMD_ERR) { auto_cmd_status = host->auto_cmd_err_sts; - pr_err("%s: %s: AUTO CMD err sts 0x%08x\n", + pr_err_ratelimited("%s: %s: AUTO CMD err sts 0x%08x\n", mmc_hostname(host->mmc), __func__, auto_cmd_status); if (auto_cmd_status & (SDHCI_AUTO_CMD12_NOT_EXEC | SDHCI_AUTO_CMD_INDEX_ERR | From a01ae39f5d254385e10f6e2c316f709f8fd7d180 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 6 Aug 2015 13:58:47 +0530 Subject: [PATCH 335/472] mmc: sdhci: Add notify_load host->op Add notify_load host->op to enable host controllers to scale up/down necessary clocks based on the load. Change-Id: I39d31d5343d8aa453f29294e340e52d94bfd0ade Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 3 +++ drivers/mmc/host/sdhci.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 6b3c90213dc3..a156fe2705ae 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1562,6 +1562,9 @@ static int sdhci_notify_load(struct mmc_host *mmc, enum mmc_load state) break; } + if (host->ops->notify_load) + err = host->ops->notify_load(host, state); + return err; } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 8ec61fb5fe76..2046f4a7fac8 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -675,6 +675,7 @@ struct sdhci_ops { struct mmc_card *card, unsigned int max_dtr, int host_drv, int card_drv, int *drv_type); + int (*notify_load)(struct sdhci_host *host, enum mmc_load state); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS From 94fe36b713c589273c39f6f33a8ec5bd7edfe3a2 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 6 Aug 2015 13:59:37 +0530 Subject: [PATCH 336/472] mmc: sdhci-msm: get the load notification from clock scaling This is needed to scale up/down the ICE clock during runtime as per the load on eMMC. Change-Id: I60d06458767c817298783219caf767866e7bf12f Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 41 +++++++++++++++++++++++++++++++++--- drivers/mmc/host/sdhci-msm.h | 2 ++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 635bcd755e09..d35cc16436ba 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1509,8 +1509,16 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, dev_err(dev, "Invalid clock table\n"); goto out; } + if (ice_clk_table_len != 2) { + dev_err(dev, "Need max and min frequencies in the table\n"); + goto out; + } pdata->sup_ice_clk_table = ice_clk_table; pdata->sup_ice_clk_cnt = ice_clk_table_len; + pdata->ice_clk_max = pdata->sup_ice_clk_table[0]; + pdata->ice_clk_min = pdata->sup_ice_clk_table[1]; + dev_dbg(dev, "supported ICE clock rates (Hz): max: %u min: %u\n", + pdata->ice_clk_max, pdata->ice_clk_min); } pdata->vreg_data = devm_kzalloc(dev, sizeof(struct @@ -2877,6 +2885,32 @@ static void sdhci_msm_detect(struct sdhci_host *host, bool detected) mmc->pm_caps &= ~MMC_PM_KEEP_POWER; } +int sdhci_msm_notify_load(struct sdhci_host *host, enum mmc_load state) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int ret = 0; + u32 clk_rate = 0; + + if (!IS_ERR(msm_host->ice_clk)) { + clk_rate = (state == MMC_LOAD_LOW) ? + msm_host->pdata->ice_clk_min : + msm_host->pdata->ice_clk_max; + if (msm_host->ice_clk_rate == clk_rate) + return 0; + pr_debug("%s: changing ICE clk rate to %u\n", + mmc_hostname(host->mmc), clk_rate); + ret = clk_set_rate(msm_host->ice_clk, clk_rate); + if (ret) { + pr_err("%s: ICE_CLK rate set failed (%d) for %u\n", + mmc_hostname(host->mmc), ret, clk_rate); + return ret; + } + msm_host->ice_clk_rate = clk_rate; + } + return 0; +} + static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, .crypto_engine_reset = sdhci_msm_ice_reset, @@ -2897,6 +2931,7 @@ static struct sdhci_ops sdhci_msm_ops = { .clear_set_dumpregs = sdhci_msm_clear_set_dumpregs, .enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask, .detect = sdhci_msm_detect, + .notify_load = sdhci_msm_notify_load, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, @@ -3132,11 +3167,11 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (!IS_ERR(msm_host->ice_clk)) { /* ICE core has only one clock frequency for now */ ret = clk_set_rate(msm_host->ice_clk, - msm_host->pdata->sup_ice_clk_table[0]); + msm_host->pdata->ice_clk_max); if (ret) { dev_err(&pdev->dev, "ICE_CLK rate set failed (%d) for %u\n", ret, - msm_host->pdata->sup_ice_clk_table[0]); + msm_host->pdata->ice_clk_max); goto pclk_disable; } ret = clk_prepare_enable(msm_host->ice_clk); @@ -3144,7 +3179,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto pclk_disable; msm_host->ice_clk_rate = - msm_host->pdata->sup_clk_table[0]; + msm_host->pdata->ice_clk_max; } } diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index e3e99c6f8097..1602668c0ff4 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -106,6 +106,8 @@ struct sdhci_msm_pltfm_data { enum pm_qos_req_type cpu_affinity_type; u32 *sup_ice_clk_table; unsigned char sup_ice_clk_cnt; + u32 ice_clk_max; + u32 ice_clk_min; }; struct sdhci_msm_bus_vote { From 8f39568c53a820094959a79b4c039afada2e9ad4 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Fri, 7 Aug 2015 20:21:20 -0700 Subject: [PATCH 337/472] mmc: core: remove shutdown handler If mmc/sd shutdown callback suspends the card when card's runtime PM status is still set to "runtime active" then any request issued after mmc/sd shutdown will not resume the card hence we will end up issuing the request to host driver while card isn't in a state to handle it. Right way to fix this issue is not to allow any requests to flow in after the shutdown callback has executed. Until it is fixed in the right way, we are disabling the shutdown handling as it anyways doesn't do real useful things. Change-Id: I44681f3c2fd0435bffa393abbbd1e8eacc5a07da Signed-off-by: Subhash Jadavani [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 22 ---------------------- drivers/mmc/core/sd.c | 1 - 2 files changed, 23 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 2261d57af6a5..a0cc1242f299 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2485,27 +2485,6 @@ out: return err; } -/* - * Shutdown callback - */ -static int mmc_shutdown(struct mmc_host *host) -{ - int err = 0; - - /* - * In a specific case for poweroff notify, we need to resume the card - * before we can shutdown it properly. - */ - if (mmc_can_poweroff_notify(host->card) && - !(host->caps2 & MMC_CAP2_FULL_PWR_CYCLE)) - err = _mmc_resume(host); - - if (!err) - err = _mmc_suspend(host, false); - - return err; -} - /* * Callback for resume. */ @@ -2673,7 +2652,6 @@ static const struct mmc_bus_ops mmc_ops = { .runtime_resume = mmc_runtime_resume, .runtime_idle = mmc_runtime_idle, .alive = mmc_alive, - .shutdown = mmc_shutdown, .change_bus_speed = mmc_change_bus_speed, .reset = mmc_reset, }; diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index efe6878479bd..486910f34e0f 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1348,7 +1348,6 @@ static const struct mmc_bus_ops mmc_sd_ops = { .suspend = mmc_sd_suspend, .resume = mmc_sd_resume, .alive = mmc_sd_alive, - .shutdown = mmc_sd_suspend, .change_bus_speed = mmc_sd_change_bus_speed, .reset = mmc_sd_reset, }; From 705de98157456301124bde37914d9f69bb133542 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 10 Aug 2015 14:55:23 -0700 Subject: [PATCH 338/472] mmc: cmdq_hci: Add a memory barrier before ringing doorbell Ensure the task descriptor list memory is flushed before ringing the doorbell by adding a memory barrier. Also commit the doorbell write immediately to help improve performance. Change-Id: I321d5bed95b802d4bcc00836ce9cdede316b29f5 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/cmdq_hci.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 9a66420e4f87..b2e23484bb5c 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -593,8 +593,9 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) if (mrq->cmdq_req->cmdq_req_flags & DCMD) { cmdq_prep_dcmd_desc(mmc, mrq); cq_host->mrq_slot[DCMD_SLOT] = mrq; - cmdq_writel(cq_host, 1 << DCMD_SLOT, CQTDBR); - goto out; + /* DCMD's are always issued on a fixed slot */ + tag = DCMD_SLOT; + goto ring_doorbell; } if (cq_host->ops->crypto_cfg) { @@ -625,7 +626,12 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) if (cq_host->ops->set_tranfer_params) cq_host->ops->set_tranfer_params(mmc); +ring_doorbell: + /* Ensure the task descriptor list is flushed before ringing doorbell */ + wmb(); cmdq_writel(cq_host, 1 << tag, CQTDBR); + /* Commit the doorbell write immediately */ + wmb(); out: return err; From 73ba7de11a02393100b1cdf9e349f5d5c800f7f4 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Mon, 10 Aug 2015 15:54:26 -0700 Subject: [PATCH 339/472] mmc: card: fix active data requests handling Clock scaling logic relies on active data request tracking variable to start/stop counting the bus busy period and we are also crashing the system if this tracking variable shows that new request tag is already in use. Set/Clear logic of this tracking variable is done only if clock scaling is enabled but consider following sequence of events: 1. Clock scaling is enabled 2. New request comes in which sets the appropriate tag in tracking variable. 3. Clock scaling gets disabled (for some reason) before the request completion. 4. Request gets completed but it doesn't clear the corresponding tag in tracking variable. 5. Clock scaling gets re-enabled. 6. New request is issued with the same tag which was never cleared from tracking variable hence we would hit the BUG_ON and system will crash. Fix this issue by doing set/clear of tracking variable irrespective of clock scaling state. Change-Id: Idffd1247ec1275e782e1ad9eb91faa81bc3ba347 Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index a223cf4b5c44..43f304036bcd 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2872,13 +2872,11 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) struct mmc_cmdq_req *mc_rq; int ret = 0; - if (host->clk_scaling.enable) { - mmc_deferred_scaling(host); - mmc_cmdq_clk_scaling_start_busy(host, true); - BUG_ON(test_and_set_bit(req->tag, - &host->cmdq_ctx.data_active_reqs)); - } + mmc_deferred_scaling(host); + mmc_cmdq_clk_scaling_start_busy(host, true); + BUG_ON((req->tag < 0) || (req->tag > card->ext_csd.cmdq_depth)); + BUG_ON(test_and_set_bit(req->tag, &host->cmdq_ctx.data_active_reqs)); BUG_ON(test_and_set_bit(req->tag, &host->cmdq_ctx.active_reqs)); active_mqrq = &mq->mqrq_cmdq[req->tag]; @@ -3110,13 +3108,11 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) BUG_ON(!test_and_clear_bit(cmdq_req->tag, &ctx_info->active_reqs)); - if (host->clk_scaling.enable) { - if (cmdq_req->cmdq_req_flags & DCMD) - is_dcmd = true; - else - BUG_ON(!test_and_clear_bit(cmdq_req->tag, - &ctx_info->data_active_reqs)); - } + if (cmdq_req->cmdq_req_flags & DCMD) + is_dcmd = true; + else + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->data_active_reqs)); mmc_cmdq_post_req(host, mrq, err); if (err) { From 602d8c8cfc1fbd35b87843baf0e8e18d8b8957ac Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Tue, 18 Aug 2015 02:28:56 +0300 Subject: [PATCH 340/472] mmc: core: clock-scaling: scale only for data requests This change makes sure we will do deferred scaling only from data path and not for other requests such as mmc_switch. Change-Id: I52142d7a0262ca8927c7c1c509235ead676aac97 Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 7531de320c31..afc7b8c26b22 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1011,9 +1011,10 @@ static int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) mmc_host_clk_hold(host); led_trigger_event(host->led, LED_FULL); - mmc_deferred_scaling(host); - if (mmc_is_data_request(mrq)) + if (mmc_is_data_request(mrq)) { + mmc_deferred_scaling(host); mmc_clk_scaling_start_busy(host, true); + } __mmc_start_request(host, mrq); From 9bc36b07a2c375ad4fac740f273cf2b83b5cd4e8 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Thu, 13 Aug 2015 17:58:30 +0300 Subject: [PATCH 341/472] mmc: core: kick block queue after unhalting cmdq If request has to be requeued due to cmdq being halted and if we change the task status to interruptible before going to sleep then cmdq thread may not wakeup again. Note that blk_requeue_request() doesn't trigger ->request_fn() again to wakeup the cmdq thread. Fix this issue by kicking the queue once cmdq state machine is unhalted. Change-Id: Icbfb3b6560285fa0a0ce7e83eee66b651d4594a0 Signed-off-by: Gilad Broner Signed-off-by: Subhash Jadavani --- drivers/mmc/card/queue.c | 1 + drivers/mmc/core/core.c | 6 +++++- include/linux/mmc/host.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index ea5d7bf124ac..d226eae5f9f1 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -347,6 +347,7 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, } else { sema_init(&mq->thread_sem, 1); mq->queue->queuedata = mq; + card->host->cmdq_ctx.q = mq->queue; mq->thread = kthread_run(mmc_cmdq_thread, mq, "mmc-cmdqd/%d%s", host->index, diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index afc7b8c26b22..9f3924db9dc7 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -32,6 +32,7 @@ #include #include #include +#include #include @@ -1505,8 +1506,11 @@ int mmc_cmdq_halt(struct mmc_host *host, bool halt) host->ops->notify_halt(host, halt); if (!err && halt) mmc_host_set_halt(host); - else if (!err && !halt) + else if (!err && !halt) { mmc_host_clr_halt(host); + if (host->cmdq_ctx.q) + blk_run_queue(host->cmdq_ctx.q); + } } else { err = -ENOSYS; } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index c6d0ff9f1cbe..aa9df4ce96e5 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -257,6 +257,7 @@ struct mmc_cmdq_context_info { /* no free tag available */ unsigned long req_starved; wait_queue_head_t queue_empty_wq; + struct request_queue *q; }; /** From 3c9889c0fb7a71610573846c7204a135158b5eca Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Wed, 22 Jul 2015 21:46:32 +0530 Subject: [PATCH 342/472] mmc: sdhci-msm: Fix HW issue with power IRQ handling during reset There is a rare scenario in HW, where the first clear pulse could be lost when the actual reset and clear/read of status register are happening at the same time. Fix this by retrying upto 10 times to ensure the status register gets cleared. Otherwise, this will lead to a spurious power IRQ which results in system instability. Change-Id: I1c4f27e131992ef036ebe64fbb2c52613ba396cc Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci-msm.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d35cc16436ba..f5d59124b222 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2088,6 +2088,18 @@ static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata, return ret; } +void sdhci_msm_dump_pwr_ctrl_regs(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + pr_err("%s: PWRCTL_STATUS: 0x%08x | PWRCTL_MASK: 0x%08x | PWRCTL_CTL: 0x%08x\n", + mmc_hostname(host->mmc), + readl_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS), + readl_relaxed(msm_host->core_mem + CORE_PWRCTL_MASK), + readl_relaxed(msm_host->core_mem + CORE_PWRCTL_CTL)); +} + static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) { struct sdhci_host *host = (struct sdhci_host *)data; @@ -2098,6 +2110,7 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) int ret = 0; int pwr_state = 0, io_level = 0; unsigned long flags; + int retry = 10; irq_status = readb_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS); pr_debug("%s: Received IRQ(%d), status=0x%x\n", @@ -2112,6 +2125,29 @@ static irqreturn_t sdhci_msm_pwr_irq(int irq, void *data) * completed before its next update to registers within hc_mem. */ mb(); + /* + * There is a rare HW scenario where the first clear pulse could be + * lost when actual reset and clear/read of status register is + * happening at a time. Hence, retry for at least 10 times to make + * sure status register is cleared. Otherwise, this will result in + * a spurious power IRQ resulting in system instability. + */ + while (irq_status & + readb_relaxed(msm_host->core_mem + CORE_PWRCTL_STATUS)) { + if (retry == 0) { + pr_err("%s: Timedout clearing (0x%x) pwrctl status register\n", + mmc_hostname(host->mmc), irq_status); + sdhci_msm_dump_pwr_ctrl_regs(host); + BUG_ON(1); + } + writeb_relaxed(irq_status, + (msm_host->core_mem + CORE_PWRCTL_CLEAR)); + retry--; + udelay(10); + } + if (likely(retry < 10)) + pr_debug("%s: success clearing (0x%x) pwrctl status register, retries left %d\n", + mmc_hostname(host->mmc), irq_status, retry); /* Handle BUS ON/OFF*/ if (irq_status & CORE_PWRCTL_BUS_ON) { From fe670347356bed5fc5e766c9e3a366f5a2aa1a8c Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Fri, 21 Aug 2015 18:09:42 +0530 Subject: [PATCH 343/472] mmc: sdhci-msm: change the rclk delay value Change the rclk delay value to 0.9ns since testing shows that the valid window may vary for different platforms and the default value (1.25ns) might fall outside the valid window. CRs-Fixed: 766702 Change-Id: I6e3522c2764047a773e028078b63e6e94e230d41 Signed-off-by: Dov Levenglick Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci-msm.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f5d59124b222..4386497d6e99 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -164,8 +164,10 @@ #define CORE_FLL_CYCLE_CNT (1 << 18) #define CORE_DLL_CLOCK_DISABLE (1 << 21) -#define CORE_DDR_CONFIG 0x1B8 -#define DDR_CONFIG_POR_VAL 0x80040853 +#define CORE_DDR_CONFIG 0x1B8 +#define DDR_CONFIG_POR_VAL 0x80040853 +#define DDR_CONFIG_PRG_RCLK_DLY_MASK 0x1FF +#define DDR_CONFIG_PRG_RCLK_DLY 115 #define MSM_MMC_DEFAULT_CPU_DMA_LATENCY 200 /* usecs */ @@ -761,19 +763,18 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; - u32 dll_status; + u32 dll_status, ddr_config; int ret = 0; pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); /* - * Currently the CORE_DDR_CONFIG register defaults to desired - * configuration on reset. Currently reprogramming the power on - * reset (POR) value in case it might have been modified by - * bootloaders. In the future, if this changes, then the desired - * values will need to be programmed appropriately. + * Reprogramming the value in case it might have been modified by + * bootloaders. */ - writel_relaxed(DDR_CONFIG_POR_VAL, host->ioaddr + CORE_DDR_CONFIG); + ddr_config = DDR_CONFIG_POR_VAL & ~DDR_CONFIG_PRG_RCLK_DLY_MASK; + ddr_config |= DDR_CONFIG_PRG_RCLK_DLY; + writel_relaxed(ddr_config, host->ioaddr + CORE_DDR_CONFIG); if (msm_host->enhanced_strobe) writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) From a264a011c707af96a0e0465886b4d699696b25fe Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 19 Aug 2015 13:03:52 -0700 Subject: [PATCH 344/472] mmc: sdhci: disable pm qos voting when in cmdq mode pm qos is currently causing race conditions with runtime pm when in cmdq mode. Disable this till it is addressed as part of the pm qos redesign. Change-Id: I32d04100bbf31995a249188eace164c8761e9141 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a156fe2705ae..191cbb3cf203 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1489,9 +1489,12 @@ static int sdhci_enable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); - if (host->cpu_dma_latency_us) + if (mmc->card && !mmc_card_cmdq(mmc->card) && + (host->cpu_dma_latency_us)) { pm_qos_update_request(&host->pm_qos_req_dma, host->cpu_dma_latency_us); + } + if (host->ops->platform_bus_voting) host->ops->platform_bus_voting(host, 1); @@ -1502,7 +1505,8 @@ static int sdhci_disable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); - if (host->cpu_dma_latency_us) { + if (mmc->card && !mmc_card_cmdq(mmc->card) && + (host->cpu_dma_latency_us)) { /* * In performance mode, release QoS vote after a timeout to * make sure back-to-back requests don't suffer from latencies From 0b56648089c3e1ad0fe211c772f86544281a1bd5 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Mon, 24 Aug 2015 18:56:22 +0530 Subject: [PATCH 345/472] mmc: host: add support to allow SANITIZE operation SANITIZE is an operation performed by the storage device and its purpose is to delete its unmap memory regions. This change adds support for the SANITIZE capability. Change-Id: I58b647fb576c694aaa16c1e827d0784d4a5b4456 Signed-off-by: Yaniv Gardi Signed-off-by: Pavan Anamula [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 9 +++++---- drivers/mmc/host/sdhci-msm.c | 1 + include/linux/mmc/host.h | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 43f304036bcd..ed868929b164 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -589,11 +589,12 @@ static int ioctl_do_sanitize(struct mmc_card *card) { int err; - if (!mmc_can_sanitize(card)) { - pr_warn("%s: %s - SANITIZE is not supported\n", + if (!mmc_can_sanitize(card) && + (card->host->caps2 & MMC_CAP2_SANITIZE)) { + pr_warn("%s: %s - SANITIZE is not supported\n", mmc_hostname(card->host), __func__); - err = -EOPNOTSUPP; - goto out; + err = -EOPNOTSUPP; + goto out; } pr_debug("%s: %s - SANITIZE IN PROGRESS...\n", diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4386497d6e99..c897cacce14a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3404,6 +3404,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; + msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index aa9df4ce96e5..001cde15242b 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -438,7 +438,7 @@ struct mmc_host { #define MMC_CAP2_HS400_POST_TUNING (1 << 22) #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ #define MMC_CAP2_CMD_QUEUE (1 << 26) /* support eMMC command queue */ - +#define MMC_CAP2_SANITIZE (1 << 27) /* Support Sanitize */ mmc_pm_flag_t pm_caps; /* supported pm features */ #ifdef CONFIG_MMC_CLKGATE From 35cf59211f338f275a1a8ed7eb9c66771547241d Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Mon, 24 Aug 2015 20:15:16 +0530 Subject: [PATCH 346/472] mmc: core: fix race between mmc_power_off and mmc_power_up In case card detect IRQ is triggered before mmc_add_host(), then there could be a potential race between mmc_power_off() which gets called frommmc_rescan() of card detect IRQ handler and mmc_start_host() which gets called from mmc_add_host(). This may turn off the clocks while mmc_start_host() is still running and thus may result in an un-clocked register access. Change-Id: I90ff99fb8e018b00600bf18197a2bcaf83ff1bc4 Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula [subhashj@codeaurora.org: fixed merge conflicts, removed 2nd pair of mmc_claim_host/mmc_remove_host calls from mmc_start_host] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9f3924db9dc7..da0affe8b651 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3974,18 +3974,18 @@ void mmc_rescan(struct work_struct *work) void mmc_start_host(struct mmc_host *host) { + mmc_claim_host(host); host->f_init = max(freqs[0], host->f_min); host->rescan_disable = 0; host->ios.power_mode = MMC_POWER_UNDEFINED; - mmc_claim_host(host); if (host->caps2 & MMC_CAP2_NO_PRESCAN_POWERUP) mmc_power_off(host); else mmc_power_up(host, host->ocr_avail); - mmc_release_host(host); mmc_gpiod_request_cd_irq(host); + mmc_release_host(host); _mmc_detect_change(host, 0, false); } From d397fec7fec5e6ead7dd3e54f48faca88aa71cf5 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 24 Aug 2015 14:36:59 -0700 Subject: [PATCH 347/472] mmc: cmdq_hci: Fix pm ref count handling on error scenarios Runtime pm get/put calls need to be called in pairs. Fix the unpaired call of runtime pm put after input validation. Change-Id: Ice2ba1e4d17ffde48b2f4d59801bb962f2e9aae7 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/cmdq_hci.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index b2e23484bb5c..1cd274303d03 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -301,7 +301,7 @@ static int cmdq_enable(struct mmc_host *mmc) pr_info("%s: %s: cq_host is already enabled\n", mmc_hostname(mmc), __func__); WARN_ON(1); - goto out; + goto pm_ref_count; } if (cq_host->quirks & CMDQ_QUIRK_NO_DCMD) @@ -321,7 +321,7 @@ static int cmdq_enable(struct mmc_host *mmc) !cq_host->trans_desc_base) { err = cmdq_host_alloc_tdl(cq_host); if (err) - goto out; + goto pm_ref_count; } cmdq_writel(cq_host, lower_32_bits(cq_host->desc_dma_base), CQTDLBA); @@ -366,8 +366,10 @@ static int cmdq_enable(struct mmc_host *mmc) if (cq_host->ops->enhanced_strobe_mask) cq_host->ops->enhanced_strobe_mask(mmc, true); -out: + +pm_ref_count: cmdq_runtime_pm_put(cq_host); +out: return err; } From b0a69fc74bd014dc8109a0dfeab58233d793ceb1 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 3 Mar 2015 14:03:20 +0530 Subject: [PATCH 348/472] mmc: sdhci-msm: Enable one MID for SDHCI controller The SDHCI reset for data is getting stuck with the default MID configuration which uses descriptor requests with MID=0 and data requests with MID=1. This enables interleaving between MID and is causing reset to be stuck somewhere in the path DDR<->NOC<->SDHC on few chipsets. Enable one MID mode as a workaround to this problem which is observed on SDCC5 controller of 8916/8939 and 8992 chipsets. Change-Id: I12343b35d45774668b7e823ccaa067813fcea4cf Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci-msm.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c897cacce14a..e194a2ad55ad 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -118,6 +118,9 @@ #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR0 0x114 #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 +#define CORE_VENDOR_SPEC_FUNC2 0x110 +#define CORE_ONE_MID_EN (1 << 25) + #define CORE_VENDOR_SPEC_CAPABILITIES0 0x11C #define CORE_8_BIT_SUPPORT (1 << 18) #define CORE_3_3V_SUPPORT (1 << 24) @@ -2820,6 +2823,8 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC), readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_ADMA_ERR_ADDR0), readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_ADMA_ERR_ADDR1)); + pr_info("Vndr func2: 0x%08x\n", + readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_FUNC2)); /* * tbsel indicates [2:0] bits and tbsel2 indicates [7:4] bits @@ -2977,6 +2982,7 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, u32 version, caps = 0; u16 minor; u8 major; + u32 val; version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION); major = (version & CORE_VERSION_MAJOR_MASK) >> @@ -3005,6 +3011,15 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, caps |= CORE_8_BIT_SUPPORT; } + /* + * Enable one MID mode for SDCC5 (major 1) on 8916/8939 (minor 0x2e) and + * on 8992 (minor 0x3e) as a workaround to reset for data stuck issue. + */ + if (major == 1 && (minor == 0x2e || minor == 0x3e)) { + val = readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_FUNC2); + writel_relaxed((val | CORE_ONE_MID_EN), + host->ioaddr + CORE_VENDOR_SPEC_FUNC2); + } /* * SDCC 5 controller with major version 1, minor version 0x34 and later * with HS 400 mode support will use CM DLL instead of CDC LP 533 DLL. From 98aaf4822e8754594aa2f24498b1f35e38739535 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Tue, 25 Aug 2015 15:00:25 +0530 Subject: [PATCH 349/472] mmc: sdhci: Add new host->op and quirk to apply reset workaround The SDHCI reset for data is getting stuck on some of sdhci-msm controllers. The SDHCI reset usually waits for any pending transfers on the bus before proceeding with the controller reset. But in the failure cases, the data transfer seems to be stuck on the bus and thus preventing the controller from being reset. The workaround is to force the controller to be reset under such scenarios. This seems to be helping the controller to return back to good state at least for the next commands following the failure. Change-Id: I487bdf3bd4afb18e69afa778aa38c3574d69e2f7 Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 32 ++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci.h | 6 ++++++ 2 files changed, 38 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 191cbb3cf203..cd588c98e47f 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -241,6 +241,7 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) { unsigned long timeout; +retry_reset: sdhci_writeb(host, mask, SDHCI_SOFTWARE_RESET); if (mask & SDHCI_RESET_ALL) { @@ -266,12 +267,43 @@ void sdhci_reset(struct sdhci_host *host, u8 mask) if (timeout == 0) { pr_err("%s: Reset 0x%x never completed.\n", mmc_hostname(host->mmc), (int)mask); + if ((host->quirks2 & SDHCI_QUIRK2_USE_RESET_WORKAROUND) + && host->ops->reset_workaround) { + if (!host->reset_wa_applied) { + /* + * apply the workaround and issue + * reset again. + */ + host->ops->reset_workaround(host, 1); + host->reset_wa_applied = 1; + host->reset_wa_cnt++; + goto retry_reset; + } else { + pr_err("%s: Reset 0x%x failed with workaround\n", + mmc_hostname(host->mmc), + (int)mask); + /* clear the workaround */ + host->ops->reset_workaround(host, 0); + host->reset_wa_applied = 0; + } + } + sdhci_dumpregs(host); return; } timeout--; mdelay(1); } + + if ((host->quirks2 & SDHCI_QUIRK2_USE_RESET_WORKAROUND) && + host->ops->reset_workaround && host->reset_wa_applied) { + pr_info("%s: Reset 0x%x successful with workaround\n", + mmc_hostname(host->mmc), (int)mask); + /* clear the workaround */ + host->ops->reset_workaround(host, 0); + host->reset_wa_applied = 0; + } + } EXPORT_SYMBOL_GPL(sdhci_reset); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 2046f4a7fac8..b044306ea585 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -501,6 +501,8 @@ struct sdhci_host { /* Controller has nonstandard clock management */ #define SDHCI_QUIRK_NONSTANDARD_CLOCK (1<<25) +/* Use reset workaround in case sdhci reset timeouts */ +#define SDHCI_QUIRK2_USE_RESET_WORKAROUND (1<<26) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ @@ -616,6 +618,9 @@ struct sdhci_host { u32 auto_cmd_err_sts; struct ratelimit_state dbg_dump_rs; struct cmdq_host *cq_host; + int reset_wa_applied; /* reset workaround status */ + ktime_t reset_wa_t; /* time when the reset workaround is applied */ + int reset_wa_cnt; /* total number of times workaround is used */ unsigned long private[0] ____cacheline_aligned; }; @@ -676,6 +681,7 @@ struct sdhci_ops { unsigned int max_dtr, int host_drv, int card_drv, int *drv_type); int (*notify_load)(struct sdhci_host *host, enum mmc_load state); + void (*reset_workaround)(struct sdhci_host *host, u32 enable); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS From 23f6b674de5707bfc4d303837bd8953c71e761d1 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Tue, 25 Aug 2015 16:11:20 +0530 Subject: [PATCH 350/472] mmc: sdhci-msm: Implement reset workaround and enable it The SDHCI reset for data is getting stuck on some of sdhci-msm controllers. The SDHCI reset usually waits for any pending transfers on the bus before proceeding with the controller reset. But in the failure cases, the data transfer seems to be stuck on the bus and thus preventing the controller from being reset. The workaround is to force the controller to be reset under such scenarios. This seems to be helping the controller to return back to good state at least for the next commands following the failure. This issue is found on SDCC5 controller of 8916/8939 and 8992 chipsets. Hence, enable the quirk SDHCI_QUIRK2_USE_RESET_WORKAROUND only on those controllers. Change-Id: Id49009736beb410ccb2535d614786a7c48098f85 Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci-msm.c | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e194a2ad55ad..19f9e9deb0f3 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -119,6 +119,8 @@ #define CORE_VENDOR_SPEC_ADMA_ERR_ADDR1 0x118 #define CORE_VENDOR_SPEC_FUNC2 0x110 +#define HC_SW_RST_WAIT_IDLE_DIS (1 << 20) +#define HC_SW_RST_REQ (1 << 21) #define CORE_ONE_MID_EN (1 << 25) #define CORE_VENDOR_SPEC_CAPABILITIES0 0x11C @@ -2953,6 +2955,48 @@ int sdhci_msm_notify_load(struct sdhci_host *host, enum mmc_load state) return 0; } +void sdhci_msm_reset_workaround(struct sdhci_host *host, u32 enable) +{ + u32 vendor_func2; + unsigned long timeout; + + vendor_func2 = readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_FUNC2); + + if (enable) { + writel_relaxed(vendor_func2 | HC_SW_RST_REQ, host->ioaddr + + CORE_VENDOR_SPEC_FUNC2); + timeout = 10000; + while (readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_FUNC2) & + HC_SW_RST_REQ) { + if (timeout == 0) { + pr_info("%s: Applying wait idle disable workaround\n", + mmc_hostname(host->mmc)); + /* + * Apply the reset workaround to not wait for + * pending data transfers on AXI before + * resetting the controller. This could be + * risky if the transfers were stuck on the + * AXI bus. + */ + vendor_func2 = readl_relaxed(host->ioaddr + + CORE_VENDOR_SPEC_FUNC2); + writel_relaxed(vendor_func2 | + HC_SW_RST_WAIT_IDLE_DIS, + host->ioaddr + CORE_VENDOR_SPEC_FUNC2); + host->reset_wa_t = ktime_get(); + return; + } + timeout--; + udelay(10); + } + pr_info("%s: waiting for SW_RST_REQ is successful\n", + mmc_hostname(host->mmc)); + } else { + writel_relaxed(vendor_func2 & ~HC_SW_RST_WAIT_IDLE_DIS, + host->ioaddr + CORE_VENDOR_SPEC_FUNC2); + } +} + static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, .crypto_engine_reset = sdhci_msm_ice_reset, @@ -2974,6 +3018,7 @@ static struct sdhci_ops sdhci_msm_ops = { .enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask, .detect = sdhci_msm_detect, .notify_load = sdhci_msm_notify_load, + .reset_workaround = sdhci_msm_reset_workaround, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, @@ -3016,6 +3061,7 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, * on 8992 (minor 0x3e) as a workaround to reset for data stuck issue. */ if (major == 1 && (minor == 0x2e || minor == 0x3e)) { + host->quirks2 |= SDHCI_QUIRK2_USE_RESET_WORKAROUND; val = readl_relaxed(host->ioaddr + CORE_VENDOR_SPEC_FUNC2); writel_relaxed((val | CORE_ONE_MID_EN), host->ioaddr + CORE_VENDOR_SPEC_FUNC2); From 8f0dd76547b0c4c43419a82f47c790057704c07f Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Thu, 23 Jul 2015 18:45:37 +0530 Subject: [PATCH 351/472] mmc: sdhci: Add new quirk for broken SDHCI LED control Some controllers may not have any LED control to indicate its status. Use this quirk for such controllers to avoid registering any LED device with LED class and also to avoid exposing sysfs nodes which doesn't actually control any LED. Change-Id: I7e8f1b8d2d735685ede87df4bb7fb32ad0a10246 Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 33 +++++++++++++++++++-------------- drivers/mmc/host/sdhci.h | 2 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index cd588c98e47f..bd84d0d5385c 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1709,7 +1709,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) WARN_ON(host->mrq != NULL); #ifndef SDHCI_USE_LEDS_CLASS - sdhci_activate_led(host); + if (!(host->quirks2 & SDHCI_QUIRK2_BROKEN_LED_CONTROL)) + sdhci_activate_led(host); #endif /* @@ -2714,7 +2715,8 @@ static void sdhci_tasklet_finish(unsigned long param) host->auto_cmd_err_sts = 0; #ifndef SDHCI_USE_LEDS_CLASS - sdhci_deactivate_led(host); + if (!(host->quirks2 & SDHCI_QUIRK2_BROKEN_LED_CONTROL)) + sdhci_deactivate_led(host); #endif mmiowb(); @@ -4103,18 +4105,20 @@ int sdhci_add_host(struct sdhci_host *host) #endif #ifdef SDHCI_USE_LEDS_CLASS - snprintf(host->led_name, sizeof(host->led_name), - "%s::", mmc_hostname(mmc)); - host->led.name = host->led_name; - host->led.brightness = LED_OFF; - host->led.default_trigger = mmc_hostname(mmc); - host->led.brightness_set = sdhci_led_control; + if (!(host->quirks2 & SDHCI_QUIRK2_BROKEN_LED_CONTROL)) { + snprintf(host->led_name, sizeof(host->led_name), + "%s::", mmc_hostname(mmc)); + host->led.name = host->led_name; + host->led.brightness = LED_OFF; + host->led.default_trigger = mmc_hostname(mmc); + host->led.brightness_set = sdhci_led_control; - ret = led_classdev_register(mmc_dev(mmc), &host->led); - if (ret) { - pr_err("%s: Failed to register LED device: %d\n", - mmc_hostname(mmc), ret); - goto reset; + ret = led_classdev_register(mmc_dev(mmc), &host->led); + if (ret) { + pr_err("%s: Failed to register LED device: %d\n", + mmc_hostname(mmc), ret); + goto reset; + } } #endif @@ -4212,7 +4216,8 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) mmc_remove_host(host->mmc); #ifdef SDHCI_USE_LEDS_CLASS - led_classdev_unregister(&host->led); + if (!(host->quirks2 & SDHCI_QUIRK2_BROKEN_LED_CONTROL)) + led_classdev_unregister(&host->led); #endif if (!dead) diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index b044306ea585..50a11128b32d 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -503,6 +503,8 @@ struct sdhci_host { #define SDHCI_QUIRK_NONSTANDARD_CLOCK (1<<25) /* Use reset workaround in case sdhci reset timeouts */ #define SDHCI_QUIRK2_USE_RESET_WORKAROUND (1<<26) +/* Some controllers doesn't have have any LED control */ +#define SDHCI_QUIRK2_BROKEN_LED_CONTROL (1<<27) int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ From d5fc519b5ed933b9154a5c82cdb3a99c0e9c6904 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 15 Dec 2014 17:45:03 -0800 Subject: [PATCH 352/472] mmc: sdhci: Fix unclocked register access During platform driver probe we call mmc_start_host in sdhci_add_host, which could start the mmc_rescan work immediately and trigger a runtime suspend. This creates a race condition where the clocks could be turned off even before the probe has completed leading to unclocked register access. CRs-Fixed: 770843 Change-Id: I77ae36f805e496d56ed96db3ccaa83f2c37c926c Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index bd84d0d5385c..20542f99b436 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -4141,8 +4141,6 @@ int sdhci_add_host(struct sdhci_host *host) mmc_hostname(mmc), ret); } - mmc_add_host(mmc); - if (host->quirks2 & SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR) { host->ier = (host->ier & ~SDHCI_INT_DATA_END_BIT); sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); @@ -4171,6 +4169,7 @@ int sdhci_add_host(struct sdhci_host *host) sdhci_enable_card_detection(host); + mmc_add_host(mmc); return 0; #ifdef SDHCI_USE_LEDS_CLASS From 2e35d277288ce9c3c5ff70b4b975b82c64d75349 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Fri, 14 Nov 2014 11:06:40 +0530 Subject: [PATCH 353/472] mmc: sdhci: Dont enable CDR for tuning commands Currently we enable CDR for every read command including for tuning procedure which is not correct (as CDR if enabled might correct the phase during tuning and we wont be able to detect the correct phase during tuning). So, disable CDR for read tuning commands. CRs-fixed: 759398 Change-Id: I051b6e3b204dde22cdc973759c3e32d0a81c369a Signed-off-by: Ritesh Harjani Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci.c | 10 ++++++++-- include/uapi/linux/mmc/mmc.h | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 20542f99b436..d6186001070e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1063,8 +1063,14 @@ static void sdhci_set_transfer_mode(struct sdhci_host *host, if (data->flags & MMC_DATA_READ) { mode |= SDHCI_TRNS_READ; - if (host->ops->toggle_cdr) - host->ops->toggle_cdr(host, true); + if (host->ops->toggle_cdr) { + if ((cmd->opcode == MMC_SEND_TUNING_BLOCK_HS200) || + (cmd->opcode == MMC_SEND_TUNING_BLOCK_HS400) || + (cmd->opcode == MMC_SEND_TUNING_BLOCK)) + host->ops->toggle_cdr(host, false); + else + host->ops->toggle_cdr(host, true); + } } if (host->ops->toggle_cdr && (data->flags & MMC_DATA_WRITE)) host->ops->toggle_cdr(host, false); diff --git a/include/uapi/linux/mmc/mmc.h b/include/uapi/linux/mmc/mmc.h index f75ae945c0c5..dd9f3a644169 100644 --- a/include/uapi/linux/mmc/mmc.h +++ b/include/uapi/linux/mmc/mmc.h @@ -29,6 +29,7 @@ #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 */ +#define MMC_SEND_TUNING_BLOCK_HS400 MMC_SEND_TUNING_BLOCK_HS200 #define MMC_TUNING_BLK_PATTERN_4BIT_SIZE 64 #define MMC_TUNING_BLK_PATTERN_8BIT_SIZE 128 From 772de4cace266954ace717fa0f12f3f3093ed885 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Wed, 22 Jul 2015 17:25:09 +0530 Subject: [PATCH 354/472] mmc: core: update host->card after getting RCA for SD card During tuning the status command CMD13 needs to be sent to the card to know the card's state upon any failure to tuning command. Change-Id: Iaefc80305d101bd72ff22f792b1967379507a739 Signed-off-by: Sahitya Tummala Signed-off-by: Pavan Anamula --- drivers/mmc/core/sd.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 486910f34e0f..c27a0d44c74b 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1032,6 +1032,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, err = mmc_send_relative_addr(host, &card->rca); if (err) goto free_card; + host->card = card; } if (!oldcard) { @@ -1098,12 +1099,13 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, card->clk_scaling_highest = mmc_sd_get_max_clock(card); card->clk_scaling_lowest = host->f_min; - host->card = card; return 0; free_card: - if (!oldcard) + if (!oldcard) { + host->card = NULL; mmc_remove_card(card); + } return err; } From 085c9196375e463476300d02691c249d1ca00362 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Fri, 14 Nov 2014 11:09:56 +0530 Subject: [PATCH 355/472] mmc: sdhci-msm: Correct the CDR toggle logic We should keep either one of CDR_EN or CDR_EXT_EN enabled. So correct this logic in toggle CDR function. CRs-fixed: 759398 Change-Id: Ic137ae2a28e912ab131644ff9d81e41f4256dd05 Signed-off-by: Ritesh Harjani Signed-off-by: Pavan Anamula --- drivers/mmc/host/sdhci-msm.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 19f9e9deb0f3..71e76c97172f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2366,14 +2366,17 @@ static void sdhci_msm_check_power_status(struct sdhci_host *host, u32 req_type) static void sdhci_msm_toggle_cdr(struct sdhci_host *host, bool enable) { - if (enable) - writel_relaxed((readl_relaxed(host->ioaddr + - CORE_DLL_CONFIG) | CORE_CDR_EN), - host->ioaddr + CORE_DLL_CONFIG); - else - writel_relaxed((readl_relaxed(host->ioaddr + - CORE_DLL_CONFIG) & ~CORE_CDR_EN), - host->ioaddr + CORE_DLL_CONFIG); + u32 config = readl_relaxed(host->ioaddr + CORE_DLL_CONFIG); + + if (enable) { + config |= CORE_CDR_EN; + config &= ~CORE_CDR_EXT_EN; + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + } else { + config &= ~CORE_CDR_EN; + config |= CORE_CDR_EXT_EN; + writel_relaxed(config, host->ioaddr + CORE_DLL_CONFIG); + } } static unsigned int sdhci_msm_max_segs(void) From 35f2004cf997cd3969d00245debeecb2733da783 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 5 Aug 2015 11:27:00 +0530 Subject: [PATCH 356/472] mmc: sdhci-msm: Set MMC_CAP_WAIT_WHILE_BUSY capability For any cmd we have a DAT line timeouts which we set in TIMEOUT_CONTROL register of sdhci. For commands with busy response (R1B), cmd is followed by a busy period exercised by card, by pulling DAT0 line low (in case of CMD5). Here host controller detects this busy period and waits for either busy period to finish or timeout to happen based on value set in SDHCI_TIMEOUT_CONTROL register. Thus for R1B commands, host controller(sdhci) is capable of sending two interrupts. 1st is the CMD response(0th bit - Command complete of Normal Interrupt Status register ) and 2nd is when the busy period has ended(1st bit - Transfer Complete bit of Normal Interrupt Status register). If MMC_CAP_WAIT_WHILE_BUSY is not enabled by the host controller driver then core layer explictely waits for fixed amount time specified by s_a_timeout parameter which is generally very high when compared to amount of time card keeps the DAT0 line low. As sdhci-msm is capable of detecting this busy period, set MMC_CAP_WAIT_WHILE_BUSY capability in the host controller driver to avoid redundant wait period. On 8952 this saves us ~110ms during mmc suspend. Change-Id: Ibb3a70575a06a5ffd1ccc3adaa96dfb3c3e22e3a Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 71e76c97172f..d85a065fe2d1 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3462,6 +3462,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; msm_host->mmc->caps |= msm_host->pdata->caps; msm_host->mmc->caps |= MMC_CAP_AGGRESSIVE_PM; + msm_host->mmc->caps |= MMC_CAP_WAIT_WHILE_BUSY; msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; From 418480167fb0cb68853bc27209e2d5de40d45b05 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 1 Sep 2015 15:52:29 -0700 Subject: [PATCH 357/472] mmc: sdhci: clear host mrq in case of error In case of a crypto error during request submission clear the host mrq structure in preparation for the next request and dump the current register state for further debugging. Change-Id: I2eeda8589ca4c83bbb4a1b372e9363224bbfb680 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index d6186001070e..fdf80b198d66 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1765,6 +1765,8 @@ end_req: mrq->cmd->error = -EIO; if (mrq->data) mrq->data->error = -EIO; + host->mrq = NULL; + sdhci_dumpregs(host); mmc_request_done(host->mmc, mrq); sdhci_runtime_pm_put(host); } From ffdb0e974a9981322d0bc3548f6421a15a4de90b Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Sun, 6 Sep 2015 16:38:42 +0300 Subject: [PATCH 358/472] mmc: core: add support for devfreq suspend/resume This change adds use of devfreq suspend/resume API. In the current version of the code, devfreq is fully brought up/down on each runtime resume/suspend which causes statistics loss and is time consuming. This change addresses the above by adding support for devfreq suspend/resume to be called on each system suspend/resume, runtime suspend/resume, power restore. Change-Id: Id209826fb9499084ae96c7d3a47e4032326f61e9 Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 152 ++++++++++++++++++++++++++-------------- drivers/mmc/core/core.h | 4 +- drivers/mmc/core/host.c | 2 - drivers/mmc/core/mmc.c | 29 ++++---- drivers/mmc/core/sd.c | 29 +++++--- 5 files changed, 134 insertions(+), 82 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index da0affe8b651..a1b92cdbe83d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -259,25 +259,6 @@ out: } EXPORT_SYMBOL(mmc_cmdq_clk_scaling_stop_busy); -/** - * mmc_disable_devfreq_clk_scaling() - Disable clock scaling - * @host: pointer to mmc host structure - * - * Disables clock scaling aggresively - */ -void mmc_disable_clk_scaling(struct mmc_host *host) -{ - if (!host) { - pr_err("bad host parameter\n"); - WARN_ON(1); - return; - } - pr_debug("%s: disabling clock scaling\n", mmc_hostname(host)); - mmc_exit_clk_scaling(host); - -} -EXPORT_SYMBOL(mmc_disable_clk_scaling); - /** * mmc_can_scale_clk() - Check clock scaling capability * @host: pointer to mmc host structure @@ -290,8 +271,7 @@ bool mmc_can_scale_clk(struct mmc_host *host) return false; } - return (host->caps2 & MMC_CAP2_CLK_SCALE) && - (!(host->pm_flags & MMC_PM_IGNORE_PM_NOTIFY)); + return host->caps2 & MMC_CAP2_CLK_SCALE; } EXPORT_SYMBOL(mmc_can_scale_clk); @@ -738,6 +718,94 @@ int mmc_init_clk_scaling(struct mmc_host *host) } EXPORT_SYMBOL(mmc_init_clk_scaling); +/** + * mmc_suspend_clk_scaling() - suspend clock scaling + * @host: pointer to mmc host structure + * + * This API will suspend devfreq feature for the specific host. + * The statistics collected by mmc will be cleared. + * This function is intended to be called by the pm callbacks + * (e.g. runtime_suspend, suspend) of the mmc device + */ +int mmc_suspend_clk_scaling(struct mmc_host *host) +{ + int err; + + if (!host) { + WARN(1, "bad host parameter\n"); + return -EINVAL; + } + + if (!mmc_can_scale_clk(host) || !host->clk_scaling.enable) + return 0; + + if (!host->clk_scaling.devfreq) { + pr_err("%s: %s: no devfreq is assosiated with this device\n", + mmc_hostname(host), __func__); + return -EPERM; + } + + atomic_inc(&host->clk_scaling.devfreq_abort); + err = devfreq_suspend_device(host->clk_scaling.devfreq); + if (err) { + pr_err("%s: %s: failed to suspend devfreq\n", + mmc_hostname(host), __func__); + return err; + } + host->clk_scaling.enable = false; + + host->clk_scaling.total_busy_time_us = 0; + + pr_debug("%s: devfreq suspended\n", mmc_hostname(host)); + + return 0; +} +EXPORT_SYMBOL(mmc_suspend_clk_scaling); + +/** + * mmc_resume_clk_scaling() - resume clock scaling + * @host: pointer to mmc host structure + * + * This API will resume devfreq feature for the specific host. + * This API is intended to be called by the pm callbacks + * (e.g. runtime_suspend, suspend) of the mmc device + */ +int mmc_resume_clk_scaling(struct mmc_host *host) +{ + int err = 0; + + if (!host) { + WARN(1, "bad host parameter\n"); + return -EINVAL; + } + + if (!mmc_can_scale_clk(host)) + return 0; + + if (!host->clk_scaling.devfreq) { + pr_err("%s: %s: no devfreq is assosiated with this device\n", + mmc_hostname(host), __func__); + return -EPERM; + } + + atomic_set(&host->clk_scaling.devfreq_abort, 0); + host->clk_scaling.curr_freq = host->ios.clock; + host->clk_scaling.clk_scaling_in_progress = false; + host->clk_scaling.need_freq_change = false; + + err = devfreq_resume_device(host->clk_scaling.devfreq); + if (err) { + pr_err("%s: %s: failed to resume devfreq (%d)\n", + mmc_hostname(host), __func__, err); + } else { + host->clk_scaling.enable = true; + pr_debug("%s: devfreq resumed\n", mmc_hostname(host)); + } + + return err; +} +EXPORT_SYMBOL(mmc_resume_clk_scaling); + /** * mmc_exit_devfreq_clk_scaling() - Disable clock scaling * @host: pointer to mmc host structure @@ -749,65 +817,42 @@ int mmc_exit_clk_scaling(struct mmc_host *host) int err; if (!host) { - pr_err("bad host parameter\n"); + pr_err("%s: bad host parameter\n", __func__); WARN_ON(1); return -EINVAL; } - if (!mmc_can_scale_clk(host) || !host->clk_scaling.enable) + if (!mmc_can_scale_clk(host)) return 0; if (!host->clk_scaling.devfreq) { - pr_err("%s: no devfreq is assosiated with this device\n", - mmc_hostname(host)); + pr_err("%s: %s: no devfreq is assosiated with this device\n", + mmc_hostname(host), __func__); return -EPERM; } - host->clk_scaling.enable = false; - atomic_inc(&host->clk_scaling.devfreq_abort); - err = devfreq_suspend_device(host->clk_scaling.devfreq); + err = mmc_suspend_clk_scaling(host); if (err) { - pr_err("%s: failed to suspend devfreq\n", mmc_hostname(host)); + pr_err("%s: %s: fail to suspend clock scaling (%d)\n", + mmc_hostname(host), __func__, err); return err; } - pr_debug("%s: devfreq suspended\n", mmc_hostname(host)); err = devfreq_remove_device(host->clk_scaling.devfreq); if (err) { - pr_err("%s: remove devfreq failed\n", mmc_hostname(host)); + pr_err("%s: remove devfreq failed (%d)\n", + mmc_hostname(host), err); return err; } host->clk_scaling.devfreq = NULL; atomic_set(&host->clk_scaling.devfreq_abort, 1); - mmc_reset_clk_scale_stats(host); pr_debug("%s: devfreq was removed\n", mmc_hostname(host)); return 0; } EXPORT_SYMBOL(mmc_exit_clk_scaling); - -/** - * mmc_reset_clk_scale_stats() - reset clock scaling statistics - * @host: pointer to mmc host structure - */ -void mmc_reset_clk_scale_stats(struct mmc_host *host) -{ - if (!host) { - pr_err("bad host parameter\n"); - WARN_ON(1); - return; - } - if (!host->clk_scaling.enable) - return; - spin_lock_bh(&host->clk_scaling.lock); - host->clk_scaling.total_busy_time_us = 0; - spin_unlock_bh(&host->clk_scaling.lock); - -} -EXPORT_SYMBOL(mmc_reset_clk_scale_stats); - /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -4166,7 +4211,6 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_unlock_irqrestore(&host->lock, flags); cancel_delayed_work_sync(&host->detect); - mmc_disable_clk_scaling(host); if (!host->bus_ops) break; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index af0b13a492a5..1116544eebc1 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -78,11 +78,11 @@ void mmc_remove_card_debugfs(struct mmc_card *card); void mmc_init_context_info(struct mmc_host *host); -extern void mmc_disable_clk_scaling(struct mmc_host *host); extern bool mmc_can_scale_clk(struct mmc_host *host); extern int mmc_init_clk_scaling(struct mmc_host *host); +extern int mmc_suspend_clk_scaling(struct mmc_host *host); +extern int mmc_resume_clk_scaling(struct mmc_host *host); extern int mmc_exit_clk_scaling(struct mmc_host *host); -extern void mmc_reset_clk_scale_stats(struct mmc_host *host); extern unsigned long mmc_get_max_frequency(struct mmc_host *host); int mmc_execute_tuning(struct mmc_card *card); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index e30261c1d9d2..fe1d90357653 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -172,8 +172,6 @@ void mmc_host_clk_hold(struct mmc_host *host) spin_unlock_irqrestore(&host->clk_lock, flags); mmc_ungate_clock(host); - /* Reset clock scaling stats as host is out of idle */ - mmc_reset_clk_scale_stats(host); spin_lock_irqsave(&host->clk_lock, flags); pr_debug("%s: ungated MCI clock\n", mmc_hostname(host)); } diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index a0cc1242f299..1ce3a3d58f11 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2347,12 +2347,12 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) BUG_ON(!host); BUG_ON(!host->card); - /* - * Disable clock scaling before suspend and enable it after resume so - * as to avoid clock scaling decisions kicking in during this window. - */ - if (mmc_can_scale_clk(host)) - mmc_disable_clk_scaling(host); + err = mmc_suspend_clk_scaling(host); + if (err) { + pr_err("%s: %s: fail to suspend clock scaling (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } mmc_claim_host(host); @@ -2472,12 +2472,10 @@ static int _mmc_resume(struct mmc_host *host) mmc_release_host(host); - /* - * We have done full initialization of the card, - * reset the clk scale stats and current frequency. - */ - if (mmc_can_scale_clk(host)) - mmc_init_clk_scaling(host); + err = mmc_resume_clk_scaling(host); + if (err) + pr_err("%s: %s: fail to resume clock scaling (%d)\n", + mmc_hostname(host), __func__, err); out: if (!err) @@ -2543,8 +2541,6 @@ static int mmc_runtime_resume(struct mmc_host *host) pr_err("%s: error %d doing aggressive resume\n", mmc_hostname(host), err); - mmc_init_clk_scaling(host); - trace_mmc_runtime_resume(mmc_hostname(host), err, ktime_to_us(ktime_sub(ktime_get(), start))); @@ -2711,6 +2707,11 @@ int mmc_attach_mmc(struct mmc_host *host) goto remove_card; mmc_claim_host(host); + err = mmc_init_clk_scaling(host); + if (err) { + mmc_release_host(host); + goto remove_card; + } register_reboot_notifier(&host->card->reboot_notify); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index c27a0d44c74b..f239e5d21856 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -1190,12 +1190,12 @@ static int _mmc_sd_suspend(struct mmc_host *host) BUG_ON(!host); BUG_ON(!host->card); - /* - * Disable clock scaling before suspend and enable it after resume so - * as to avoid clock scaling decisions kicking in during this window. - */ - if (mmc_can_scale_clk(host)) - mmc_disable_clk_scaling(host); + err = mmc_suspend_clk_scaling(host); + if (err) { + pr_err("%s: %s: fail to suspend clock scaling (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } mmc_claim_host(host); @@ -1273,8 +1273,12 @@ static int _mmc_sd_resume(struct mmc_host *host) #endif mmc_card_clr_suspended(host->card); - if (mmc_can_scale_clk(host)) - mmc_init_clk_scaling(host); + err = mmc_resume_clk_scaling(host); + if (err) { + pr_err("%s: %s: fail to resume clock scaling (%d)\n", + mmc_hostname(host), __func__, err); + goto out; + } out: mmc_release_host(host); @@ -1331,8 +1335,6 @@ static int mmc_sd_runtime_resume(struct mmc_host *host) pr_err("%s: error %d doing aggressive resume\n", mmc_hostname(host), err); - mmc_init_clk_scaling(host); - return 0; } @@ -1432,6 +1434,13 @@ int mmc_attach_sd(struct mmc_host *host) goto remove_card; mmc_claim_host(host); + + err = mmc_init_clk_scaling(host); + if (err) { + mmc_release_host(host); + goto remove_card; + } + return 0; remove_card: From b6184a1b463a6f16ca33ae9699942075843d2e12 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 4 Sep 2015 18:32:25 -0700 Subject: [PATCH 359/472] mmc: sdhci-msm: fix register address change for DDR_CONFIG The power on reset value of DDR_CONFIG register was fixed in controller revision (major - 0x1 and minor > 0x49) to address the default rclk delay value after characterization. The register offset for this register was also changed starting from this revision. Make necessary changes to account for this. Change-Id: I4e4a87aebd24e5669b03a914c6e0f4b469f5ec7b Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 17 ++++++++++++++--- drivers/mmc/host/sdhci-msm.h | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d85a065fe2d1..b961a3d9339c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -173,6 +173,8 @@ #define DDR_CONFIG_POR_VAL 0x80040853 #define DDR_CONFIG_PRG_RCLK_DLY_MASK 0x1FF #define DDR_CONFIG_PRG_RCLK_DLY 115 +#define CORE_DDR_CONFIG_2 0x1BC +#define DDR_CONFIG_2_POR_VAL 0x80040873 #define MSM_MMC_DEFAULT_CPU_DMA_LATENCY 200 /* usecs */ @@ -777,9 +779,15 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) * Reprogramming the value in case it might have been modified by * bootloaders. */ - ddr_config = DDR_CONFIG_POR_VAL & ~DDR_CONFIG_PRG_RCLK_DLY_MASK; - ddr_config |= DDR_CONFIG_PRG_RCLK_DLY; - writel_relaxed(ddr_config, host->ioaddr + CORE_DDR_CONFIG); + if (msm_host->rclk_delay_fix) { + writel_relaxed(DDR_CONFIG_2_POR_VAL, + host->ioaddr + CORE_DDR_CONFIG_2); + } else { + ddr_config = DDR_CONFIG_POR_VAL & + ~DDR_CONFIG_PRG_RCLK_DLY_MASK; + ddr_config |= DDR_CONFIG_PRG_RCLK_DLY; + writel_relaxed(ddr_config, host->ioaddr + CORE_DDR_CONFIG); + } if (msm_host->enhanced_strobe) writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) @@ -3097,6 +3105,9 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, if ((major == 1) && ((minor == 0x42) || (minor == 0x46) || (minor == 0x49))) msm_host->use_14lpp_dll = true; + + if ((major == 1) && (minor >= 0x49)) + msm_host->rclk_delay_fix = true; /* * Mask 64-bit support for controller with 32-bit address bus so that * smaller descriptor size will be used and improve memory consumption. diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 1602668c0ff4..8cd799532c64 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -157,6 +157,7 @@ struct sdhci_msm_host { bool use_updated_dll_reset; bool use_14lpp_dll; bool enhanced_strobe; + bool rclk_delay_fix; u32 caps_0; struct sdhci_msm_ice_data ice; u32 ice_clk_rate; From ae7480062fd447688bc6bcc92cff7552571731d4 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 9 Sep 2015 19:05:50 -0700 Subject: [PATCH 360/472] mmc: cmdq_hci: Don't reset tag id on error path Use the tag id of the error'd request and don't reset it to zero; to handle the error'd request appropriately. Change-Id: I0f5eac47197fa7b59208d0a61776d4ba186aa3dc Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/cmdq_hci.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 1cd274303d03..04441073242b 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -694,7 +694,6 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) mrq->data->error = err; } - tag = 0; /* * CQE detected a reponse error from device * In most cases, this would require a reset. From 5175b0f4f57ed86136a89aac8e45ae56f68d9b92 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 10 Sep 2015 12:25:27 -0700 Subject: [PATCH 361/472] mmc: sdhci-msm: return correct error code if emmc is not bootdevice Update the error code to ENODEV if eMMC is not the boot device. Change-Id: Ide0863a5aa64f9990d39095de6f6b13f752a6b3e Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index b961a3d9339c..0e8f99050d53 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3229,8 +3229,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) } /* skip the probe if eMMC isn't a boot device */ - if ((ret == 1) && !sdhci_msm_is_bootdevice(&pdev->dev)) + if ((ret == 1) && !sdhci_msm_is_bootdevice(&pdev->dev)) { + ret = -ENODEV; goto pltfm_free; + } if (disable_slots & (1 << (ret - 1))) { dev_info(&pdev->dev, "%s: Slot %d disabled\n", __func__, From cedee495d8ea2a70fbbd928f1dbbaf4d2a2db82c Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 28 Aug 2015 17:18:54 -0700 Subject: [PATCH 362/472] mmc: card: fix null pointer deference in cmdq timeout handler The mmc_queue_req will be present only if the request is issued to the LLD. The request could be fetched from block layer queue but could be waiting to be issued (for e.g. clock scaling is waiting for an empty cmdq queue). Evaluate such cases and reset the timer to give LLD more time. Change-Id: Ic5818f5c2b8356bda9b1612d78b65e07dad011d7 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index ed868929b164..7224f4c57421 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2989,18 +2989,38 @@ static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) struct mmc_queue *mq = req->q->queuedata; struct mmc_host *host = mq->card->host; struct mmc_queue_req *mq_rq = req->special; - struct mmc_request *mrq = &mq_rq->cmdq_req.mrq; - struct mmc_cmdq_req *cmdq_req = &mq_rq->cmdq_req; + struct mmc_request *mrq; + struct mmc_cmdq_req *cmdq_req; + + BUG_ON(!host); + + /* + * The mmc_queue_req will be present only if the request + * is issued to the LLD. The request could be fetched from + * block layer queue but could be waiting to be issued + * (for e.g. clock scaling is waiting for an empty cmdq queue) + * Reset the timer in such cases to give LLD more time + */ + if (!mq_rq) { + pr_warn("%s: restart timer for tag: %d\n", __func__, req->tag); + return BLK_EH_RESET_TIMER; + } + + mrq = &mq_rq->cmdq_req.mrq; + cmdq_req = &mq_rq->cmdq_req; + + BUG_ON(!mrq || !cmdq_req); if (cmdq_req->cmdq_req_flags & DCMD) mrq->cmd->error = -ETIMEDOUT; else mrq->data->error = -ETIMEDOUT; + BUG_ON(host->err_mrq != NULL); host->err_mrq = mrq; - mrq->done(mrq); - return BLK_EH_NOT_HANDLED; + mmc_host_clk_release(mrq->host); + return BLK_EH_HANDLED; } static void mmc_blk_cmdq_err(struct mmc_queue *mq) @@ -3081,6 +3101,7 @@ unhalt: mmc_cmdq_halt(host, false); out: + host->err_mrq = NULL; pm_runtime_mark_last_busy(&card->dev); __mmc_put_card(card); From 2f3532d344cd186e7f185e3ff566f54ce6df3aec Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 1 Sep 2015 16:44:08 +0530 Subject: [PATCH 363/472] mmc: cmdq: add new crypto_cfg_reset host operation When encryption/decryption is enabled in CQ mode, the legacy commands that are sent in HALT state will use different slot other than slot 0 for crypto configuration information. The slot that is selected depends on the last slot that was used when it is in CQ mode. This is causing the data of legacy commands to be encrypted/decrypted based on the wrong slot usage for crypto config details. Hence, clear the crypto configuration of the slot used in CQ mode whenever it gets completed. Change-Id: If573de5025054a10de1dde544aa79022016f65fd Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 2 ++ drivers/mmc/host/cmdq_hci.h | 1 + drivers/mmc/host/sdhci.c | 16 ++++++++++++++++ drivers/mmc/host/sdhci.h | 1 + 4 files changed, 20 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 04441073242b..5aa704048d9d 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -653,6 +653,8 @@ static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) CMDQ_SEND_STATUS_TRIGGER, CQCTL); cmdq_runtime_pm_put(cq_host); + if (cq_host->ops->crypto_cfg_reset) + cq_host->ops->crypto_cfg_reset(mmc, tag); mrq->done(mrq); } diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index 610715f1875c..d136e35032db 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -206,6 +206,7 @@ struct cmdq_host_ops { int (*reset)(struct mmc_host *mmc); int (*crypto_cfg)(struct mmc_host *mmc, struct mmc_request *mrq, u32 slot); + void (*crypto_cfg_reset)(struct mmc_host *mmc, unsigned int slot); void (*post_cqe_halt)(struct mmc_host *mmc); }; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index fdf80b198d66..7be75740650d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3530,6 +3530,17 @@ static int sdhci_cmdq_crypto_cfg(struct mmc_host *mmc, return sdhci_crypto_cfg(host, mrq, slot); } +static void sdhci_cmdq_crypto_cfg_reset(struct mmc_host *mmc, unsigned int slot) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (!host->is_crypto_en) + return; + + if (host->ops->crypto_cfg_reset) + host->ops->crypto_cfg_reset(host, slot); +} + static void sdhci_cmdq_post_cqe_halt(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); @@ -3580,6 +3591,10 @@ static int sdhci_cmdq_crypto_cfg(struct mmc_host *mmc, return 0; } +static void sdhci_cmdq_crypto_cfg_reset(struct mmc_host *mmc, unsigned int slot) +{ + +} static void sdhci_cmdq_post_cqe_halt(struct mmc_host *mmc) { } @@ -3593,6 +3608,7 @@ static const struct cmdq_host_ops sdhci_cmdq_ops = { .clear_set_dumpregs = sdhci_cmdq_clear_set_dumpregs, .enhanced_strobe_mask = sdhci_enhanced_strobe_mask, .crypto_cfg = sdhci_cmdq_crypto_cfg, + .crypto_cfg_reset = sdhci_cmdq_crypto_cfg_reset, .post_cqe_halt = sdhci_cmdq_post_cqe_halt, }; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 50a11128b32d..c4f553fe00ae 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -655,6 +655,7 @@ struct sdhci_ops { int (*crypto_engine_cfg)(struct sdhci_host *host, struct mmc_request *mrq, u32 slot); int (*crypto_engine_reset)(struct sdhci_host *host); + void (*crypto_cfg_reset)(struct sdhci_host *host, unsigned int slot); void (*set_uhs_signaling)(struct sdhci_host *host, unsigned int uhs); void (*hw_reset)(struct sdhci_host *host); void (*adma_workaround)(struct sdhci_host *host, u32 intmask); From d409a3b3d9572d40cd5b128df0e90d322d0c71ca Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Tue, 25 Aug 2015 11:30:48 +0530 Subject: [PATCH 364/472] mmc: core: Check if card supports strobe before calling host ops Should check if mmc_card supports strobe before calling enhanced strobe host ops. This also adds a macro for use by LLD to know if card supports strobe Change-Id: Id79098ff66bd36be2496b86bf71556204aca7fe3 Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 2 +- include/linux/mmc/card.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 1ce3a3d58f11..4eac93303776 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1254,7 +1254,7 @@ static int mmc_select_hs400(struct mmc_card *card) goto out_err; } - if (host->ops->enhanced_strobe) { + if (card->ext_csd.strobe_support && host->ops->enhanced_strobe) { mmc_host_clk_hold(host); err = host->ops->enhanced_strobe(host); mmc_host_clk_release(host); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b652076be866..1b891365653f 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -101,6 +101,7 @@ struct mmc_ext_csd { u8 raw_rpmb_size_mult; /* 168 */ u8 raw_erased_mem_count; /* 181 */ u8 strobe_support; /* 184 */ +#define MMC_STROBE_SUPPORT (1 << 0) u8 raw_ext_csd_structure; /* 194 */ u8 raw_card_type; /* 196 */ u8 raw_driver_strength; /* 197 */ @@ -586,6 +587,8 @@ static inline void __maybe_unused remove_quirk(struct mmc_card *card, int data) #define mmc_card_set_auto_bkops(c) ((c)->state |= MMC_STATE_AUTO_BKOPS) #define mmc_card_clr_auto_bkops(c) ((c)->state &= ~MMC_STATE_AUTO_BKOPS) +#define mmc_card_strobe(c) (((c)->ext_csd).strobe_support & MMC_STROBE_SUPPORT) + /* * Quirk add/remove for MMC products. */ From cd14781b293dcbc7534ad75e288942088bc1116e Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Tue, 25 Aug 2015 11:34:16 +0530 Subject: [PATCH 365/472] mmc: sdhci-msm: Add checks to know if card supports strobe This patch adds checks in msm host driver to check if card also supports enhanced strobe before changing strobe specific host configuration. Change-Id: Iab4833e80600c4ad89b16c76b52e917f885eea0e Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 0e8f99050d53..3e7ce1c1522b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -789,7 +789,7 @@ static int sdhci_msm_cm_dll_sdc4_calibration(struct sdhci_host *host) writel_relaxed(ddr_config, host->ioaddr + CORE_DDR_CONFIG); } - if (msm_host->enhanced_strobe) + if (msm_host->enhanced_strobe && mmc_card_strobe(msm_host->mmc->card)) writel_relaxed((readl_relaxed(host->ioaddr + CORE_DDR_200_CFG) | CORE_CMDIN_RCLK_EN), host->ioaddr + CORE_DDR_200_CFG); @@ -837,8 +837,8 @@ static int sdhci_msm_enhanced_strobe(struct sdhci_host *host) pr_debug("%s: Enter %s\n", mmc_hostname(host->mmc), __func__); - if (!msm_host->enhanced_strobe) { - pr_debug("%s: host does not support hs400 enhanced strobe\n", + if (!msm_host->enhanced_strobe || !mmc_card_strobe(mmc->card)) { + pr_debug("%s: host/card does not support hs400 enhanced strobe\n", mmc_hostname(mmc)); return -EINVAL; } @@ -2672,8 +2672,10 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock) * Select HS400 mode using the HC_SELECT_IN from VENDOR SPEC * register */ - if ((msm_host->tuning_done || msm_host->enhanced_strobe) && - !msm_host->calibration_done) { + if ((msm_host->tuning_done || + (mmc_card_strobe(msm_host->mmc->card) && + msm_host->enhanced_strobe)) && + !msm_host->calibration_done) { /* * Write 0x6 to HC_SELECT_IN and 1 to HC_SELECT_IN_EN * field in VENDOR_SPEC_FUNC @@ -2894,8 +2896,9 @@ static void sdhci_msm_enhanced_strobe_mask(struct sdhci_host *host, bool set) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; - if (!msm_host->enhanced_strobe) { - pr_debug("%s: host does not support hs400 enhanced strobe\n", + if (!msm_host->enhanced_strobe || + !mmc_card_strobe(msm_host->mmc->card)) { + pr_debug("%s: host/card does not support hs400 enhanced strobe\n", mmc_hostname(host->mmc)); return; } From 505869b6c22cf25fcb16da7bd33059745917d5c5 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 15 Sep 2015 12:33:13 -0700 Subject: [PATCH 366/472] mmc: sdhci: Clear error interrupt status in CMDQ mode Any CMD/DAT errors raised using the error interrupt status in CMDQ mode also needs to be cleared. If this is not cleared, any error in supported CMDQ CMD's like CMD 44/45/46/47/48 will cause an interrupt storm. Change-Id: Ie725bbf1c859a2dc91b64274e05e71328dfeb1b2 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 7be75740650d..35b763820a66 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3039,22 +3039,30 @@ static int sdhci_get_data_err(u32 intmask) return 0; } -static irqreturn_t sdhci_cmdq_irq(struct mmc_host *mmc, u32 intmask) +static irqreturn_t sdhci_cmdq_irq(struct sdhci_host *host, u32 intmask) { int err = 0; + u32 mask = 0; if (intmask & SDHCI_INT_CMD_MASK) err = sdhci_get_cmd_err(intmask); else if (intmask & SDHCI_INT_DATA_MASK) err = sdhci_get_data_err(intmask); - return cmdq_irq(mmc, err); + if (err) { + /* Clear the error interrupts */ + mask = intmask & SDHCI_INT_ERROR_MASK; + sdhci_writel(host, mask, SDHCI_INT_STATUS); + } + + return cmdq_irq(host->mmc, err); } #else -static irqreturn_t sdhci_cmdq_irq(struct mmc_host *mmc, u32 intmask) +static irqreturn_t sdhci_cmdq_irq(struct sdhci_host *host, u32 intmask) { - pr_err("%s: rxd cmdq-irq when disabled !!!!\n", mmc_hostname(mmc)); + pr_err("%s: Received cmdq-irq when disabled !!!!\n", + mmc_hostname(host->mmc)); return IRQ_NONE; } #endif @@ -3085,7 +3093,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) pr_debug("*** %s: cmdq intr: 0x%08x\n", mmc_hostname(host->mmc), intmask); - result = sdhci_cmdq_irq(host->mmc, intmask); + result = sdhci_cmdq_irq(host, intmask); if (result == IRQ_HANDLED) goto out; } From fd6903e1eb9466a2c753fe14465f7ec1bc9db622 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Mon, 22 Jun 2015 17:44:46 +0300 Subject: [PATCH 367/472] mmc: sdhci: remove support for pm_qos pm_qos is causing race conditions in CQ mode with power management. Removing the feature in order to allow power management. Change-Id: I340cd784829f389f18df6bff664337aca0f3c867 Signed-off-by: Dov Levenglick Signed-off-by: Konstantin Dorfman --- .../devicetree/bindings/mmc/sdhci-msm.txt | 12 --- drivers/mmc/host/sdhci-msm.c | 37 -------- drivers/mmc/host/sdhci-msm.h | 2 - drivers/mmc/host/sdhci.c | 95 ------------------- drivers/mmc/host/sdhci.h | 6 -- 5 files changed, 152 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index fe0a29bacc7c..25f9ad7b89d9 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -71,18 +71,6 @@ In the following, can be vdd (flash core voltage) or vdd-io (I/O voltag - pinctrl-names - pinctrl-0, pinctrl-1,.. pinctrl-n - - qcom,cpu-affinity: this is a string that specifies the pm QoS request - type. The supported cpu affinity modes are : - "all_cores" - PM_QOS_REQ_ALL_CORES is applicable to all CPU cores that - are online and this would have a power impact when there are more - number of CPUs. - "affine_irq" - PM_QOS_REQ_AFFINE_IRQ request type shall update/apply - the vote only to that CPU to which this IRQ's affinity is set to. - "affine_cores" - PM_QOS_REQ_AFFINE_CORES request type is used for - targets that have little cluster and will update/apply the vote to - all the cores in the little cluster. - The default CPU affinity mode is PM_QOS_REQ_AFFINE_IRQ to maintain - backward compatibility. - qcom,large-address-bus - specifies whether the soc is capable of supporting larger than 32 bit address bus width. diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3e7ce1c1522b..415131ff361a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -176,8 +176,6 @@ #define CORE_DDR_CONFIG_2 0x1BC #define DDR_CONFIG_2_POR_VAL 0x80040873 -#define MSM_MMC_DEFAULT_CPU_DMA_LATENCY 200 /* usecs */ - /* 512 descriptors */ #define SDHCI_MSM_MAX_SEGMENTS (1 << 9) #define SDHCI_MSM_MMC_CLK_GATE_DELAY 200 /* msecs */ @@ -1426,30 +1424,6 @@ out: return ret; } -#ifdef CONFIG_SMP -static void sdhci_msm_populate_affinity_type(struct sdhci_msm_pltfm_data *pdata, - struct device_node *np) -{ - const char *cpu_affinity = NULL; - - pdata->cpu_affinity_type = PM_QOS_REQ_AFFINE_IRQ; - if (!of_property_read_string(np, "qcom,cpu-affinity", - &cpu_affinity)) { - if (!strcmp(cpu_affinity, "all_cores")) - pdata->cpu_affinity_type = PM_QOS_REQ_ALL_CORES; - else if (!strcmp(cpu_affinity, "affine_cores")) - pdata->cpu_affinity_type = PM_QOS_REQ_AFFINE_CORES; - else if (!strcmp(cpu_affinity, "affine_irq")) - pdata->cpu_affinity_type = PM_QOS_REQ_AFFINE_IRQ; - } -} -#else -static void sdhci_msm_populate_affinity_type(struct sdhci_msm_pltfm_data *pdata, - struct device_node *np) -{ -} -#endif - /* Parse platform data */ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, @@ -1458,7 +1432,6 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, struct sdhci_msm_pltfm_data *pdata = NULL; struct device_node *np = dev->of_node; u32 bus_width = 0; - u32 cpu_dma_latency; int len, i; int clk_table_len; u32 *clk_table = NULL; @@ -1486,12 +1459,6 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, pdata->mmc_bus_width = 0; } - if (!of_property_read_u32(np, "qcom,cpu-dma-latency-us", - &cpu_dma_latency)) - pdata->cpu_dma_latency_us = cpu_dma_latency; - else - pdata->cpu_dma_latency_us = MSM_MMC_DEFAULT_CPU_DMA_LATENCY; - if (sdhci_msm_dt_get_array(dev, "qcom,devfreq,freq-table", &msm_host->mmc->clk_scaling.freq_table, &msm_host->mmc->clk_scaling.freq_table_sz, 0)) @@ -1595,8 +1562,6 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, pdata->largeaddressbus = of_property_read_bool(np, "qcom,large-address-bus"); - sdhci_msm_populate_affinity_type(pdata, np); - if (of_property_read_bool(np, "qcom,wakeup-on-idle")) msm_host->mmc->wakeup_on_idle = true; @@ -3493,8 +3458,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) if (msm_host->pdata->nonhotplug) msm_host->mmc->caps2 |= MMC_CAP2_NONHOTPLUG; - host->cpu_dma_latency_us = msm_host->pdata->cpu_dma_latency_us; - host->pm_qos_req_dma.type = msm_host->pdata->cpu_affinity_type; /* Initialize ICE if present */ if (msm_host->ice.pdev) { diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 8cd799532c64..4f724b181c51 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -98,12 +98,10 @@ struct sdhci_msm_pltfm_data { bool pin_cfg_sts; struct sdhci_msm_pin_data *pin_data; struct sdhci_pinctrl_data *pctrl_data; - u32 cpu_dma_latency_us; int status_gpio; /* card detection GPIO that is configured as IRQ */ struct sdhci_msm_bus_voting_data *voting_data; u32 *sup_clk_table; unsigned char sup_clk_cnt; - enum pm_qos_req_type cpu_affinity_type; u32 *sup_ice_clk_table; unsigned char sup_ice_clk_cnt; u32 ice_clk_max; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 35b763820a66..aed0584379d1 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -172,33 +172,6 @@ static void sdhci_dumpregs(struct sdhci_host *host) pr_info(DRIVER_NAME ": ===========================================\n"); } -#define MAX_PM_QOS_TIMEOUT_VALUE 100000 /* 100 ms */ -static ssize_t -show_sdhci_pm_qos_tout(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct sdhci_host *host = dev_get_drvdata(dev); - - return snprintf(buf, PAGE_SIZE, "%d us\n", host->pm_qos_timeout_us); -} - -static ssize_t -store_sdhci_pm_qos_tout(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct sdhci_host *host = dev_get_drvdata(dev); - uint32_t value; - unsigned long flags; - - if (!kstrtou32(buf, 0, &value)) { - spin_lock_irqsave(&host->lock, flags); - if (value <= MAX_PM_QOS_TIMEOUT_VALUE) - host->pm_qos_timeout_us = value; - spin_unlock_irqrestore(&host->lock, flags); - } - return count; -} - /*****************************************************************************\ * * * Low level functions * @@ -1527,12 +1500,6 @@ static int sdhci_enable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); - if (mmc->card && !mmc_card_cmdq(mmc->card) && - (host->cpu_dma_latency_us)) { - pm_qos_update_request(&host->pm_qos_req_dma, - host->cpu_dma_latency_us); - } - if (host->ops->platform_bus_voting) host->ops->platform_bus_voting(host, 1); @@ -1543,24 +1510,6 @@ static int sdhci_disable(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); - if (mmc->card && !mmc_card_cmdq(mmc->card) && - (host->cpu_dma_latency_us)) { - /* - * In performance mode, release QoS vote after a timeout to - * make sure back-to-back requests don't suffer from latencies - * that are involved to wake CPU from low power modes in cases - * where the CPU goes into low power mode as soon as QoS vote is - * released. - */ - if (host->power_policy == SDHCI_PERFORMANCE_MODE) - pm_qos_update_request_timeout(&host->pm_qos_req_dma, - host->cpu_dma_latency_us, - host->pm_qos_timeout_us); - else - pm_qos_update_request(&host->pm_qos_req_dma, - PM_QOS_DEFAULT_VALUE); - } - if (host->ops->platform_bus_voting) host->ops->platform_bus_voting(host, 0); @@ -3436,31 +3385,6 @@ struct sdhci_host *sdhci_alloc_host(struct device *dev, EXPORT_SYMBOL_GPL(sdhci_alloc_host); -#ifdef CONFIG_SMP -static void sdhci_set_pmqos_req_type(struct sdhci_host *host) -{ - /* - * The default request type PM_QOS_REQ_ALL_CORES is - * applicable to all CPU cores that are online and - * this would have a power impact when there are more - * number of CPUs. This new PM_QOS_REQ_AFFINE_IRQ request - * type shall update/apply the vote only to that CPU to - * which this IRQ's affinity is set to. - * PM_QOS_REQ_AFFINE_CORES request type is used for targets that have - * little cluster and will update/apply the vote to all the cores in - * the little cluster. - */ - if (host->pm_qos_req_dma.type == PM_QOS_REQ_AFFINE_CORES) - host->pm_qos_req_dma.cpus_affine.bits[0] = 0x0F; - else if (host->pm_qos_req_dma.type == PM_QOS_REQ_AFFINE_IRQ) - host->pm_qos_req_dma.irq = host->irq; -} -#else -static void sdhci_set_pmqos_req_type(struct sdhci_host *host) -{ -} -#endif - #ifdef CONFIG_MMC_CQ_HCI static void sdhci_cmdq_clear_set_irqs(struct mmc_host *mmc, bool clear) { @@ -4156,23 +4080,6 @@ int sdhci_add_host(struct sdhci_host *host) mmiowb(); - if (host->cpu_dma_latency_us) { - host->pm_qos_timeout_us = 10000; /* default value */ - sdhci_set_pmqos_req_type(host); - pm_qos_add_request(&host->pm_qos_req_dma, - PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); - - host->pm_qos_tout.show = show_sdhci_pm_qos_tout; - host->pm_qos_tout.store = store_sdhci_pm_qos_tout; - sysfs_attr_init(&host->pm_qos_tout.attr); - host->pm_qos_tout.attr.name = "pm_qos_unvote_delay"; - host->pm_qos_tout.attr.mode = S_IRUGO | S_IWUSR; - ret = device_create_file(mmc_dev(mmc), &host->pm_qos_tout); - if (ret) - pr_err("%s: cannot create pm_qos_unvote_delay %d\n", - mmc_hostname(mmc), ret); - } - if (host->quirks2 & SDHCI_QUIRK2_IGN_DATA_END_BIT_ERROR) { host->ier = (host->ier & ~SDHCI_INT_DATA_END_BIT); sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); @@ -4242,8 +4149,6 @@ void sdhci_remove_host(struct sdhci_host *host, int dead) sdhci_disable_card_detection(host); - if (host->cpu_dma_latency_us) - pm_qos_remove_request(&host->pm_qos_req_dma); mmc_remove_host(host->mmc); #ifdef SDHCI_USE_LEDS_CLASS diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index c4f553fe00ae..36e178f52d90 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -605,13 +604,8 @@ struct sdhci_host { unsigned int tuning_count; /* Timer count for re-tuning */ unsigned int tuning_mode; /* Re-tuning mode supported by host */ #define SDHCI_TUNING_MODE_1 0 - unsigned int cpu_dma_latency_us; - struct pm_qos_request pm_qos_req_dma; ktime_t data_start_time; - unsigned int pm_qos_timeout_us; /* timeout for PM QoS request */ - struct device_attribute pm_qos_tout; - enum sdhci_power_policy power_policy; bool is_crypto_en; From 8a67fa5b178a680ae401dcc62164a89d06d12b02 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Tue, 8 Sep 2015 15:39:11 +0300 Subject: [PATCH 368/472] mmc: sdhci-msm: add PM QoS properties for IRQ and cpu group voting Add the necessary device tree properties and parsing in the driver to support PM QoS voting for IRQ and CPU groups for CMDQ / legacy modes. Change-Id: I1a94978ca66823d2ce78ee230cf36b4ebb72e6d8 Signed-off-by: Gilad Broner [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- .../devicetree/bindings/mmc/sdhci-msm.txt | 30 ++++ drivers/mmc/host/sdhci-msm.c | 165 ++++++++++++++++++ drivers/mmc/host/sdhci-msm.h | 23 +++ drivers/mmc/host/sdhci.h | 1 + 4 files changed, 219 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 25f9ad7b89d9..6c8c13f335df 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -45,6 +45,26 @@ Optional Properties: the driver will construct one based on the card supported max and min frequencies. The frequencies must be ordered from lowest to highest. + - qcom,pm-qos-irq-type - the PM QoS request type to be used for IRQ voting. + Can be either "affine_cores" or "affine_irq". If not specified, will default + to "affine_cores". Use "affine_irq" setting in case an IRQ balancer is active, + and IRQ affinity changes during runtime. + - qcom,pm-qos-irq-cpu - specifies the CPU for which IRQ voting shall be done. + If "affine_cores" was specified for property 'qcom,pm-qos-irq-type' + then this property must be defined, and is not relevant otherwise. + - qcom,pm-qos-irq-latency - a tuple defining two latency values with which + PM QoS IRQ voting shall be done. The first value is the latecy to be used + when load is high (performance mode) and the second is for low loads + (power saving mode). + - qcom,pm-qos-cpu-groups - defines cpu groups mapping. + Each cell represnets a group, which is a cpu bitmask defining which cpus belong + to that group. + - qcom,pm-qos--latency-us - where is either "cmdq" or "legacy". + An array of latency value tuples, each tuple corresponding to a cpu group in the order + defined in property 'qcom,pm-qos-cpu-groups'. The first value is the latecy to be used + when load is high (performance mode) and the second is for low loads + (power saving mode). These values will be used for cpu group voting for + command-queueing mode or legacy respectively. In the following, can be vdd (flash core voltage) or vdd-io (I/O voltage). - qcom,-always-on - specifies whether supply should be kept "on" always. @@ -122,6 +142,13 @@ Example: <&msmgpio 36 0>, /* DATA2 */ <&msmgpio 35 0>; /* DATA3 */ qcom,gpio-names = "CLK", "CMD", "DAT0", "DAT1", "DAT2", "DAT3"; + + qcom,pm-qos-irq-type = "affine_cores"; + qcom,pm-qos-irq-cpu = <0>; + qcom,pm-qos-irq-latency = <500 100>; + qcom,pm-qos-cpu-groups = <0x03 0x0c>; + qcom,pm-qos-cmdq-latency-us = <50 100>, <50 100>; + qcom,pm-qos-legacy-latency-us = <50 100>, <50 100>; }; sdhc_2: qcom,sdhc@f98a4900 { @@ -145,4 +172,7 @@ Example: qcom,pad-pull-off = <0x0 0x3 0x3>; /* no-pull, pull-up, pull-up */ qcom,pad-drv-on = <0x7 0x4 0x4>; /* 16mA, 10mA, 10mA */ qcom,pad-drv-off = <0x0 0x0 0x0>; /* 2mA, 2mA, 2mA */ + + qcom,pm-qos-irq-type = "affine_irq"; + qcom,pm-qos-irq-latency = <120 200>; }; diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 415131ff361a..14726cae365a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1424,6 +1424,169 @@ out: return ret; } +static int sdhci_msm_pm_qos_parse_irq(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + struct device_node *np = dev->of_node; + const char *str; + u32 cpu; + int ret = 0; + int i; + + pdata->pm_qos_data.irq_valid = false; + pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_CORES; + if (!of_property_read_string(np, "qcom,pm-qos-irq-type", &str) && + !strcmp(str, "affine_irq")) { + pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_IRQ; + } + + /* must specify cpu for "affine_cores" type */ + if (pdata->pm_qos_data.irq_req_type == PM_QOS_REQ_AFFINE_CORES) { + pdata->pm_qos_data.irq_cpu = -1; + ret = of_property_read_u32(np, "qcom,pm-qos-irq-cpu", &cpu); + if (ret) { + dev_err(dev, "%s: error %d reading irq cpu\n", __func__, + ret); + goto out; + } + if (cpu < 0 || cpu >= num_possible_cpus()) { + dev_err(dev, "%s: invalid irq cpu %d (NR_CPUS=%d)\n", + __func__, cpu, num_possible_cpus()); + ret = -EINVAL; + goto out; + } + pdata->pm_qos_data.irq_cpu = cpu; + } + + if (of_property_count_u32_elems(np, "qcom,pm-qos-irq-latency") != + SDHCI_POWER_POLICY_NUM) { + dev_err(dev, "%s: could not read %d values for 'qcom,pm-qos-irq-latency'\n", + __func__, SDHCI_POWER_POLICY_NUM); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < SDHCI_POWER_POLICY_NUM; i++) + of_property_read_u32_index(np, "qcom,pm-qos-irq-latency", i, + &pdata->pm_qos_data.irq_latency.latency[i]); + + pdata->pm_qos_data.irq_valid = true; +out: + return ret; +} + +static int sdhci_msm_pm_qos_parse_cpu_groups(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + struct device_node *np = dev->of_node; + u32 mask; + int nr_groups; + int ret; + int i; + + /* Read cpu group mapping */ + nr_groups = of_property_count_u32_elems(np, "qcom,pm-qos-cpu-groups"); + if (nr_groups <= 0) { + ret = -EINVAL; + goto out; + } + pdata->pm_qos_data.cpu_group_map.nr_groups = nr_groups; + pdata->pm_qos_data.cpu_group_map.mask = + kcalloc(nr_groups, sizeof(cpumask_t), GFP_KERNEL); + if (!pdata->pm_qos_data.cpu_group_map.mask) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < nr_groups; i++) { + of_property_read_u32_index(np, "qcom,pm-qos-cpu-groups", + i, &mask); + + pdata->pm_qos_data.cpu_group_map.mask[i].bits[0] = mask; + if (!cpumask_subset(&pdata->pm_qos_data.cpu_group_map.mask[i], + cpu_possible_mask)) { + dev_err(dev, "%s: invalid mask 0x%x of cpu group #%d\n", + __func__, mask, i); + ret = -EINVAL; + goto free_res; + } + } + return 0; + +free_res: + kfree(pdata->pm_qos_data.cpu_group_map.mask); +out: + return ret; +} + +static int sdhci_msm_pm_qos_parse_latency(struct device *dev, const char *name, + int nr_groups, struct sdhci_msm_pm_qos_latency **latency) +{ + struct device_node *np = dev->of_node; + struct sdhci_msm_pm_qos_latency *values; + int ret; + int i; + int group; + int cfg; + + ret = of_property_count_u32_elems(np, name); + if (ret > 0 && ret != SDHCI_POWER_POLICY_NUM * nr_groups) { + dev_err(dev, "%s: invalid number of values for property %s: expected=%d actual=%d\n", + __func__, name, SDHCI_POWER_POLICY_NUM * nr_groups, + ret); + return -EINVAL; + } else if (ret < 0) { + return ret; + } + + values = kcalloc(nr_groups, sizeof(struct sdhci_msm_pm_qos_latency), + GFP_KERNEL); + if (!values) + return -ENOMEM; + + for (i = 0; i < SDHCI_POWER_POLICY_NUM * nr_groups; i++) { + group = i / SDHCI_POWER_POLICY_NUM; + cfg = i % SDHCI_POWER_POLICY_NUM; + of_property_read_u32_index(np, name, i, + &(values[group].latency[cfg])); + } + + *latency = values; + return 0; +} + +static void sdhci_msm_pm_qos_parse(struct device *dev, + struct sdhci_msm_pltfm_data *pdata) +{ + if (sdhci_msm_pm_qos_parse_irq(dev, pdata)) + dev_notice(dev, "%s: PM QoS voting for IRQ will be disabled\n", + __func__); + + if (!sdhci_msm_pm_qos_parse_cpu_groups(dev, pdata)) { + pdata->pm_qos_data.cmdq_valid = + !sdhci_msm_pm_qos_parse_latency(dev, + "qcom,pm-qos-cmdq-latency-us", + pdata->pm_qos_data.cpu_group_map.nr_groups, + &pdata->pm_qos_data.cmdq_latency); + pdata->pm_qos_data.legacy_valid = + !sdhci_msm_pm_qos_parse_latency(dev, + "qcom,pm-qos-legacy-latency-us", + pdata->pm_qos_data.cpu_group_map.nr_groups, + &pdata->pm_qos_data.latency); + if (!pdata->pm_qos_data.cmdq_valid && + !pdata->pm_qos_data.legacy_valid) { + /* clean-up previously allocated arrays */ + kfree(pdata->pm_qos_data.latency); + kfree(pdata->pm_qos_data.cmdq_latency); + dev_err(dev, "%s: invalid PM QoS latency values. Voting for cpu group will be disabled\n", + __func__); + } + } else { + dev_notice(dev, "%s: PM QoS voting for cpu group will be disabled\n", + __func__); + } +} + /* Parse platform data */ static struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, @@ -1565,6 +1728,8 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, if (of_property_read_bool(np, "qcom,wakeup-on-idle")) msm_host->mmc->wakeup_on_idle = true; + sdhci_msm_pm_qos_parse(dev, pdata); + return pdata; out: return NULL; diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 4f724b181c51..1a7fd827b96e 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -16,6 +16,7 @@ #define __SDHCI_MSM_H__ #include +#include #include "sdhci-pltfm.h" /* This structure keeps information per regulator */ @@ -83,6 +84,27 @@ struct sdhci_msm_bus_voting_data { unsigned int bw_vecs_size; }; +struct sdhci_msm_cpu_group_map { + int nr_groups; + cpumask_t *mask; +}; + +struct sdhci_msm_pm_qos_latency { + s32 latency[SDHCI_POWER_POLICY_NUM]; +}; + +struct sdhci_msm_pm_qos_data { + struct sdhci_msm_cpu_group_map cpu_group_map; + enum pm_qos_req_type irq_req_type; + int irq_cpu; + struct sdhci_msm_pm_qos_latency irq_latency; + struct sdhci_msm_pm_qos_latency *cmdq_latency; + struct sdhci_msm_pm_qos_latency *latency; + bool irq_valid; + bool cmdq_valid; + bool legacy_valid; +}; + struct sdhci_msm_pltfm_data { /* Supported UHS-I Modes */ u32 caps; @@ -106,6 +128,7 @@ struct sdhci_msm_pltfm_data { unsigned char sup_ice_clk_cnt; u32 ice_clk_max; u32 ice_clk_min; + struct sdhci_msm_pm_qos_data pm_qos_data; }; struct sdhci_msm_bus_vote { diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 36e178f52d90..5468abe9698b 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -328,6 +328,7 @@ enum sdhci_cookie { enum sdhci_power_policy { SDHCI_PERFORMANCE_MODE, SDHCI_POWER_SAVE_MODE, + SDHCI_POWER_POLICY_NUM /* Always keep this one last */ }; struct sdhci_host { From 96deb87c8ca7975fb0ed73e9dbfbe013fd36e2fc Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 24 Sep 2015 14:17:08 +0300 Subject: [PATCH 369/472] mmc: core: claim host before halt in pm runtime idle There can be a race condition between runtime idle and incoming requests. In such a race condition, we can send requests while the queue is halted. Claiming the host at the beginning of runtime idle will prevent such a case. Change-Id: I8e55f0798543b2de44b37da13f7770889e6fbe5f Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 5 ++--- drivers/mmc/core/mmc.c | 8 ++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index a1b92cdbe83d..8e24ddfb51db 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1259,7 +1259,8 @@ EXPORT_SYMBOL(mmc_check_bkops); * mmc_start_manual_bkops - start BKOPS for supported cards * @card: MMC card to start BKOPS * - * Send START_BKOPS to the card. + * Send START_BKOPS to the card. + * The function should be called with claimed host. */ void mmc_start_manual_bkops(struct mmc_card *card) { @@ -1273,7 +1274,6 @@ void mmc_start_manual_bkops(struct mmc_card *card) if (mmc_card_doing_bkops(card)) return; - mmc_claim_host(card->host); mmc_retune_hold(card->host); err = __mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BKOPS_START, @@ -1288,7 +1288,6 @@ void mmc_start_manual_bkops(struct mmc_card *card) } mmc_retune_release(card->host); - mmc_release_host(card->host); } EXPORT_SYMBOL(mmc_start_manual_bkops); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 4eac93303776..3a23cf434159 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2587,6 +2587,9 @@ static int mmc_runtime_idle(struct mmc_host *host) bool halt_cmdq; BUG_ON(!host->card); + + mmc_claim_host(host); + halt_cmdq = mmc_card_cmdq(host->card) && (host->card->bkops.needs_check || host->card->bkops.needs_manual); @@ -2611,10 +2614,9 @@ static int mmc_runtime_idle(struct mmc_host *host) host->card->bkops.needs_check = false; if (host->card->bkops.needs_check) { - mmc_claim_host(host); mmc_check_bkops(host->card); host->card->bkops.needs_check = false; - mmc_release_host(host); + } if (host->card->bkops.needs_manual) @@ -2626,6 +2628,7 @@ static int mmc_runtime_idle(struct mmc_host *host) pr_err("%s: %s failed to unhalt cmdq (%d)\n", mmc_hostname(host), __func__, err); } + out: /* * TODO: consider prolonging suspend when bkops @@ -2634,6 +2637,7 @@ out: * */ pm_schedule_suspend(&host->card->dev, MMC_AUTOSUSPEND_DELAY_MS); no_suspend: + mmc_release_host(host); pm_runtime_mark_last_busy(&host->card->dev); /* return negative value in order to avoid autosuspend */ return (err) ? err : NO_AUTOSUSPEND; From 4ca359f32882a8221499cd43b6e0c705de263d1d Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 1 Sep 2015 16:46:09 +0530 Subject: [PATCH 370/472] mmc: sdhci-msm-ice: implement crypto_cfg_reset host operation When encryption/decryption is enabled in CQ mode, the legacy commands that are sent in HALT state will use different slot other than slot 0 for crypto configuration information. The slot that is selected depends on the last slot that was used when it is in CQ mode. This is causing the data of legacy commands to be encrypted/decrypted based on the wrong slot usage for crypto config details. Hence, clear the crypto configuration of the slot used in CQ mode whenever it gets completed. Change-Id: I6817de46d895b61f410dd732be57ba60efb58fb2 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm-ice.c | 6 ++++++ drivers/mmc/host/sdhci-msm-ice.h | 6 ++++++ drivers/mmc/host/sdhci-msm.c | 1 + 3 files changed, 13 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm-ice.c b/drivers/mmc/host/sdhci-msm-ice.c index 2ba882ccbb93..a4840dbc2e80 100644 --- a/drivers/mmc/host/sdhci-msm-ice.c +++ b/drivers/mmc/host/sdhci-msm-ice.c @@ -146,6 +146,12 @@ int sdhci_msm_ice_init(struct sdhci_host *host) return 0; } +void sdhci_msm_ice_cfg_reset(struct sdhci_host *host, u32 slot) +{ + writel_relaxed(SDHCI_MSM_ICE_ENABLE_BYPASS, + host->ioaddr + CORE_VENDOR_SPEC_ICE_CTRL_INFO_3_n + 16 * slot); +} + int sdhci_msm_ice_cfg(struct sdhci_host *host, struct mmc_request *mrq, u32 slot) { diff --git a/drivers/mmc/host/sdhci-msm-ice.h b/drivers/mmc/host/sdhci-msm-ice.h index cceb2b4195b3..a3007a623b78 100644 --- a/drivers/mmc/host/sdhci-msm-ice.h +++ b/drivers/mmc/host/sdhci-msm-ice.h @@ -80,6 +80,7 @@ enum { #ifdef CONFIG_MMC_SDHCI_MSM_ICE int sdhci_msm_ice_get_dev(struct sdhci_host *host); int sdhci_msm_ice_init(struct sdhci_host *host); +void sdhci_msm_ice_cfg_reset(struct sdhci_host *host, u32 slot); int sdhci_msm_ice_cfg(struct sdhci_host *host, struct mmc_request *mrq, u32 slot); int sdhci_msm_ice_reset(struct sdhci_host *host); @@ -103,6 +104,11 @@ inline int sdhci_msm_ice_init(struct sdhci_host *host) { return 0; } + +inline void sdhci_msm_ice_cfg_reset(struct sdhci_host *host, u32 slot) +{ +} + inline int sdhci_msm_ice_cfg(struct sdhci_host *host, struct mmc_request *mrq, u32 slot) { diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 14726cae365a..89b0088c6474 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3143,6 +3143,7 @@ void sdhci_msm_reset_workaround(struct sdhci_host *host, u32 enable) static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, + .crypto_cfg_reset = sdhci_msm_ice_cfg_reset, .crypto_engine_reset = sdhci_msm_ice_reset, .set_uhs_signaling = sdhci_msm_set_uhs_signaling, .check_power_status = sdhci_msm_check_power_status, From 64be1cd3e02145b3ab5918b4526081840cbff477 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Tue, 29 Sep 2015 16:05:39 +0300 Subject: [PATCH 371/472] mmc: sdhci-msm: add PM QoS voting Add PM QoS voting mechanism to sdhci-msm driver for command queueing. Two types of voting schemes are supported: 1) Vote for HW IRQ 2) Vote for a cpu group according to the request's designated cpu Using PM QoS voting should benefit performance. Change-Id: I8a20653eeb6348d5b442c846708d92c8fb64a8e9 Signed-off-by: Gilad Broner [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/queue.c | 3 + drivers/mmc/host/cmdq_hci.c | 47 ++++++++ drivers/mmc/host/sdhci-msm.c | 208 +++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-msm.h | 35 ++++++ include/linux/mmc/host.h | 1 + 5 files changed, 294 insertions(+) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index d226eae5f9f1..d647a1a19575 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -346,6 +346,9 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, blk_cleanup_queue(mq->queue); } else { sema_init(&mq->thread_sem, 1); + /* hook for pm qos cmdq init */ + if (card->host->cmdq_ops->init) + card->host->cmdq_ops->init(card->host); mq->queue->queuedata = mq; card->host->cmdq_ctx.q = mq->queue; mq->thread = kthread_run(mmc_cmdq_thread, mq, diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 5aa704048d9d..3f4d2367a745 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -24,8 +24,12 @@ #include #include #include +#include +#include #include "cmdq_hci.h" +#include "sdhci.h" +#include "sdhci-msm.h" #define DCMD_SLOT 31 #define NUM_SLOTS 32 @@ -575,6 +579,21 @@ static void cmdq_prep_dcmd_desc(struct mmc_host *mmc, } +static void cmdq_pm_qos_vote(struct sdhci_host *host, struct mmc_request *mrq) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + sdhci_msm_pm_qos_cpu_vote(host, + msm_host->pdata->pm_qos_data.cmdq_latency, mrq->req->cpu); +} + +static void cmdq_pm_qos_unvote(struct sdhci_host *host, struct mmc_request *mrq) +{ + /* use async as we're inside an atomic context (soft-irq) */ + sdhci_msm_pm_qos_cpu_unvote(host, mrq->req->cpu, true); +} + static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) { int err = 0; @@ -582,6 +601,7 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) u64 *task_desc = NULL; u32 tag = mrq->cmdq_req->tag; struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + struct sdhci_host *host = mmc_priv(mmc); if (!cq_host->enabled) { pr_err("%s: CMDQ host not enabled yet !!!\n", @@ -628,6 +648,9 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) if (cq_host->ops->set_tranfer_params) cq_host->ops->set_tranfer_params(mmc); + /* PM QoS */ + sdhci_msm_pm_qos_irq_vote(host); + cmdq_pm_qos_vote(host, mrq); ring_doorbell: /* Ensure the task descriptor list is flushed before ringing doorbell */ wmb(); @@ -781,6 +804,7 @@ static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) { struct mmc_data *data = mrq->data; + struct sdhci_host *sdhci_host = mmc_priv(host); if (data) { data->error = err; @@ -791,6 +815,10 @@ static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, data->bytes_xfered = 0; else data->bytes_xfered = blk_rq_bytes(mrq->req); + + /* we're in atomic context (soft-irq) so unvote async. */ + sdhci_msm_pm_qos_irq_unvote(sdhci_host, true); + cmdq_pm_qos_unvote(sdhci_host, mrq); } } @@ -802,7 +830,26 @@ static void cmdq_dumpstate(struct mmc_host *mmc) cmdq_runtime_pm_put(cq_host); } +static int cmdq_late_init(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + /* + * TODO: This should basically move to something like "sdhci-cmdq-msm" + * for msm specific implementation. + */ + sdhci_msm_pm_qos_irq_init(host); + + if (msm_host->pdata->pm_qos_data.cmdq_valid) + sdhci_msm_pm_qos_cpu_init(host, + msm_host->pdata->pm_qos_data.cmdq_latency); + return 0; +} + static const struct mmc_cmdq_host_ops cmdq_host_ops = { + .init = cmdq_late_init, .enable = cmdq_enable, .disable = cmdq_disable, .request = cmdq_request, diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 89b0088c6474..382df8e0df39 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3141,6 +3141,214 @@ void sdhci_msm_reset_workaround(struct sdhci_host *host, u32 enable) } } +static void sdhci_msm_pm_qos_irq_unvote_work(struct work_struct *work) +{ + struct sdhci_msm_pm_qos_irq *pm_qos_irq = + container_of(work, struct sdhci_msm_pm_qos_irq, unvote_work); + + if (atomic_read(&pm_qos_irq->counter)) + return; + + pm_qos_irq->latency = PM_QOS_DEFAULT_VALUE; + pm_qos_update_request(&pm_qos_irq->req, pm_qos_irq->latency); +} + +void sdhci_msm_pm_qos_irq_vote(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct sdhci_msm_pm_qos_latency *latency = + &msm_host->pdata->pm_qos_data.irq_latency; + int counter; + + if (!msm_host->pm_qos_irq.enabled) + return; + + counter = atomic_inc_return(&msm_host->pm_qos_irq.counter); + /* Make sure to update the voting in case power policy has changed */ + if (msm_host->pm_qos_irq.latency == latency->latency[host->power_policy] + && counter > 1) + return; + + cancel_work_sync(&msm_host->pm_qos_irq.unvote_work); + msm_host->pm_qos_irq.latency = latency->latency[host->power_policy]; + pm_qos_update_request(&msm_host->pm_qos_irq.req, + msm_host->pm_qos_irq.latency); +} + +void sdhci_msm_pm_qos_irq_unvote(struct sdhci_host *host, bool async) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int counter; + + if (!msm_host->pm_qos_irq.enabled) + return; + + counter = atomic_dec_return(&msm_host->pm_qos_irq.counter); + if (counter < 0) { + pr_err("%s: counter=%d\n", __func__, counter); + BUG(); + } + if (counter) + return; + + if (async) { + schedule_work(&msm_host->pm_qos_irq.unvote_work); + return; + } + + msm_host->pm_qos_irq.latency = PM_QOS_DEFAULT_VALUE; + pm_qos_update_request(&msm_host->pm_qos_irq.req, + msm_host->pm_qos_irq.latency); +} + +void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct sdhci_msm_pm_qos_latency *irq_latency; + + if (!msm_host->pdata->pm_qos_data.irq_valid) + return; + + /* Initialize only once as this gets called per partition */ + if (msm_host->pm_qos_irq.enabled) + return; + + atomic_set(&msm_host->pm_qos_irq.counter, 0); + msm_host->pm_qos_irq.req.type = + msm_host->pdata->pm_qos_data.irq_req_type; + if (msm_host->pm_qos_irq.req.type == PM_QOS_REQ_AFFINE_IRQ) + msm_host->pm_qos_irq.req.irq = host->irq; + else + cpumask_copy(&msm_host->pm_qos_irq.req.cpus_affine, + cpumask_of(msm_host->pdata->pm_qos_data.irq_cpu)); + + INIT_WORK(&msm_host->pm_qos_irq.unvote_work, + sdhci_msm_pm_qos_irq_unvote_work); + /* For initialization phase, set the performance latency */ + irq_latency = &msm_host->pdata->pm_qos_data.irq_latency; + msm_host->pm_qos_irq.latency = + irq_latency->latency[SDHCI_PERFORMANCE_MODE]; + pm_qos_add_request(&msm_host->pm_qos_irq.req, PM_QOS_CPU_DMA_LATENCY, + msm_host->pm_qos_irq.latency); + msm_host->pm_qos_irq.enabled = true; +} + +static int sdhci_msm_get_cpu_group(struct sdhci_msm_host *msm_host, int cpu) +{ + int i; + struct sdhci_msm_cpu_group_map *map = + &msm_host->pdata->pm_qos_data.cpu_group_map; + + if (cpu < 0) + goto not_found; + + for (i = 0; i < map->nr_groups; i++) + if (cpumask_test_cpu(cpu, &map->mask[i])) + return i; + +not_found: + return -EINVAL; +} + +void sdhci_msm_pm_qos_cpu_vote(struct sdhci_host *host, + struct sdhci_msm_pm_qos_latency *latency, int cpu) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int group = sdhci_msm_get_cpu_group(msm_host, cpu); + struct sdhci_msm_pm_qos_group *pm_qos_group; + int counter; + + if (!msm_host->pm_qos_group_enable || group < 0) + return; + + pm_qos_group = &msm_host->pm_qos[group]; + counter = atomic_inc_return(&pm_qos_group->counter); + + /* Make sure to update the voting in case power policy has changed */ + if (pm_qos_group->latency == latency->latency[host->power_policy] + && counter > 1) + return; + + cancel_work_sync(&pm_qos_group->unvote_work); + + pm_qos_group->latency = latency->latency[host->power_policy]; + pm_qos_update_request(&pm_qos_group->req, pm_qos_group->latency); +} + +static void sdhci_msm_pm_qos_cpu_unvote_work(struct work_struct *work) +{ + struct sdhci_msm_pm_qos_group *group = + container_of(work, struct sdhci_msm_pm_qos_group, unvote_work); + + if (atomic_read(&group->counter)) + return; + + group->latency = PM_QOS_DEFAULT_VALUE; + pm_qos_update_request(&group->req, group->latency); +} + +void sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int group = sdhci_msm_get_cpu_group(msm_host, cpu); + + if (!msm_host->pm_qos_group_enable || group < 0 || + atomic_dec_return(&msm_host->pm_qos[group].counter)) + return; + + if (async) { + schedule_work(&msm_host->pm_qos[group].unvote_work); + return; + } + + msm_host->pm_qos[group].latency = PM_QOS_DEFAULT_VALUE; + pm_qos_update_request(&msm_host->pm_qos[group].req, + msm_host->pm_qos[group].latency); +} + +void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, + struct sdhci_msm_pm_qos_latency *latency) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups; + struct sdhci_msm_pm_qos_group *group; + int i; + + if (msm_host->pm_qos_group_enable) + return; + + msm_host->pm_qos = kcalloc(nr_groups, sizeof(*msm_host->pm_qos), + GFP_KERNEL); + if (!msm_host->pm_qos) + return; + + for (i = 0; i < nr_groups; i++) { + group = &msm_host->pm_qos[i]; + INIT_WORK(&group->unvote_work, + sdhci_msm_pm_qos_cpu_unvote_work); + atomic_set(&group->counter, 0); + group->req.type = PM_QOS_REQ_AFFINE_CORES; + cpumask_copy(&group->req.cpus_affine, + &msm_host->pdata->pm_qos_data.cpu_group_map.mask[i]); + /* For initialization phase, set the performance mode latency */ + group->latency = latency[i].latency[SDHCI_PERFORMANCE_MODE]; + pm_qos_add_request(&group->req, PM_QOS_CPU_DMA_LATENCY, + group->latency); + pr_info("%s (): voted for group #%d (mask=0x%lx) latency=%d (0x%p)\n", + __func__, i, + group->req.cpus_affine.bits[0], + group->latency, + &latency[i].latency[SDHCI_PERFORMANCE_MODE]); + } + msm_host->pm_qos_group_enable = true; +} + static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, .crypto_cfg_reset = sdhci_msm_ice_cfg_reset, diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 1a7fd827b96e..50db9363ef4e 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -105,6 +105,26 @@ struct sdhci_msm_pm_qos_data { bool legacy_valid; }; +/* + * PM QoS for group voting management - each cpu group defined is associated + * with 1 instance of this structure. + */ +struct sdhci_msm_pm_qos_group { + struct pm_qos_request req; + struct work_struct unvote_work; + atomic_t counter; + s32 latency; +}; + +/* PM QoS HW IRQ voting */ +struct sdhci_msm_pm_qos_irq { + struct pm_qos_request req; + struct work_struct unvote_work; + atomic_t counter; + s32 latency; + bool enabled; +}; + struct sdhci_msm_pltfm_data { /* Supported UHS-I Modes */ u32 caps; @@ -182,8 +202,23 @@ struct sdhci_msm_host { u32 caps_0; struct sdhci_msm_ice_data ice; u32 ice_clk_rate; + struct sdhci_msm_pm_qos_group *pm_qos; + int pm_qos_prev_group; + bool pm_qos_group_enable; + struct sdhci_msm_pm_qos_irq pm_qos_irq; }; extern char *saved_command_line; +void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host); +void sdhci_msm_pm_qos_irq_vote(struct sdhci_host *host); +void sdhci_msm_pm_qos_irq_unvote(struct sdhci_host *host, bool async); + +void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, + struct sdhci_msm_pm_qos_latency *latency); +void sdhci_msm_pm_qos_cpu_vote(struct sdhci_host *host, + struct sdhci_msm_pm_qos_latency *latency, int cpu); +void sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async); + + #endif /* __SDHCI_MSM_H__ */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 001cde15242b..0759649084dd 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -91,6 +91,7 @@ enum mmc_load { }; struct mmc_cmdq_host_ops { + int (*init)(struct mmc_host *host); int (*enable)(struct mmc_host *host); void (*disable)(struct mmc_host *host, bool soft); int (*request)(struct mmc_host *host, struct mmc_request *mrq); From 17a072dd256d9a3246abf28e78e62a65c63f84f1 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Tue, 29 Sep 2015 16:57:21 +0300 Subject: [PATCH 372/472] mmc: sdhci-msm: add PM QoS legacy voting Add PM QoS voting mechanism to sdhci-msm driver for legacy eMMC. Two types of voting schemes are supported: 1) Vote for HW IRQ 2) Vote for a cpu group according to the request's designated cpu Using PM QoS voting should benefit performance. Change-Id: I5d2b71fc4eabfa5060f343634fbc7363f2ee1344 Signed-off-by: Konstantin Dorfman Signed-off-by: Gilad Broner [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/queue.c | 4 +++ drivers/mmc/host/sdhci-msm.c | 66 ++++++++++++++++++++++++++++++++++-- drivers/mmc/host/sdhci-msm.h | 4 +-- drivers/mmc/host/sdhci.c | 15 ++++++++ drivers/mmc/host/sdhci.h | 3 ++ include/linux/mmc/host.h | 1 + 6 files changed, 88 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index d647a1a19575..efe2059d4fad 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -479,6 +479,10 @@ cur_sg_alloc_failed: success: sema_init(&mq->thread_sem, 1); + /* hook for pm qos legacy init */ + if (card->host->ops->init) + card->host->ops->init(card->host); + mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s", host->index, subname ? subname : ""); diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 382df8e0df39..8801b3afa6ac 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3291,7 +3291,7 @@ static void sdhci_msm_pm_qos_cpu_unvote_work(struct work_struct *work) pm_qos_update_request(&group->req, group->latency); } -void sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async) +bool sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; @@ -3299,16 +3299,17 @@ void sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async) if (!msm_host->pm_qos_group_enable || group < 0 || atomic_dec_return(&msm_host->pm_qos[group].counter)) - return; + return false; if (async) { schedule_work(&msm_host->pm_qos[group].unvote_work); - return; + return true; } msm_host->pm_qos[group].latency = PM_QOS_DEFAULT_VALUE; pm_qos_update_request(&msm_host->pm_qos[group].req, msm_host->pm_qos[group].latency); + return true; } void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, @@ -3346,9 +3347,65 @@ void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, group->latency, &latency[i].latency[SDHCI_PERFORMANCE_MODE]); } + msm_host->pm_qos_prev_cpu = -1; msm_host->pm_qos_group_enable = true; } +static void sdhci_msm_pre_req(struct sdhci_host *host, + struct mmc_request *mmc_req) +{ + int cpu; + int group; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int prev_group = sdhci_msm_get_cpu_group(msm_host, + msm_host->pm_qos_prev_cpu); + + sdhci_msm_pm_qos_irq_vote(host); + + cpu = get_cpu(); + put_cpu(); + group = sdhci_msm_get_cpu_group(msm_host, cpu); + if (group < 0) + return; + + if (group != prev_group && prev_group >= 0) { + sdhci_msm_pm_qos_cpu_unvote(host, + msm_host->pm_qos_prev_cpu, false); + prev_group = -1; /* make sure to vote for new group */ + } + + if (prev_group < 0) { + sdhci_msm_pm_qos_cpu_vote(host, + msm_host->pdata->pm_qos_data.latency, cpu); + msm_host->pm_qos_prev_cpu = cpu; + } +} + +static void sdhci_msm_post_req(struct sdhci_host *host, + struct mmc_request *mmc_req) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + sdhci_msm_pm_qos_irq_unvote(host, false); + + if (sdhci_msm_pm_qos_cpu_unvote(host, msm_host->pm_qos_prev_cpu, false)) + msm_host->pm_qos_prev_cpu = -1; +} + +static void sdhci_msm_init(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + sdhci_msm_pm_qos_irq_init(host); + + if (msm_host->pdata->pm_qos_data.legacy_valid) + sdhci_msm_pm_qos_cpu_init(host, + msm_host->pdata->pm_qos_data.latency); +} + static struct sdhci_ops sdhci_msm_ops = { .crypto_engine_cfg = sdhci_msm_ice_cfg, .crypto_cfg_reset = sdhci_msm_ice_cfg_reset, @@ -3372,6 +3429,9 @@ static struct sdhci_ops sdhci_msm_ops = { .detect = sdhci_msm_detect, .notify_load = sdhci_msm_notify_load, .reset_workaround = sdhci_msm_reset_workaround, + .init = sdhci_msm_init, + .pre_req = sdhci_msm_pre_req, + .post_req = sdhci_msm_post_req, }; static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 50db9363ef4e..01ad6d1593a2 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -203,7 +203,7 @@ struct sdhci_msm_host { struct sdhci_msm_ice_data ice; u32 ice_clk_rate; struct sdhci_msm_pm_qos_group *pm_qos; - int pm_qos_prev_group; + int pm_qos_prev_cpu; bool pm_qos_group_enable; struct sdhci_msm_pm_qos_irq pm_qos_irq; }; @@ -218,7 +218,7 @@ void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, struct sdhci_msm_pm_qos_latency *latency); void sdhci_msm_pm_qos_cpu_vote(struct sdhci_host *host, struct sdhci_msm_pm_qos_latency *latency, int cpu); -void sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async); +bool sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async); #endif /* __SDHCI_MSM_H__ */ diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index aed0584379d1..5f139a5b9c87 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2512,6 +2512,8 @@ static void sdhci_post_req(struct mmc_host *mmc, struct mmc_request *mrq, DMA_TO_DEVICE : DMA_FROM_DEVICE); data->host_cookie = COOKIE_UNMAPPED; } + if (host->ops->post_req) + host->ops->post_req(host, mrq); } static int sdhci_pre_dma_transfer(struct sdhci_host *host, @@ -2548,6 +2550,9 @@ static void sdhci_pre_req(struct mmc_host *mmc, struct mmc_request *mrq, if (host->flags & SDHCI_REQ_USE_DMA) sdhci_pre_dma_transfer(host, mrq->data); + + if (host->ops->pre_req) + host->ops->pre_req(host, mrq); } static void sdhci_card_event(struct mmc_host *mmc) @@ -2589,7 +2594,17 @@ static void sdhci_detect(struct mmc_host *mmc, bool detected) host->ops->detect(host, detected); } +static int sdhci_late_init(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + + if (host->ops->init) + host->ops->init(host); + + return 0; +} static const struct mmc_host_ops sdhci_ops = { + .init = sdhci_late_init, .request = sdhci_request, .post_req = sdhci_post_req, .pre_req = sdhci_pre_req, diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 5468abe9698b..a4017e6f6c8c 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -680,6 +680,9 @@ struct sdhci_ops { int card_drv, int *drv_type); int (*notify_load)(struct sdhci_host *host, enum mmc_load state); void (*reset_workaround)(struct sdhci_host *host, u32 enable); + void (*init)(struct sdhci_host *host); + void (*pre_req)(struct sdhci_host *host, struct mmc_request *req); + void (*post_req)(struct sdhci_host *host, struct mmc_request *req); }; #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 0759649084dd..367e122d1f2f 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -103,6 +103,7 @@ struct mmc_cmdq_host_ops { }; struct mmc_host_ops { + int (*init)(struct mmc_host *host); /* * 'enable' is called when the host is claimed and 'disable' is called * when the host is released. 'enable' and 'disable' are deprecated. From 21a32a7af8ca151dff4f3710742864705a5a7e7d Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Tue, 29 Sep 2015 17:34:03 +0300 Subject: [PATCH 373/472] mmc: core: add partial initialization support This change adds the ability to partially initialize the MMC card by using card Sleep/Awake sequence (CMD5). Card will be sent to Sleep state during runtime/system suspend and will be woken up during runtime/system resume. By using this sequence the card doesn't need full initialization which gives time reduction in system/runtime resume path. Change-Id: Id8dadf03ef4226f7c4675fadbacac7bb15c0289e Signed-off-by: Talel Shenhar Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed merge conflicts & compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 175 ++++++++++++++++++++++++++++++++++++--- include/linux/mmc/card.h | 4 + include/linux/mmc/host.h | 3 + 3 files changed, 169 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 3a23cf434159..356a6e036411 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1542,7 +1542,7 @@ static int mmc_select_cmdq(struct mmc_card *card) } mmc_host_clk_release(card->host); - pr_info("%s: CMDQ enabled on card\n", mmc_hostname(host)); + pr_info_once("%s: CMDQ enabled on card\n", mmc_hostname(host)); out: return ret; } @@ -2181,12 +2181,13 @@ err: return err; } -static int mmc_can_sleep(struct mmc_card *card) +static int mmc_can_sleepawake(struct mmc_host *host) { - return (card && card->ext_csd.rev >= 3); + return host && (host->caps2 & MMC_CAP2_SLEEP_AWAKE) && host->card && + (host->card->ext_csd.rev >= 3); } -static int mmc_sleep(struct mmc_host *host) +static int mmc_sleepawake(struct mmc_host *host, bool sleep) { struct mmc_command cmd = {0}; struct mmc_card *card = host->card; @@ -2196,13 +2197,16 @@ static int mmc_sleep(struct mmc_host *host) /* Re-tuning can't be done once the card is deselected */ mmc_retune_hold(host); - err = mmc_deselect_cards(host); - if (err) - goto out_release; + if (sleep) { + err = mmc_deselect_cards(host); + if (err) + goto out_release; + } cmd.opcode = MMC_SLEEP_AWAKE; cmd.arg = card->rca << 16; - cmd.arg |= 1 << 15; + if (sleep) + cmd.arg |= 1 << 15; /* * If the max_busy_timeout of the host is specified, validate it against @@ -2230,6 +2234,9 @@ static int mmc_sleep(struct mmc_host *host) if (!cmd.busy_timeout || !(host->caps & MMC_CAP_WAIT_WHILE_BUSY)) mmc_delay(timeout_ms); + if (!sleep) + err = mmc_select_card(card); + out_release: mmc_retune_release(host); return err; @@ -2340,6 +2347,69 @@ static void mmc_detect(struct mmc_host *host) } } +static int mmc_cache_card_ext_csd(struct mmc_host *host) +{ + int err; + u8 *ext_csd; + struct mmc_card *card = host->card; + + err = mmc_get_ext_csd(card, &ext_csd); + if (err || !ext_csd) { + pr_err("%s: %s: mmc_get_ext_csd failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + /* only cache read/write fields that the sw changes */ + card->ext_csd.raw_ext_csd_cmdq = ext_csd[EXT_CSD_CMDQ]; + card->ext_csd.raw_ext_csd_cache_ctrl = ext_csd[EXT_CSD_CACHE_CTRL]; + card->ext_csd.raw_ext_csd_bus_width = ext_csd[EXT_CSD_BUS_WIDTH]; + card->ext_csd.raw_ext_csd_hs_timing = ext_csd[EXT_CSD_HS_TIMING]; + + kfree(ext_csd); + + return 0; +} + +static int mmc_test_awake_ext_csd(struct mmc_host *host) +{ + int err; + u8 *ext_csd; + struct mmc_card *card = host->card; + + err = mmc_get_ext_csd(card, &ext_csd); + if (err) { + pr_err("%s: %s: mmc_get_ext_csd failed (%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + /* only compare read/write fields that the sw changes */ + pr_debug("%s: %s: type(cached:current) cmdq(%d:%d) cache_ctrl(%d:%d) bus_width (%d:%d) timing(%d:%d)\n", + mmc_hostname(host), __func__, + card->ext_csd.raw_ext_csd_cmdq, + ext_csd[EXT_CSD_CMDQ], + card->ext_csd.raw_ext_csd_cache_ctrl, + ext_csd[EXT_CSD_CACHE_CTRL], + card->ext_csd.raw_ext_csd_bus_width, + ext_csd[EXT_CSD_BUS_WIDTH], + card->ext_csd.raw_ext_csd_hs_timing, + ext_csd[EXT_CSD_HS_TIMING]); + + err = !((card->ext_csd.raw_ext_csd_cmdq == + ext_csd[EXT_CSD_CMDQ]) && + (card->ext_csd.raw_ext_csd_cache_ctrl == + ext_csd[EXT_CSD_CACHE_CTRL]) && + (card->ext_csd.raw_ext_csd_bus_width == + ext_csd[EXT_CSD_BUS_WIDTH]) && + (card->ext_csd.raw_ext_csd_hs_timing == + ext_csd[EXT_CSD_HS_TIMING])); + + kfree(ext_csd); + + return err; +} + static int _mmc_suspend(struct mmc_host *host, bool is_suspend) { int err = 0; @@ -2391,10 +2461,13 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (err) goto out; - if (mmc_can_sleep(host->card)) - err = mmc_sleep(host); - else if (!mmc_host_is_spi(host)) + if (mmc_can_sleepawake(host)) { + memcpy(&host->cached_ios, &host->ios, sizeof(host->cached_ios)); + mmc_cache_card_ext_csd(host); + err = mmc_sleepawake(host, true); + } else if (!mmc_host_is_spi(host)) { err = mmc_deselect_cards(host); + } if (!err) { mmc_power_off(host); @@ -2409,6 +2482,71 @@ out: return err; } +static int mmc_partial_init(struct mmc_host *host) +{ + int err = 0; + struct mmc_card *card = host->card; + + pr_debug("%s: %s: starting partial init\n", + mmc_hostname(host), __func__); + + mmc_set_bus_width(host, host->cached_ios.bus_width); + mmc_set_timing(host, host->cached_ios.timing); + mmc_set_clock(host, host->cached_ios.clock); + mmc_set_bus_mode(host, host->cached_ios.bus_mode); + + mmc_host_clk_hold(host); + + if (mmc_card_hs200(card) || mmc_card_hs400(card)) { + if (card->ext_csd.strobe_support && host->ops->enhanced_strobe) + err = host->ops->enhanced_strobe(host); + else + err = host->ops->execute_tuning(host, + MMC_SEND_TUNING_BLOCK_HS200); + if (err) + pr_warn("%s: %s: tuning execution failed (%d)\n", + mmc_hostname(host), __func__, err); + } + + /* + * The ext_csd is read to make sure the card did not went through + * Power-failure during sleep period. + * A subset of the W/E_P, W/C_P register will be tested. In case + * these registers values are different from the values that were + * cached during suspend, we will conclude that a Power-failure occurred + * and will do full initialization sequence. + * In addition, full init sequence also transfer ext_csd before moving + * to CMDQ mode which has a side affect of configuring SDHCI registers + * which needed to be done before moving to CMDQ mode. The same + * registers need to be configured for partial init. + */ + err = mmc_test_awake_ext_csd(host); + if (err) { + pr_debug("%s: %s: fail on ext_csd read (%d)\n", + mmc_hostname(host), __func__, err); + goto out; + } + pr_debug("%s: %s: reading and comparing ext_csd successful\n", + mmc_hostname(host), __func__); + + if (card->ext_csd.cmdq_support && (card->host->caps2 & + MMC_CAP2_CMD_QUEUE)) { + err = mmc_select_cmdq(card); + if (err) { + pr_warn("%s: %s: enabling CMDQ mode failed (%d)\n", + mmc_hostname(card->host), + __func__, err); + } + } +out: + mmc_host_clk_release(host); + + pr_debug("%s: %s: done partial init (%d)\n", + mmc_hostname(host), __func__, err); + + return err; +} + /* * Suspend callback */ @@ -2434,7 +2572,7 @@ static int mmc_suspend(struct mmc_host *host) */ static int _mmc_resume(struct mmc_host *host) { - int err = 0; + int err = -ENOSYS; int retries; BUG_ON(!host); @@ -2450,7 +2588,18 @@ static int _mmc_resume(struct mmc_host *host) mmc_power_up(host, host->card->ocr); retries = 3; while (retries) { - err = mmc_init_card(host, host->card->ocr, host->card); + if (mmc_can_sleepawake(host)) { + err = mmc_sleepawake(host, false); + if (!err) + err = mmc_partial_init(host); + if (err) + pr_err("%s: %s: awake failed (%d), fallback to full init\n", + mmc_hostname(host), __func__, err); + } + + if (err) + err = mmc_init_card(host, host->card->ocr, host->card); + if (err) { pr_err("%s: MMC card re-init failed rc = %d (retries = %d)\n", mmc_hostname(host), err, retries); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 1b891365653f..b8430181704a 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -93,6 +93,8 @@ struct mmc_ext_csd { unsigned int data_tag_unit_size; /* DATA TAG UNIT size */ unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; + u8 raw_ext_csd_cmdq; /* 15 */ + u8 raw_ext_csd_cache_ctrl; /* 33 */ bool ffu_capable; /* Firmware upgrade support */ #define MMC_FIRMWARE_LEN 8 u8 fwrev[MMC_FIRMWARE_LEN]; /* FW version */ @@ -100,8 +102,10 @@ struct mmc_ext_csd { u8 raw_partition_support; /* 160 */ u8 raw_rpmb_size_mult; /* 168 */ u8 raw_erased_mem_count; /* 181 */ + u8 raw_ext_csd_bus_width; /* 183 */ u8 strobe_support; /* 184 */ #define MMC_STROBE_SUPPORT (1 << 0) + u8 raw_ext_csd_hs_timing; /* 185 */ u8 raw_ext_csd_structure; /* 194 */ u8 raw_card_type; /* 196 */ u8 raw_driver_strength; /* 197 */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 367e122d1f2f..0f8d285a1192 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -441,6 +441,8 @@ struct mmc_host { #define MMC_CAP2_NONHOTPLUG (1 << 25) /*Don't support hotplug*/ #define MMC_CAP2_CMD_QUEUE (1 << 26) /* support eMMC command queue */ #define MMC_CAP2_SANITIZE (1 << 27) /* Support Sanitize */ +#define MMC_CAP2_SLEEP_AWAKE (1 << 28) /* Use Sleep/Awake (CMD5) */ + mmc_pm_flag_t pm_caps; /* supported pm features */ #ifdef CONFIG_MMC_CLKGATE @@ -468,6 +470,7 @@ struct mmc_host { spinlock_t lock; /* lock for claim and bus ops */ struct mmc_ios ios; /* current io bus settings */ + struct mmc_ios cached_ios; /* group bitfields together to minimize padding */ unsigned int use_spi_crc:1; From a93e005ce59486720ff0ee503fe2d93f96ddeda9 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Mon, 21 Sep 2015 14:43:38 +0300 Subject: [PATCH 374/472] mmc: card: stop BKOPS in mmc_blk_cmdq_issue_rq In case there are no pending requests, runtime idle API can be invoked and start manual BKOPS. We need to check if manual BKOPS is enabled on the device and stop it before serving new incoming requests in CQ mode. Change-Id: I870eff40ea9fa91eedb4c0d2600c32d8534a3868 Signed-off-by: Maya Erez --- drivers/mmc/card/block.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7224f4c57421..e15684473c45 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3431,14 +3431,27 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) unsigned int cmd_flags = req ? req->cmd_flags : 0; mmc_get_card(card); + + if (!card->host->cmdq_ctx.active_reqs && mmc_card_doing_bkops(card)) { + ret = mmc_cmdq_halt(card->host, true); + if (ret) + goto out; + ret = mmc_stop_bkops(card); + if (ret) { + pr_err("%s: %s: mmc_stop_bkops failed %d\n", + md->disk->disk_name, __func__, ret); + goto out; + } + ret = mmc_cmdq_halt(card->host, false); + if (ret) + goto out; + } + ret = mmc_blk_cmdq_part_switch(card, md); if (ret) { pr_err("%s: %s: partition switch failed %d\n", md->disk->disk_name, __func__, ret); - if (req) - blk_end_request_all(req, ret); - mmc_put_card(card); - goto switch_failure; + goto out; } if (req) { @@ -3455,7 +3468,13 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) } } -switch_failure: + return ret; + +out: + if (req) + blk_end_request_all(req, ret); + mmc_put_card(card); + return ret; } From 06ddb05074e204e336d39d0ae18975e9ce0c0d65 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 17 Sep 2015 16:07:39 +0300 Subject: [PATCH 375/472] mmc: core: check if manual BKOPS is ongoing before scaling Clock scaling commands are not allowed to be sent when the device performs manual BKOPS. In case the device is busy with manual BKOPS, skip the clock scaling and allow the device to continue the BKOPS. Clock scaling will be invoked again later by dev freq. Change-Id: I78b505326d245b1ddcd9d6f1cdd4294850889d45 Signed-off-by: Maya Erez --- drivers/mmc/core/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 8e24ddfb51db..3e279f4c6aa0 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -336,7 +336,8 @@ static bool mmc_is_valid_state_for_clk_scaling(struct mmc_host *host) * this mode. */ if (!card || (mmc_card_mmc(card) && - card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB)) + (card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB || + mmc_card_doing_bkops(card)))) return false; if (mmc_send_status(card, &status)) { From ae4bb021f88b8c13be8b12a6bcfacff52d2d0bd6 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 28 Sep 2015 15:54:21 +0530 Subject: [PATCH 376/472] mmc: sdhci-msm-ice: add crypto register dump for debug Dump crypto related register information during error for debugging purpose. Change-Id: I8976e8c0b5e9bda910634464202578dbacd7666e Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm-ice.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm-ice.c b/drivers/mmc/host/sdhci-msm-ice.c index a4840dbc2e80..7b9516e7d451 100644 --- a/drivers/mmc/host/sdhci-msm-ice.c +++ b/drivers/mmc/host/sdhci-msm-ice.c @@ -135,6 +135,7 @@ int sdhci_msm_ice_init(struct sdhci_host *host) pr_err("%s: ice init timedout after %d ms\n", mmc_hostname(host->mmc), SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); + sdhci_msm_ice_print_regs(host); return -ETIMEDOUT; } @@ -259,6 +260,7 @@ int sdhci_msm_ice_reset(struct sdhci_host *host) pr_err("%s: ice reset timedout after %d ms\n", mmc_hostname(host->mmc), SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); + sdhci_msm_ice_print_regs(host); return -ETIMEDOUT; } @@ -299,6 +301,7 @@ int sdhci_msm_ice_resume(struct sdhci_host *host) pr_err("%s: ice resume timedout after %d ms\n", mmc_hostname(host->mmc), SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); + sdhci_msm_ice_print_regs(host); return -ETIMEDOUT; } @@ -359,3 +362,12 @@ int sdhci_msm_ice_get_status(struct sdhci_host *host, int *ice_status) } return 0; } + +void sdhci_msm_ice_print_regs(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (msm_host->ice.vops->debug) + msm_host->ice.vops->debug(msm_host->ice.pdev); +} From 2906eb31e5867f13e6bc4c5a3b2d01deb97e9181 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Thu, 9 Jul 2015 17:35:22 +0530 Subject: [PATCH 377/472] mmc: card: read the firmware version from ext_csd Read the firmware version from ext_csd register and print it for debugging purpose. Change-Id: I4c1fefd5bff753915c9858fb35c958335986c778 Signed-off-by: Asutosh Das Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts and compilation errors] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 4 ++++ include/linux/mmc/card.h | 1 + 2 files changed, 5 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 356a6e036411..602dfb3a2b1d 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -637,6 +637,10 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) */ card->ext_csd.strobe_support = ext_csd[EXT_CSD_STROBE_SUPPORT]; card->ext_csd.cmdq_support = ext_csd[EXT_CSD_CMDQ_SUPPORT]; + card->ext_csd.fw_version = ext_csd[EXT_CSD_FIRMWARE_VERSION]; + pr_info("%s: eMMC FW version: 0x%02x\n", + mmc_hostname(card->host), + card->ext_csd.fw_version); if (card->ext_csd.cmdq_support) { /* * Queue Depth = N + 1, diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b8430181704a..4f49a254d3f3 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -135,6 +135,7 @@ struct mmc_ext_csd { u8 barrier_support; /* 486 */ u8 barrier_en; + u8 fw_version; /* 254 */ unsigned int feature_support; #define MMC_DISCARD_FEATURE BIT(0) /* CMD38 feature */ }; From f0b1bca2eba1429fa0a89561f5e7fd7d20e8e568 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 24 Sep 2015 17:39:22 -0700 Subject: [PATCH 378/472] mmc: block: fix DCMD timeout err handling DCMD requests don't have data, check for the data pointer before accessing it to prevent null pointer dereference in case of DCMD timeout err. Also signal a completion event for non flush requests like discard that wait for the completion of DCMD request. Change-Id: Ia71a5f1e278a039ba22f6ac42614d9ae79dba7e9 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index e15684473c45..1008b6247288 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3054,7 +3054,7 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) goto reset; } - if (mrq->data->error) { + if (mrq->data && mrq->data->error) { blk_end_request_all(mrq->req, mrq->data->error); for (; retry < MAX_RETRIES; retry++) { err = get_card_status(card, &status, 0); @@ -3087,8 +3087,18 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) } /* DCMD commands */ - if (mrq->cmd->error) + if (mrq->cmd && mrq->cmd->error) { + /* + * Notify completion for non flush commands like discard + * that wait for DCMD finish. + */ + if (!(mrq->req->cmd_flags & REQ_FLUSH)) { + complete(&mrq->completion); + goto reset; + } + clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); blk_end_request_all(mrq->req, mrq->cmd->error); + } reset: spin_lock_irq(mq->queue->queue_lock); From baf5c81221e569365ea58ddee370a68a503fe4e9 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 1 Oct 2015 11:27:17 +0300 Subject: [PATCH 379/472] mmc: block: stop BKOPs before handling RPMB and ioctl IOCTL and RPMB commands can be issued while the device is busy with background Operations handling. Stop the device BKOPs before handling the RPMB / IOCTL command. Change-Id: I088f74c77026ccd901276e1214e4466ac7815bf1 Signed-off-by: Maya Erez [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 1008b6247288..5c554c882f29 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -670,6 +670,15 @@ static int __mmc_blk_ioctl_cmd(struct mmc_card *card, struct mmc_blk_data *md, mrq.cmd = &cmd; + if (mmc_card_doing_bkops(card)) { + err = mmc_stop_bkops(card); + if (err) { + dev_err(mmc_dev(card->host), + "%s: stop_bkops failed %d\n", __func__, err); + return err; + } + } + err = mmc_blk_part_switch(card, md); if (err) return err; @@ -790,6 +799,25 @@ static int mmc_blk_ioctl_rpmb_cmd(struct block_device *bdev, mmc_get_card(card); + if (mmc_card_doing_bkops(card)) { + if (mmc_card_cmdq(card)) { + err = mmc_cmdq_halt(card->host, true); + if (err) + goto cmd_rel_host; + } + err = mmc_stop_bkops(card); + if (err) { + dev_err(mmc_dev(card->host), + "%s: stop_bkops failed %d\n", __func__, err); + goto cmd_rel_host; + } + if (mmc_card_cmdq(card)) { + err = mmc_cmdq_halt(card->host, false); + if (err) + goto cmd_rel_host; + } + } + err = mmc_blk_part_switch(card, md); if (err) goto cmd_rel_host; From 423dbd76eb6b6f0aa81dd51247ee1cdd0d6b2429 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 1 Oct 2015 17:56:54 +0300 Subject: [PATCH 380/472] mmc: sdhci-msm: add offset to CMDQ_COMMAND_DEBUG_RAM_x registers access Starting from MCI_VERSION 4.2.0 use a different offset for the following registers: PERIPH_SS_SDC1_SDCC_HC_CMDQ_COMMAND_DEBUG_RAM_n PERIPH_SS_SDC1_SDCC_HC_CMDQ_COMMAND_DEBUG_RAM_WRAPAROUND PERIPH_SS_SDC1_SDCC_HC_CMDQ_COMMAND_DEBUG_RAM_OVERLAP In addition, move dump debug ram functionality to the MSM specific file. Change-Id: I3b0f5101150de9f2c190ce69b306bd151cbb65ae Signed-off-by: Konstantin Dorfman --- drivers/mmc/host/cmdq_hci.c | 18 ------------------ drivers/mmc/host/cmdq_hci.h | 5 ----- drivers/mmc/host/sdhci-msm.c | 29 +++++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 3f4d2367a745..a2c2f7b68563 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -132,23 +132,6 @@ static void cmdq_clear_set_irqs(struct cmdq_host *cq_host, u32 clear, u32 set) #define DRV_NAME "cmdq-host" -static void cmdq_dump_debug_ram(struct cmdq_host *cq_host) -{ - int i = 0; - - pr_err("---- Debug RAM dump ----\n"); - pr_err(DRV_NAME ": Debug RAM wrap-around: 0x%08x | Debug RAM overlap: 0x%08x\n", - cmdq_readl(cq_host, CQ_CMD_DBG_RAM_WA), - cmdq_readl(cq_host, CQ_CMD_DBG_RAM_OL)); - - while (i < 16) { - pr_err(DRV_NAME ": Debug RAM dump [%d]: 0x%08x\n", i, - cmdq_readl(cq_host, CQ_CMD_DBG_RAM + (0x4 * i))); - i++; - } - pr_err("-------------------------\n"); -} - static void cmdq_dumpregs(struct cmdq_host *cq_host) { struct mmc_host *mmc = cq_host->mmc; @@ -193,7 +176,6 @@ static void cmdq_dumpregs(struct cmdq_host *cq_host) cmdq_readl(cq_host, CQ_VENDOR_CFG)); pr_err(DRV_NAME ": ===========================================\n"); - cmdq_dump_debug_ram(cq_host); if (cq_host->ops->dump_vendor_regs) cq_host->ops->dump_vendor_regs(mmc); } diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index d136e35032db..bda6fdc1ffb5 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -114,11 +114,6 @@ #define CQIC_DEFAULT_ICCTH 31 #define CQIC_DEFAULT_ICTOVAL 1 -#define CQ_CMD_DBG_RAM 0x158 -#define CQ_CMD_DBG_RAM_WA 0x198 -#define CQ_CMD_DBG_RAM_OL 0x19C - - /* attribute fields */ #define VALID(x) ((x & 1) << 0) #define END(x) ((x & 1) << 1) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 8801b3afa6ac..7b571bd0289d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -58,6 +58,7 @@ #define CORE_VERSION_MAJOR_MASK 0xF0000000 #define CORE_VERSION_MAJOR_SHIFT 28 #define CORE_VERSION_TARGET_MASK 0x000000FF +#define SDHCI_MSM_VER_420 0x49 #define CORE_GENERICS 0x70 #define SWITCHABLE_SIGNALLING_VOL (1 << 29) @@ -151,6 +152,10 @@ #define CORE_CDC_ERROR_CODE_MASK 0x7000000 +#define CQ_CMD_DBG_RAM 0x110 +#define CQ_CMD_DBG_RAM_WA 0x150 +#define CQ_CMD_DBG_RAM_OL 0x154 + #define CORE_CSR_CDC_GEN_CFG 0x178 #define CORE_CDC_SWITCH_BYPASS_OFF (1 << 0) #define CORE_CDC_SWITCH_RC_EN (1 << 1) @@ -2944,6 +2949,28 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, } #define MAX_TEST_BUS 60 +#define DRV_NAME "cmdq-host" +static void sdhci_msm_cmdq_dump_debug_ram(struct sdhci_msm_host *msm_host) +{ + int i = 0; + struct cmdq_host *cq_host = mmc_cmdq_private(msm_host->mmc); + u32 version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION); + u16 minor = version & CORE_VERSION_TARGET_MASK; + /* registers offset changed starting from 4.2.0 */ + int offset = minor >= SDHCI_MSM_VER_420 ? 0 : 0x48; + + pr_err("---- Debug RAM dump ----\n"); + pr_err(DRV_NAME ": Debug RAM wrap-around: 0x%08x | Debug RAM overlap: 0x%08x\n", + cmdq_readl(cq_host, CQ_CMD_DBG_RAM_WA + offset), + cmdq_readl(cq_host, CQ_CMD_DBG_RAM_OL + offset)); + + while (i < 16) { + pr_err(DRV_NAME ": Debug RAM dump [%d]: 0x%08x\n", i, + cmdq_readl(cq_host, CQ_CMD_DBG_RAM + offset + (4 * i))); + i++; + } + pr_err("-------------------------\n"); +} void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) { @@ -2956,6 +2983,8 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) u32 sts = 0; pr_info("----------- VENDOR REGISTER DUMP -----------\n"); + sdhci_msm_cmdq_dump_debug_ram(msm_host); + pr_info("Data cnt: 0x%08x | Fifo cnt: 0x%08x | Int sts: 0x%08x\n", readl_relaxed(msm_host->core_mem + CORE_MCI_DATA_CNT), readl_relaxed(msm_host->core_mem + CORE_MCI_FIFO_CNT), From 8552909808a16fead65d0c2bb6896ad794f96c04 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Sun, 20 Sep 2015 11:59:46 +0300 Subject: [PATCH 381/472] mmc: sdhci-msm: add sysfs entries for PM QoS Add sysfs entries to allow getting the current status of PM QoS voting and enable or disable voting. Change-Id: If5b8e4b155090343112916c9c57a766bb2104e10 Signed-off-by: Gilad Broner --- drivers/mmc/host/sdhci-msm.c | 180 +++++++++++++++++++++++++++++++++++ drivers/mmc/host/sdhci-msm.h | 4 + 2 files changed, 184 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 7b571bd0289d..cbfc473ca76f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3232,11 +3232,69 @@ void sdhci_msm_pm_qos_irq_unvote(struct sdhci_host *host, bool async) msm_host->pm_qos_irq.latency); } +static ssize_t +sdhci_msm_pm_qos_irq_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct sdhci_msm_pm_qos_irq *irq = &msm_host->pm_qos_irq; + + return snprintf(buf, PAGE_SIZE, + "IRQ PM QoS: enabled=%d, counter=%d, latency=%d\n", + irq->enabled, atomic_read(&irq->counter), irq->latency); +} + +static ssize_t +sdhci_msm_pm_qos_irq_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + return snprintf(buf, PAGE_SIZE, "%u\n", msm_host->pm_qos_irq.enabled); +} + +static ssize_t +sdhci_msm_pm_qos_irq_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + uint32_t value; + bool enable; + int ret; + + ret = kstrtou32(buf, 0, &value); + if (ret) + goto out; + enable = !!value; + + if (enable == msm_host->pm_qos_irq.enabled) + goto out; + + msm_host->pm_qos_irq.enabled = enable; + if (!enable) { + cancel_work_sync(&msm_host->pm_qos_irq.unvote_work); + atomic_set(&msm_host->pm_qos_irq.counter, 0); + msm_host->pm_qos_irq.latency = PM_QOS_DEFAULT_VALUE; + pm_qos_update_request(&msm_host->pm_qos_irq.req, + msm_host->pm_qos_irq.latency); + } + +out: + return count; +} + void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; struct sdhci_msm_pm_qos_latency *irq_latency; + int ret; if (!msm_host->pdata->pm_qos_data.irq_valid) return; @@ -3263,6 +3321,101 @@ void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host) pm_qos_add_request(&msm_host->pm_qos_irq.req, PM_QOS_CPU_DMA_LATENCY, msm_host->pm_qos_irq.latency); msm_host->pm_qos_irq.enabled = true; + + /* sysfs */ + msm_host->pm_qos_irq.enable_attr.show = + sdhci_msm_pm_qos_irq_enable_show; + msm_host->pm_qos_irq.enable_attr.store = + sdhci_msm_pm_qos_irq_enable_store; + sysfs_attr_init(&msm_host->pm_qos_irq.enable_attr.attr); + msm_host->pm_qos_irq.enable_attr.attr.name = "pm_qos_irq_enable"; + msm_host->pm_qos_irq.enable_attr.attr.mode = S_IRUGO | S_IWUSR; + ret = device_create_file(&msm_host->pdev->dev, + &msm_host->pm_qos_irq.enable_attr); + if (ret) + pr_err("%s: fail to create pm_qos_irq_enable (%d)\n", + __func__, ret); + + msm_host->pm_qos_irq.status_attr.show = sdhci_msm_pm_qos_irq_show; + msm_host->pm_qos_irq.status_attr.store = NULL; + sysfs_attr_init(&msm_host->pm_qos_irq.status_attr.attr); + msm_host->pm_qos_irq.status_attr.attr.name = "pm_qos_irq_status"; + msm_host->pm_qos_irq.status_attr.attr.mode = S_IRUGO; + ret = device_create_file(&msm_host->pdev->dev, + &msm_host->pm_qos_irq.status_attr); + if (ret) + pr_err("%s: fail to create pm_qos_irq_status (%d)\n", + __func__, ret); +} + +static ssize_t sdhci_msm_pm_qos_group_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + struct sdhci_msm_pm_qos_group *group; + int i; + int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups; + int offset = 0; + + for (i = 0; i < nr_groups; i++) { + group = &msm_host->pm_qos[i]; + offset += snprintf(&buf[offset], PAGE_SIZE, + "Group #%d (mask=0x%lx) PM QoS: enabled=%d, counter=%d, latency=%d\n", + i, group->req.cpus_affine.bits[0], + msm_host->pm_qos_group_enable, + atomic_read(&group->counter), + group->latency); + } + + return offset; +} + +static ssize_t sdhci_msm_pm_qos_group_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + return snprintf(buf, PAGE_SIZE, "%s\n", + msm_host->pm_qos_group_enable ? "enabled" : "disabled"); +} + +static ssize_t sdhci_msm_pm_qos_group_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups; + uint32_t value; + bool enable; + int ret; + int i; + + ret = kstrtou32(buf, 0, &value); + if (ret) + goto out; + enable = !!value; + + if (enable == msm_host->pm_qos_group_enable) + goto out; + + msm_host->pm_qos_group_enable = enable; + if (!enable) { + for (i = 0; i < nr_groups; i++) { + cancel_work_sync(&msm_host->pm_qos[i].unvote_work); + atomic_set(&msm_host->pm_qos[i].counter, 0); + msm_host->pm_qos[i].latency = PM_QOS_DEFAULT_VALUE; + pm_qos_update_request(&msm_host->pm_qos[i].req, + msm_host->pm_qos[i].latency); + } + } + +out: + return count; } static int sdhci_msm_get_cpu_group(struct sdhci_msm_host *msm_host, int cpu) @@ -3349,6 +3502,7 @@ void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, int nr_groups = msm_host->pdata->pm_qos_data.cpu_group_map.nr_groups; struct sdhci_msm_pm_qos_group *group; int i; + int ret; if (msm_host->pm_qos_group_enable) return; @@ -3378,6 +3532,32 @@ void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, } msm_host->pm_qos_prev_cpu = -1; msm_host->pm_qos_group_enable = true; + + /* sysfs */ + msm_host->pm_qos_group_status_attr.show = sdhci_msm_pm_qos_group_show; + msm_host->pm_qos_group_status_attr.store = NULL; + sysfs_attr_init(&msm_host->pm_qos_group_status_attr.attr); + msm_host->pm_qos_group_status_attr.attr.name = + "pm_qos_cpu_groups_status"; + msm_host->pm_qos_group_status_attr.attr.mode = S_IRUGO; + ret = device_create_file(&msm_host->pdev->dev, + &msm_host->pm_qos_group_status_attr); + if (ret) + dev_err(&msm_host->pdev->dev, "%s: fail to create pm_qos_group_status_attr (%d)\n", + __func__, ret); + msm_host->pm_qos_group_enable_attr.show = + sdhci_msm_pm_qos_group_enable_show; + msm_host->pm_qos_group_enable_attr.store = + sdhci_msm_pm_qos_group_enable_store; + sysfs_attr_init(&msm_host->pm_qos_group_enable_attr.attr); + msm_host->pm_qos_group_enable_attr.attr.name = + "pm_qos_cpu_groups_enable"; + msm_host->pm_qos_group_enable_attr.attr.mode = S_IRUGO; + ret = device_create_file(&msm_host->pdev->dev, + &msm_host->pm_qos_group_enable_attr); + if (ret) + dev_err(&msm_host->pdev->dev, "%s: fail to create pm_qos_group_enable_attr (%d)\n", + __func__, ret); } static void sdhci_msm_pre_req(struct sdhci_host *host, diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 01ad6d1593a2..0a90409a7409 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -120,6 +120,8 @@ struct sdhci_msm_pm_qos_group { struct sdhci_msm_pm_qos_irq { struct pm_qos_request req; struct work_struct unvote_work; + struct device_attribute enable_attr; + struct device_attribute status_attr; atomic_t counter; s32 latency; bool enabled; @@ -204,6 +206,8 @@ struct sdhci_msm_host { u32 ice_clk_rate; struct sdhci_msm_pm_qos_group *pm_qos; int pm_qos_prev_cpu; + struct device_attribute pm_qos_group_enable_attr; + struct device_attribute pm_qos_group_status_attr; bool pm_qos_group_enable; struct sdhci_msm_pm_qos_irq pm_qos_irq; }; From 8d85485469fda5ac3a212a083d3f39cf54c4f963 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 30 Sep 2015 15:55:41 +0530 Subject: [PATCH 382/472] mmc: cmdq_hci: Fix ADMA error issue The controller triggers an ADMA error when ADMA engine is configured and used in 32-bit mode. This is because the current code always writes 64-bit address to 32-bit address field of a transfer descriptor (bits [63:32]). This corrupts the first 32-bit value of the next transfer descriptor. Below scenario describes how ADMA error can happen - 1. Req#1 - uses slot 1, prepares it's descriptors, queues to the controller 2. Req#2 - uses slot 0, prepares max descriptors (cq_host->mmc->max_segs). 3. Req#1 gets ADMA error from the controller. At step 2, when it prepares the last transfer descriptor (max_segs), it overwrites the 32-bit address field with a 64-bit address and thus corrupts the first entry of slot 1 transfer descriptor. Change-Id: I3eb2dbb40c76ec77626f647d6ec24df4a0858fcb Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index a2c2f7b68563..b29472afd26c 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -468,10 +468,9 @@ static int cmdq_dma_map(struct mmc_host *host, struct mmc_request *mrq) return sg_count; } -static void cmdq_set_tran_desc(u8 *desc, - dma_addr_t addr, int len, bool end) +static void cmdq_set_tran_desc(u8 *desc, dma_addr_t addr, int len, + bool end, bool is_dma64) { - __le64 *dataddr = (__le64 __force *)(desc + 4); __le32 *attr = (__le32 __force *)desc; *attr = (VALID(1) | @@ -480,7 +479,15 @@ static void cmdq_set_tran_desc(u8 *desc, ACT(0x4) | DAT_LENGTH(len)); - dataddr[0] = cpu_to_le64(addr); + if (is_dma64) { + __le64 *dataddr = (__le64 __force *)(desc + 4); + + dataddr[0] = cpu_to_le64(addr); + } else { + __le32 *dataddr = (__le32 __force *)(desc + 4); + + dataddr[0] = cpu_to_le32(addr); + } } static int cmdq_prep_tran_desc(struct mmc_request *mrq, @@ -509,7 +516,7 @@ static int cmdq_prep_tran_desc(struct mmc_request *mrq, if ((i+1) == sg_count) end = true; - cmdq_set_tran_desc(desc, addr, len, end); + cmdq_set_tran_desc(desc, addr, len, end, cq_host->dma64); desc += cq_host->trans_desc_len; } From 09f5d9f02107547cacbd645b193d87e9d20e142d Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Mon, 5 Oct 2015 11:33:11 -0700 Subject: [PATCH 383/472] mmc: core: fix downdifferential for clock scaling "downdifferential" parameter of devfreq's simple ondemand governor supposed to report expected difference between upthreshold and downthreshold value. In other words, downdifferential = upthreshold - downthreshold. But currently downdifferential is set to same as downthreshold, this change fixes this issue. Change-Id: Ic2e762d192f1fed8f94d4d2579d6a4b5d4c2c8b5 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 3e279f4c6aa0..42ad100a5104 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -681,7 +681,7 @@ int mmc_init_clk_scaling(struct mmc_host *host) host->clk_scaling.ondemand_gov_data.upthreshold = host->clk_scaling.upthreshold; host->clk_scaling.ondemand_gov_data.downdifferential = - host->clk_scaling.downthreshold; + host->clk_scaling.upthreshold - host->clk_scaling.downthreshold; err = mmc_devfreq_create_freq_table(host); if (err) { From 6d9a04f1b112e5afd57bafb1fb80e67778656844 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Sat, 26 Sep 2015 17:38:26 -0700 Subject: [PATCH 384/472] mmc: core: allow hosts to specify a large discard size max_busy_timeout is used to decide whether R1B response should be used or a R1 response should be used. This is also used to decide what the discard size of mmc queue (registered with block layer) can be set to. In order to keep both the features in place, this change will allow for hosts to specify a larger discard size while still specifying max_busy_timeout. Change-Id: I1e607329c4377897a7cb4086db02cbc150bd02b7 Signed-off-by: Krishna Konda --- drivers/mmc/core/core.c | 3 ++- include/linux/mmc/host.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 42ad100a5104..2b5298ea0555 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3761,7 +3761,8 @@ unsigned int mmc_calc_max_discard(struct mmc_card *card) struct mmc_host *host = card->host; unsigned int max_discard, max_trim; - if (!host->max_busy_timeout) + if (!host->max_busy_timeout || + (host->caps2 & MMC_CAP2_MAX_DISCARD_SIZE)) return UINT_MAX; /* diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 0f8d285a1192..87f90a7d061a 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -442,6 +442,8 @@ struct mmc_host { #define MMC_CAP2_CMD_QUEUE (1 << 26) /* support eMMC command queue */ #define MMC_CAP2_SANITIZE (1 << 27) /* Support Sanitize */ #define MMC_CAP2_SLEEP_AWAKE (1 << 28) /* Use Sleep/Awake (CMD5) */ +/* use max discard ignoring max_busy_timeout parameter */ +#define MMC_CAP2_MAX_DISCARD_SIZE (1 << 29) mmc_pm_flag_t pm_caps; /* supported pm features */ From d2cd07bae2906121ab050ef345b26ae5f401f9ca Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Sat, 26 Sep 2015 17:55:48 -0700 Subject: [PATCH 385/472] mmc: sdhci-msm: use max discard size supported for mmc queue With newer mmc drivers, max_discard_to has been remvoed from the sdhci driver. So instead of incorrectly using max_busy_timeout for calculating max_discard to be used by mmc queue, use the mmc cap to indicate that the max discard should be used for msm sdhci driver. Change-Id: I424cd0a5ee9ffd7199be58a5a091984c5fcda52f Signed-off-by: Krishna Konda [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 2 +- drivers/mmc/host/sdhci.c | 4 ---- drivers/mmc/host/sdhci.h | 12 +++--------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index cbfc473ca76f..a5e18e35b115 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -4032,7 +4032,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) host->quirks |= SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN; host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC; host->quirks2 |= SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK; - host->quirks2 |= SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE; host->quirks2 |= SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD; host->quirks2 |= SDHCI_QUIRK2_BROKEN_PRESET_VALUE; host->quirks2 |= SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT; @@ -4094,6 +4093,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE; + msm_host->mmc->caps2 |= MMC_CAP2_MAX_DISCARD_SIZE; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 5f139a5b9c87..999bd8e839de 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3791,10 +3791,6 @@ int sdhci_add_host(struct sdhci_host *host) if (override_timeout_clk) host->timeout_clk = override_timeout_clk; - - if (!(host->quirks2 & SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE)) - mmc->max_busy_timeout = (1 << 27) / host->timeout_clk; - mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23; mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index a4017e6f6c8c..489aa2572692 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -452,12 +452,6 @@ struct sdhci_host { * required frequency. */ #define SDHCI_QUIRK2_ALWAYS_USE_BASE_CLOCK (1<<19) -/* - * Dont use the max_discard_to in sdhci driver so that the maximum discard - * unit gets picked by the mmc queue. Otherwise, it takes a long time for - * secure discard kind of operations to complete. - */ -#define SDHCI_QUIRK2_USE_MAX_DISCARD_SIZE (1<<20) /* * Ignore data timeout error for R1B commands as there will be no @@ -465,20 +459,20 @@ struct sdhci_host { * could be lager than the maximum timeout value that controller * can handle. */ -#define SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD (1<<21) +#define SDHCI_QUIRK2_IGNORE_DATATOUT_FOR_R1BCMD (1<<20) /* * The preset value registers are not properly initialized by * some hardware and hence preset value must not be enabled for * such controllers. */ -#define SDHCI_QUIRK2_BROKEN_PRESET_VALUE (1<<22) +#define SDHCI_QUIRK2_BROKEN_PRESET_VALUE (1<<21) /* * Some controllers define the usage of 0xF in data timeout counter * register (0x2E) which is actually a reserved bit as per * specification. */ -#define SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT (1<<23) +#define SDHCI_QUIRK2_USE_RESERVED_MAX_TIMEOUT (1<<22) /* * This is applicable for controllers that advertize timeout clock * value in capabilities register (bit 5-0) as just 50MHz whereas the From 77da995311249747adf8b5e3b08e1ab415437f25 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 5 Oct 2015 16:20:10 +0530 Subject: [PATCH 386/472] mmc: cmdq_hci: Fix issue with triggering queue status after dcmd To trigger queue status command after sending dcmd, we need to set bit 31 of CQ_VENDOR_CFG register. But the current code incorrectly sets bit 31 of CQCTL register. Change-Id: Ic5b914cf6a5237ac51b2104453caba2c49c1efbc Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index b29472afd26c..4c6efe93ae91 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -662,7 +662,7 @@ static void cmdq_finish_data(struct mmc_host *mmc, unsigned int tag) if (mrq->cmdq_req->cmdq_req_flags & DCMD) cmdq_writel(cq_host, cmdq_readl(cq_host, CQ_VENDOR_CFG) | - CMDQ_SEND_STATUS_TRIGGER, CQCTL); + CMDQ_SEND_STATUS_TRIGGER, CQ_VENDOR_CFG); cmdq_runtime_pm_put(cq_host); if (cq_host->ops->crypto_cfg_reset) From b78e1b402589663b584111e24b6756beccef4797 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Tue, 15 Sep 2015 15:57:35 -0700 Subject: [PATCH 387/472] mmc: debugfs: add debugfs entry to force raise host errors The SDHC spec allows to force raise errors that is useful for debugging error handler routines. Add debugfs entry force_error to trigger host errors from userspace. Check SDHCI_SET_INT_ERROR register for error bitmask info. Usage: echo 0x1 > /sys/kernel/debug/mmcX/force_error X - denotes the slot id Change-Id: I9f67442a79b2645cbdc3020d1a10c0b32840ce32 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/debugfs.c | 19 +++++++++++++++++++ drivers/mmc/host/sdhci.c | 13 +++++++++++++ include/linux/mmc/host.h | 1 + 3 files changed, 33 insertions(+) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 98b9e90e667a..7e3d82f658e7 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -313,6 +313,21 @@ out: DEFINE_SIMPLE_ATTRIBUTE(mmc_max_clock_fops, mmc_max_clock_get, mmc_max_clock_set, "%llu\n"); +static int mmc_force_err_set(void *data, u64 val) +{ + struct mmc_host *host = data; + + if (host && host->ops && host->ops->force_err_irq) { + mmc_host_clk_hold(host); + host->ops->force_err_irq(host, val); + mmc_host_clk_release(host); + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(mmc_force_err_fops, NULL, mmc_force_err_set, "%llu\n"); + void mmc_add_host_debugfs(struct mmc_host *host) { struct dentry *root; @@ -362,6 +377,10 @@ void mmc_add_host_debugfs(struct mmc_host *host) &host->fail_mmc_request))) goto err_node; #endif + if (!debugfs_create_file("force_error", S_IWUSR, root, host, + &mmc_force_err_fops)) + goto err_node; + return; err_node: diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 999bd8e839de..9761cd7f5a80 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2603,6 +2603,18 @@ static int sdhci_late_init(struct mmc_host *mmc) return 0; } + +static void sdhci_force_err_irq(struct mmc_host *mmc, u64 errmask) +{ + struct sdhci_host *host = mmc_priv(mmc); + u16 mask = errmask & 0xFFFF; + + pr_err("%s: Force raise error mask:0x%04x\n", __func__, mask); + sdhci_runtime_pm_get(host); + sdhci_writew(host, mask, SDHCI_SET_INT_ERROR); + sdhci_runtime_pm_put(host); +} + static const struct mmc_host_ops sdhci_ops = { .init = sdhci_late_init, .request = sdhci_request, @@ -2625,6 +2637,7 @@ static const struct mmc_host_ops sdhci_ops = { .notify_load = sdhci_notify_load, .notify_halt = sdhci_notify_halt, .detect = sdhci_detect, + .force_err_irq = sdhci_force_err_irq, }; /*****************************************************************************\ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 87f90a7d061a..a312467be721 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -182,6 +182,7 @@ struct mmc_host_ops { int (*notify_load)(struct mmc_host *, enum mmc_load); void (*notify_halt)(struct mmc_host *mmc, bool halt); void (*detect)(struct mmc_host *host, bool detected); + void (*force_err_irq)(struct mmc_host *host, u64 errmask); }; struct mmc_card; From 38c20819fce7eb22a3d0f560049fc8f5c0292bf7 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 12 Aug 2015 17:53:21 -0700 Subject: [PATCH 388/472] mmc: queue: fix the cmdq thread wake up handling If request has to be requeued (due to any DCMD commmand pending or cmdq being halted) and if we change the task status to interruptible before going to sleep then thread may not wakeup again. Note that blk_requeue_request() doesn't trigger ->request_fn() again to wakeup the thread. Fix this issue by making cmdq thread wait for the completion of DCMD or until the cmdq is unhalted. This change also simplifies the cmdq thread function. Change-Id: Iebffc993241e5fadb2962fedc44576566dc66e9c Signed-off-by: Subhash Jadavani Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 18 ++--- drivers/mmc/card/queue.c | 166 +++++++++++++++++++++------------------ drivers/mmc/card/queue.h | 1 + drivers/mmc/core/core.c | 4 +- include/linux/mmc/host.h | 9 +-- 5 files changed, 101 insertions(+), 97 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 5c554c882f29..4b2025fc18d1 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1643,9 +1643,7 @@ clear_dcmd: } out: blk_end_request(req, err, blk_rq_bytes(req)); - - if (test_and_clear_bit(0, &ctx_info->req_starved)) - blk_run_queue(mq->queue); + wake_up(&ctx_info->wait); mmc_put_card(card); return err ? 1 : 0; } @@ -1759,9 +1757,7 @@ clear_dcmd: } out: blk_end_request(req, err, blk_rq_bytes(req)); - - if (test_and_clear_bit(0, &ctx_info->req_starved)) - blk_run_queue(mq->queue); + wake_up(&ctx_info->wait); mmc_put_card(card); return err ? 1 : 0; } @@ -3141,10 +3137,9 @@ unhalt: out: host->err_mrq = NULL; pm_runtime_mark_last_busy(&card->dev); + clear_bit(CMDQ_STATE_ERR, &ctx_info->curr_state); + wake_up(&ctx_info->wait); __mmc_put_card(card); - - if (test_and_clear_bit(0, &ctx_info->req_starved)) - blk_run_queue(mrq->req->q); } /* invoked by block layer in softirq context */ @@ -3200,9 +3195,8 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) out: mmc_cmdq_clk_scaling_stop_busy(host, true, is_dcmd); - if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state) && - test_and_clear_bit(0, &ctx_info->req_starved)) - blk_run_queue(mq->queue); + if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) + wake_up(&ctx_info->wait); mmc_put_card(host->card); if (!ctx_info->active_reqs) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index efe2059d4fad..05f456b0d1c1 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -55,91 +56,81 @@ static int mmc_prep_request(struct request_queue *q, struct request *req) return BLKPREP_OK; } -static inline bool mmc_cmdq_should_pull_reqs(struct mmc_host *host, - struct mmc_cmdq_context_info *ctx, - struct request *req) - +static struct request *mmc_peek_request(struct mmc_queue *mq) { - bool ret = true; + struct request_queue *q = mq->queue; - if ((req->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && - test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) - ret = false; - else if (!host->card->part_curr && - mmc_host_halt(host) && !mmc_card_suspended(host->card)) - ret = false; - else if (test_bit(CMDQ_STATE_ERR, &ctx->curr_state)) - ret = false; + spin_lock_irq(q->queue_lock); + mq->cmdq_req_peeked = blk_peek_request(q); + spin_unlock_irq(q->queue_lock); - if (!ret) - pr_debug("%s: %s: skip pulling reqs: state: %lu, cmd_flags: 0x%x\n", - mmc_hostname(host), __func__, - ctx->curr_state, (unsigned int)req->cmd_flags); - return ret; + return mq->cmdq_req_peeked; +} + +static bool mmc_check_blk_queue_start_tag(struct request_queue *q, + struct request *req) +{ + int ret; + + spin_lock_irq(q->queue_lock); + ret = blk_queue_start_tag(q, req); + spin_unlock_irq(q->queue_lock); + + return !!ret; +} + +static inline void mmc_cmdq_ready_wait(struct mmc_host *host, + struct mmc_queue *mq) +{ + struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; + struct request_queue *q = mq->queue; + + /* + * Wait until all of the following conditions are true: + * 1. There is a request pending in the block layer queue + * to be processed. + * 2. If the peeked request is flush/discard then there shouldn't + * be any other direct command active. + * 3. cmdq state should be unhalted. + * 4. cmdq state shouldn't be in error state. + * 5. free tag available to process the new request. + */ + wait_event(ctx->wait, mmc_peek_request(mq) && + !((mq->cmdq_req_peeked->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) + && test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) + && !(!host->card->part_curr && !mmc_card_suspended(host->card) + && mmc_host_halt(host)) + && !test_bit(CMDQ_STATE_ERR, &ctx->curr_state) + && !mmc_check_blk_queue_start_tag(q, mq->cmdq_req_peeked)); } static int mmc_cmdq_thread(void *d) { struct mmc_queue *mq = d; - struct request_queue *q = mq->queue; struct mmc_card *card = mq->card; - - struct request *req; struct mmc_host *host = card->host; - struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; - unsigned long flags; current->flags |= PF_MEMALLOC; if (card->host->wakeup_on_idle) set_wake_up_idle(true); - down(&mq->thread_sem); while (1) { int ret = 0; - spin_lock_irqsave(q->queue_lock, flags); - set_current_state(TASK_INTERRUPTIBLE); - req = blk_peek_request(q); - if (req) { - ret = blk_queue_start_tag(q, req); - spin_unlock_irqrestore(q->queue_lock, flags); - if (ret) { - test_and_set_bit(0, &ctx->req_starved); - schedule(); - } else { - if (!mmc_cmdq_should_pull_reqs(host, ctx, - req)) { - spin_lock_irqsave(q->queue_lock, flags); - blk_requeue_request(q, req); - spin_unlock_irqrestore(q->queue_lock, - flags); - test_and_set_bit(0, &ctx->req_starved); - schedule(); - continue; - } - set_current_state(TASK_RUNNING); - ret = mq->cmdq_issue_fn(mq, req); - if (ret) { - pr_err("%s: failed (%d) to issue req, requeue\n", - mmc_hostname(host), ret); - spin_lock_irqsave(q->queue_lock, flags); - blk_requeue_request(q, req); - spin_unlock_irqrestore(q->queue_lock, - flags); - } - } - } else { - spin_unlock_irqrestore(q->queue_lock, flags); - if (kthread_should_stop()) { - set_current_state(TASK_RUNNING); - break; - } - up(&mq->thread_sem); - schedule(); - down(&mq->thread_sem); - } + mmc_cmdq_ready_wait(host, mq); + + ret = mq->cmdq_issue_fn(mq, mq->cmdq_req_peeked); + /* + * Don't requeue if issue_fn fails, just bug on. + * We don't expect failure here and there is no recovery other + * than fixing the actual issue if there is any. + * Also we end the request if there is a partition switch error, + * so we should not requeue the request here. + */ + if (ret) + BUG_ON(1); } /* loop */ - up(&mq->thread_sem); + return 0; } @@ -211,7 +202,7 @@ static void mmc_cmdq_dispatch_req(struct request_queue *q) { struct mmc_queue *mq = q->queuedata; - wake_up_process(mq->thread); + wake_up(&mq->card->host->cmdq_ctx.wait); } /* @@ -350,7 +341,6 @@ int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, if (card->host->cmdq_ops->init) card->host->cmdq_ops->init(card->host); mq->queue->queuedata = mq; - card->host->cmdq_ctx.q = mq->queue; mq->thread = kthread_run(mmc_cmdq_thread, mq, "mmc-cmdqd/%d%s", host->index, @@ -634,6 +624,8 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) } init_waitqueue_head(&card->host->cmdq_ctx.queue_empty_wq); + init_waitqueue_head(&card->host->cmdq_ctx.wait); + mq->mqrq_cmdq = kzalloc( sizeof(struct mmc_queue_req) * q_depth, GFP_KERNEL); if (!mq->mqrq_cmdq) { @@ -711,29 +703,47 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) unsigned long flags; int rc = 0; struct mmc_card *card = mq->card; + struct request *req; + #define SLEEP_TIME_BETWEEN_BLK_REQ_CHECK 100 /* microseconds */ if (card->cmdq_init && blk_queue_tagged(q)) { struct mmc_host *host = card->host; + if (test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags)) + goto out; + spin_lock_irqsave(q->queue_lock, flags); blk_stop_queue(q); spin_unlock_irqrestore(q->queue_lock, flags); + wake_up(&host->cmdq_ctx.wait); + if (wait) { - /* - * Wait for already queued requests to be issued by - * mmc_cmdqd. - */ - down(&mq->thread_sem); + while (1) { + spin_lock_irqsave(q->queue_lock, flags); + req = blk_peek_request(q); + spin_unlock_irqrestore(q->queue_lock, flags); + + if (!req) + break; + + /* sleep for some time before rechecking */ + usleep_range(SLEEP_TIME_BETWEEN_BLK_REQ_CHECK, + SLEEP_TIME_BETWEEN_BLK_REQ_CHECK + 10); + } + /* Wait for already issued requests to complete */ if (host->cmdq_ctx.active_reqs) wait_for_completion( &mq->cmdq_shutdown_complete); mq->cmdq_shutdown(mq); - } else if (!test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags)) { - rc = down_trylock(&mq->thread_sem); - if (rc || host->cmdq_ctx.active_reqs) { + } else { + spin_lock_irqsave(q->queue_lock, flags); + req = blk_peek_request(q); + spin_unlock_irqrestore(q->queue_lock, flags); + + if (req || host->cmdq_ctx.active_reqs) { clear_bit(MMC_QUEUE_SUSPENDED, &mq->flags); spin_lock_irqsave(q->queue_lock, flags); blk_start_queue(q); @@ -777,11 +787,13 @@ out: void mmc_queue_resume(struct mmc_queue *mq) { struct request_queue *q = mq->queue; + struct mmc_card *card = mq->card; unsigned long flags; if (test_and_clear_bit(MMC_QUEUE_SUSPENDED, &mq->flags)) { - up(&mq->thread_sem); + if (!(card->cmdq_init && blk_queue_tagged(q))) + up(&mq->thread_sem); spin_lock_irqsave(q->queue_lock, flags); blk_start_queue(q); diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index e13f3ceb0cc2..e67d0546346a 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -73,6 +73,7 @@ struct mmc_queue { struct completion cmdq_shutdown_complete; struct completion cmdq_pending_req_done; + struct request *cmdq_req_peeked; int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); void (*cmdq_shutdown)(struct mmc_queue *); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 2b5298ea0555..779c4b8966a4 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -32,7 +32,6 @@ #include #include #include -#include #include @@ -1553,8 +1552,7 @@ int mmc_cmdq_halt(struct mmc_host *host, bool halt) mmc_host_set_halt(host); else if (!err && !halt) { mmc_host_clr_halt(host); - if (host->cmdq_ctx.q) - blk_run_queue(host->cmdq_ctx.q); + wake_up(&host->cmdq_ctx.wait); } } else { err = -ENOSYS; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index a312467be721..3b028e566282 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -244,11 +244,12 @@ struct mmc_slot { * @active_reqs requests being processed * @data_active_reqs data requests being processed * @curr_state state of cmdq engine - * @req_starved completion should invoke the request_fn since - * no tags were available * @cmdq_ctx_lock acquire this before accessing this structure * @queue_empty_wq workqueue for waiting for all * the outstanding requests to be completed + * @wait waiting for all conditions described in + * mmc_cmdq_ready_wait to be satisified before + * issuing the new request to LLD. */ struct mmc_cmdq_context_info { unsigned long active_reqs; /* in-flight requests */ @@ -257,10 +258,8 @@ struct mmc_cmdq_context_info { #define CMDQ_STATE_ERR 0 #define CMDQ_STATE_DCMD_ACTIVE 1 #define CMDQ_STATE_HALT 2 - /* no free tag available */ - unsigned long req_starved; wait_queue_head_t queue_empty_wq; - struct request_queue *q; + wait_queue_head_t wait; }; /** From 4c7ffe38b389ba2df9b36a4f3cfd70447f042c0b Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Wed, 30 Sep 2015 18:46:18 -0700 Subject: [PATCH 389/472] mmc: sdhci_msm: keep a reference to the sdhc host instance Keep a reference to the sdhc host instance to retrieve all the necessary data structures in case of a crash for debugging. Change-Id: Ic8d696d53cf48290dbcc92f1567650989dd45755 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index a5e18e35b115..3b328d6197ef 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -212,6 +212,9 @@ static const u32 tuning_block_128[] = { 0xFFFFBBBB, 0xFFFF77FF, 0xFF7777FF, 0xEEDDBB77 }; +/* global to hold each slot instance for debug */ +static struct sdhci_msm_host *sdhci_slot[2]; + static int disable_slots; /* root can write, others read */ module_param(disable_slots, int, S_IRUGO|S_IWUSR); @@ -3852,6 +3855,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto pltfm_free; } + if (ret <= 2) + sdhci_slot[ret-1] = msm_host; + msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev, msm_host); if (!msm_host->pdata) { From 28791b7d462d292aff9fea06376656c43e84d0eb Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 1 Oct 2015 14:34:10 -0700 Subject: [PATCH 390/472] mmc: cmdq_hci: verify the doorbell bit for DCMD before setting We make sure that the doorbell bit for any regular slot is not set before ringing the doorbell for that slot, make sure of the same for DCMD slot too. Change-Id: Ia96e8d6ae0c28aad21f4a3cf46b27c7a5c878971 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/cmdq_hci.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 4c6efe93ae91..af67be35ef4b 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -631,8 +631,6 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) goto out; } - BUG_ON(cmdq_readl(cq_host, CQTDBR) & (1 << tag)); - cq_host->mrq_slot[tag] = mrq; if (cq_host->ops->set_tranfer_params) cq_host->ops->set_tranfer_params(mmc); @@ -643,6 +641,10 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) ring_doorbell: /* Ensure the task descriptor list is flushed before ringing doorbell */ wmb(); + if (cmdq_readl(cq_host, CQTDBR) & (1 << tag)) { + cmdq_dumpregs(cq_host); + BUG_ON(1); + } cmdq_writel(cq_host, 1 << tag, CQTDBR); /* Commit the doorbell write immediately */ wmb(); From 0044c4d39c43fbe97e98a8607bf95321fb6fa9e0 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Wed, 7 Oct 2015 21:58:28 +0300 Subject: [PATCH 391/472] mmc: sdhci-msm: enable partial initialization in resume Enable partial init on eMMC devices for using card Sleep/Awake sequence (CMD5). Card will be sent to Sleep state during runtime/system suspend and will be woken up during runtime/system resume. By using this sequence the card doesn't need full initialization which gives time reduction in system/runtime resume path. Change-Id: Ide2b8050b56e858cda7bb22b4acf43b5bd3db786 Signed-off-by: Talel Shenhar Signed-off-by: Maya Erez --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 3b328d6197ef..cf390ff7a73c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -4100,6 +4100,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE; msm_host->mmc->caps2 |= MMC_CAP2_MAX_DISCARD_SIZE; + msm_host->mmc->caps2 |= MMC_CAP2_SLEEP_AWAKE; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; From 9a4df9847e68932453e45e0a962352d9d14113fc Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Tue, 6 Oct 2015 13:25:45 +0300 Subject: [PATCH 392/472] mmc: cmdq_hci: fix race between req completion and clearing CQTCN CQ irq handler first calls completion path for every notified tag and then clears CQTCN register for all completed tags. This approach could cause following problem: 1) issue context (unblocked by tag completion) issues next request, CQE successfully completes it and notifies task completion through CQTCN again for the same tag. 2) CQ irq handler proceeds to clear CQTCN for the original request. In the above scenario clear of CQTCN register will mask next request completion and issue context will be never unblocked. Hence clear CQTCN first and then notify the request completion. Change-Id: Ie644e24279ca30de42bbc9f8e1ae4326609d38a5 Signed-off-by: Konstantin Dorfman Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/cmdq_hci.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index af67be35ef4b..021ba4cc4ec8 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -737,18 +737,28 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) } if (status & CQIS_TCC) { - /* read QCTCN and complete the request */ + /* read CQTCN and complete the request */ comp_status = cmdq_readl(cq_host, CQTCN); if (!comp_status) goto out; - + /* + * The CQTCN must be cleared before notifying req completion + * to upper layers to avoid missing completion notification + * of new requests with the same tag. + */ + cmdq_writel(cq_host, comp_status, CQTCN); + /* + * A write memory barrier is necessary to guarantee that CQTCN + * gets cleared first before next doorbell for the same tag is + * set but that is already achieved by the barrier present + * before setting doorbell, hence one is not needed here. + */ for_each_set_bit(tag, &comp_status, cq_host->num_slots) { /* complete the corresponding mrq */ pr_debug("%s: completing tag -> %lu\n", mmc_hostname(mmc), tag); cmdq_finish_data(mmc, tag); } - cmdq_writel(cq_host, comp_status, CQTCN); } if (status & CQIS_HAC) { From 24bd8b95d9063ad4be0d484fb4e2a1956ea9977e Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Tue, 15 Sep 2015 19:21:32 +0530 Subject: [PATCH 393/472] mmc: cmdq_hci: Add retry mechanism for cmdq_halt Currently all functions which calls cmdq_halt relies upon HALT to pass other wise considers it as a FATAL error (like clk scaling). So add retry mechanism in halt - retry if halt completion timesout & HALT also did not complete(by doing register read). Change-Id: I59e199681e46085f804636f4c07249e6f21de1d5 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/cmdq_hci.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 021ba4cc4ec8..4e79db942a04 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -778,17 +778,27 @@ static int cmdq_halt(struct mmc_host *mmc, bool halt) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); u32 ret = 0; + int retries = 3; cmdq_runtime_pm_get(cq_host); if (halt) { - cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) | HALT, - CQCTL); - ret = wait_for_completion_timeout(&cq_host->halt_comp, + while (retries) { + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) | HALT, + CQCTL); + ret = wait_for_completion_timeout(&cq_host->halt_comp, msecs_to_jiffies(HALT_TIMEOUT_MS)); - /* halt done: re-enable legacy interrupts */ - if (cq_host->ops->clear_set_irqs) - cq_host->ops->clear_set_irqs(mmc, false); - ret = ret ? 0 : -ETIMEDOUT; + if (!ret && !(cmdq_readl(cq_host, CQCTL) & HALT)) { + retries--; + continue; + } else { + /* halt done: re-enable legacy interrupts */ + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, + false); + break; + } + } + return retries ? 0 : -ETIMEDOUT; } else { if (cq_host->ops->set_data_timeout) cq_host->ops->set_data_timeout(mmc, 0xf); From 919917a73cdf911f26371bbc5b42aafc9af29b4d Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 14 Oct 2015 19:04:26 -0700 Subject: [PATCH 394/472] mmc: core: prevent calling ->post_req 2 times if request submit fails Currently we might end up calling ->post_req() 2 times if we failed to submit the request to host driver. This change fixes this issue. Change-Id: I10b681ab955e0a9126b22df68d3601b75328c949 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 779c4b8966a4..90cd7c5a61d1 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1685,8 +1685,7 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, if (host->areq) mmc_post_req(host, host->areq->mrq, 0); - /* Cancel a prepared request if it was not started. */ - if ((err || start_err) && areq) + if (err && areq) mmc_post_req(host, areq->mrq, -EINVAL); if (err) From 24c09c2fdf65cc4a4d16c3c72e27c90d31f2fe21 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 15 Oct 2015 12:16:43 -0700 Subject: [PATCH 395/472] mmc: sdhci-msm: don't bug on PM QoS request counter going negative Currently we are crashing the system if PM QoS request counter goes negative but this doesn't seem to be right way to handle. We should instead skip decrementing this counter once it reaches 0 and just print a dump stack to know the callstack. This change fixes this as mentioned above. Change-Id: I36fb03b1ddf8e04ecc9fe449496b656db84e77d2 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index cf390ff7a73c..246a31ad539a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3217,11 +3217,13 @@ void sdhci_msm_pm_qos_irq_unvote(struct sdhci_host *host, bool async) if (!msm_host->pm_qos_irq.enabled) return; - counter = atomic_dec_return(&msm_host->pm_qos_irq.counter); - if (counter < 0) { - pr_err("%s: counter=%d\n", __func__, counter); - BUG(); + if (atomic_read(&msm_host->pm_qos_irq.counter)) { + counter = atomic_dec_return(&msm_host->pm_qos_irq.counter); + } else { + WARN(1, "attempt to decrement pm_qos_irq.counter when it's 0"); + return; } + if (counter) return; From 1cdc481924aff628fefbba895cf649a2c757edfb Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Fri, 16 Oct 2015 18:33:25 -0700 Subject: [PATCH 396/472] mmc: sdhci-msm: dump cmdq debug ram only for command queue host Currently we are printing the cmdq debug ram contents during the error handling but this code path shouldn't be executed for non cmdq hosts. Change-Id: Ic397e4378c290b604037e69b7df58200810e981c Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 246a31ad539a..c4f6cdd2150c 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2986,7 +2986,8 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) u32 sts = 0; pr_info("----------- VENDOR REGISTER DUMP -----------\n"); - sdhci_msm_cmdq_dump_debug_ram(msm_host); + if (host->cq_host) + sdhci_msm_cmdq_dump_debug_ram(msm_host); pr_info("Data cnt: 0x%08x | Fifo cnt: 0x%08x | Int sts: 0x%08x\n", readl_relaxed(msm_host->core_mem + CORE_MCI_DATA_CNT), @@ -3744,11 +3745,13 @@ static void sdhci_msm_cmdq_init(struct sdhci_host *host, struct sdhci_msm_host *msm_host = pltfm_host->priv; host->cq_host = cmdq_pltfm_init(pdev); - if (IS_ERR(host->cq_host)) + if (IS_ERR(host->cq_host)) { dev_dbg(&pdev->dev, "cmdq-pltfm init: failed: %ld\n", PTR_ERR(host->cq_host)); - else + host->cq_host = NULL; + } else { msm_host->mmc->caps2 |= MMC_CAP2_CMD_QUEUE; + } } #else static void sdhci_msm_cmdq_init(struct sdhci_host *host, From 6aa3b33671feb9dbd6ae3af93c71f8cb631dba96 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Mon, 19 Oct 2015 17:25:22 -0700 Subject: [PATCH 397/472] mmc: cmdq_hci: release runtime PM reference after halt/unhalt Currently we are not releasing the runtime PM reference count after halt/unhalt completion which basically keeps the runtime PM state to active forever. Fix this by releasing the PM reference count on completion of halt/unhalt operation. Change-Id: I28a37917b49acde7f5d75bf9a639d0eb67a1c169 Signed-off-by: Subhash Jadavani --- drivers/mmc/host/cmdq_hci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 4e79db942a04..73a586e72b04 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -798,7 +798,7 @@ static int cmdq_halt(struct mmc_host *mmc, bool halt) break; } } - return retries ? 0 : -ETIMEDOUT; + ret = retries ? 0 : -ETIMEDOUT; } else { if (cq_host->ops->set_data_timeout) cq_host->ops->set_data_timeout(mmc, 0xf); From 4463dc76685b8e2702501502b0c4a4f03ac82e95 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Mon, 19 Oct 2015 12:04:46 -0700 Subject: [PATCH 398/472] mmc: card: kick command queue thread on completion of RPMB operation During RPMB operation, command queue mode would be disabled and we don't exlicitly move back to command queue once the RPMB operation is completed. We expect the command queue thread to switch operating mode (or partition) to command queue before issuing the new transfers in command queue but command queue thread isn't getting scheduled if we don't wake it up explicitly or if some other new transfer get submitted to the queue. Fix this issue by explicitly waking up the command queue thread after the completion of RPMB operation. Change-Id: I68b2f7989d68b51b4810346458e1966d45aee5a2 Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 4b2025fc18d1..a66cae904383 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -922,6 +922,8 @@ idata_free: cmd_done: mmc_blk_put(md); + if (card->cmdq_init) + wake_up(&card->host->cmdq_ctx.wait); return err; } From e3a22cb14423fa5189393214e55ad50db03b7710 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 10 Sep 2015 11:26:59 +0530 Subject: [PATCH 399/472] mmc: core: Fix CQ runtime suspend Fix the timeout issue with mmc_cmdq_halt() that happens in the below scenario - 1. Let us say an RPMB request is processed just before the runtime suspend. The RPMB request will put the controller in HALT state and will put the card in legacy mode. 2. So during runtime suspend, the SW will check for card's state and since it is in legacy mode, it won't call CQ disable host->op. But CQ HW will get reset due to SW reset that gets issued during suspend. 3. If we get another RPMB request, we try to do the runtime resume. But since SW did not invoke CQ disable host->op, it thinks that CQ is still enabled and returns here without enabling CQ. 4. Now the RPMB request tries to put the controller in HALT state but timesout since CQ is still not enabled. Fix this issue by checking for host->card->cmdq_init to determine if the controller is initialized in CQ mode instead of checking for card's state in runtime suspend. This will ensure CQ will be disabled as part of runtime suspend and will be enabled again during runtime resume. Change-Id: I26bf97962d31522a9cb25009c6a048dfff6e4535 Signed-off-by: Sahitya Tummala --- drivers/mmc/core/mmc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 602dfb3a2b1d..65b2581e3195 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2436,7 +2436,7 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (is_suspend) host->dev_status = DEV_SUSPENDING; - if (mmc_card_cmdq(host->card)) { + if (host->card->cmdq_init) { BUG_ON(host->cmdq_ctx.active_reqs); err = mmc_cmdq_halt(host, true); From 71728d8db1686f38faaef0f07326d2aaff753a01 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 18 Apr 2014 13:00:20 +0530 Subject: [PATCH 400/472] mmc: core: fix issue with sleep cmd that follows an RPMB access If the last access to eMMC before runtime/system suspend is an RPMB access, the partition type within EXT_CSD[179] will be set to RPMB. As per specification, the deselect CMD7 and sleep CMD5 are ignored by the card and are treated as illegal commands in this state. This causes eMMC sleep command to timeout and thus fails runtime/system suspend. Hence, switch to default partition config before sending deselect CMD7 and sleep CMD5. CRs-fixed: 630894 Change-Id: I40f3fb590aeba787de8ca3356a8eed5f2780bcc1 Signed-off-by: Sahitya Tummala Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 65b2581e3195..e9827c52e283 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2195,9 +2195,39 @@ static int mmc_sleepawake(struct mmc_host *host, bool sleep) { struct mmc_command cmd = {0}; struct mmc_card *card = host->card; - unsigned int timeout_ms = DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000); + unsigned int timeout_ms; int err; + if (!card) { + pr_err("%s: %s: invalid card\n", mmc_hostname(host), __func__); + return -EINVAL; + } + + timeout_ms = DIV_ROUND_UP(card->ext_csd.sa_timeout, 10000); + if (card->ext_csd.rev >= 3 && + card->part_curr == EXT_CSD_PART_CONFIG_ACC_RPMB) { + u8 part_config = card->ext_csd.part_config; + + /* + * If the last access before suspend is RPMB access, then + * switch to default part config so that sleep command CMD5 + * and deselect CMD7 can be sent to the card. + */ + part_config &= ~EXT_CSD_PART_CONFIG_ACC_MASK; + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_PART_CONFIG, + part_config, + card->ext_csd.part_time); + if (err) { + pr_err("%s: %s: failed to switch to default part config %x\n", + mmc_hostname(host), __func__, part_config); + return err; + } + card->ext_csd.part_config = part_config; + card->part_curr = card->ext_csd.part_config & + EXT_CSD_PART_CONFIG_ACC_MASK; + } + /* Re-tuning can't be done once the card is deselected */ mmc_retune_hold(host); From 603e5ef719b620ebddb2d61681a80dc44ea0c8a9 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Mon, 28 Sep 2015 18:53:18 -0700 Subject: [PATCH 401/472] mmc: cmdq_hci: Add cyclic buffer to keep history of last 32 tasks Keep track of task history for the last 32 tasks and dump it along with registers in case of any error for debugging. The log is of the following format: [index] Task: | Args: of the task descriptor structure. The values need to be decoded accordingly depending on the data or dcmd task descriptor. The last entry index denotes the latest entry in this list. ---- Circular Task History ---- cmdq-host: Last entry index: 1 cmdq-host: [00]DATA Task: 0x0400002f | Args: 0x00d37d18 cmdq-host: [01]DATA Task: 0x0400002f | Args: 0x00d38118 cmdq-host: [02]DCMD Task: 0x0186402f | Args: 0x03200101 Add a debugfs entry to enable/disable this dynamically. It is disabled by default. This applies only to eMMC devices. Usage: echo Y > /sys/kernel/debug/mmcX/cmdq_task_history X - denotes the slot id Change-Id: I6abf29aa928d3d8270405cfc104241043dadfe45 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/debugfs.c | 5 ++++ drivers/mmc/host/cmdq_hci.c | 53 ++++++++++++++++++++++++++++++++++++- drivers/mmc/host/cmdq_hci.h | 8 ++++++ include/linux/mmc/host.h | 1 + 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 7e3d82f658e7..0c55824a449b 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -363,6 +363,11 @@ void mmc_add_host_debugfs(struct mmc_host *host) &host->clk_scaling.skip_clk_scale_freq_update)) goto err_node; + if (!debugfs_create_bool("cmdq_task_history", + S_IRUSR | S_IWUSR, root, + &host->cmdq_thist_enabled)) + goto err_node; + #ifdef CONFIG_MMC_CLKGATE if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR), root, &host->clk_delay)) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 73a586e72b04..de55c0649ea3 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -132,6 +132,31 @@ static void cmdq_clear_set_irqs(struct cmdq_host *cq_host, u32 clear, u32 set) #define DRV_NAME "cmdq-host" +static void cmdq_dump_task_history(struct cmdq_host *cq_host) +{ + int i; + + if (likely(!cq_host->mmc->cmdq_thist_enabled)) + return; + + if (!cq_host->thist) { + pr_err("%s: %s: CMDQ task history buffer not allocated\n", + mmc_hostname(cq_host->mmc), __func__); + return; + } + + pr_err("---- Circular Task History ----\n"); + pr_err(DRV_NAME ": Last entry index: %d", cq_host->thist_idx - 1); + + for (i = 0; i < cq_host->num_slots; i++) { + pr_err(DRV_NAME ": [%02d]%s Task: 0x%08x | Args: 0x%08x\n", i, + (cq_host->thist[i].is_dcmd) ? "DCMD" : "DATA", + lower_32_bits(cq_host->thist[i].task), + upper_32_bits(cq_host->thist[i].task)); + } + pr_err("-------------------------\n"); +} + static void cmdq_dumpregs(struct cmdq_host *cq_host) { struct mmc_host *mmc = cq_host->mmc; @@ -176,6 +201,7 @@ static void cmdq_dumpregs(struct cmdq_host *cq_host) cmdq_readl(cq_host, CQ_VENDOR_CFG)); pr_err(DRV_NAME ": ===========================================\n"); + cmdq_dump_task_history(cq_host); if (cq_host->ops->dump_vendor_regs) cq_host->ops->dump_vendor_regs(mmc); } @@ -252,6 +278,10 @@ static int cmdq_host_alloc_tdl(struct cmdq_host *cq_host) data_size, &cq_host->trans_desc_dma_base, GFP_KERNEL); + cq_host->thist = devm_kzalloc(mmc_dev(cq_host->mmc), + (sizeof(*cq_host->thist) * + cq_host->num_slots), + GFP_KERNEL); if (!cq_host->desc_base || !cq_host->trans_desc_base) return -ENOMEM; @@ -526,6 +556,26 @@ static int cmdq_prep_tran_desc(struct mmc_request *mrq, return 0; } +static void cmdq_log_task_desc_history(struct cmdq_host *cq_host, u64 task, + bool is_dcmd) +{ + if (likely(!cq_host->mmc->cmdq_thist_enabled)) + return; + + if (!cq_host->thist) { + pr_err("%s: %s: CMDQ task history buffer not allocated\n", + mmc_hostname(cq_host->mmc), __func__); + return; + } + + if (cq_host->thist_idx >= cq_host->num_slots) + cq_host->thist_idx = 0; + + cq_host->thist[cq_host->thist_idx].is_dcmd = is_dcmd; + memcpy(&cq_host->thist[cq_host->thist_idx++].task, + &task, cq_host->task_desc_len); +} + static void cmdq_prep_dcmd_desc(struct mmc_host *mmc, struct mmc_request *mrq) { @@ -565,7 +615,7 @@ static void cmdq_prep_dcmd_desc(struct mmc_host *mmc, mrq->cmd->opcode, timing, resp_type); dataddr = (__le64 __force *)(desc + 4); dataddr[0] = cpu_to_le64((u64)mrq->cmd->arg); - + cmdq_log_task_desc_history(cq_host, *task_desc, true); } static void cmdq_pm_qos_vote(struct sdhci_host *host, struct mmc_request *mrq) @@ -623,6 +673,7 @@ static int cmdq_request(struct mmc_host *mmc, struct mmc_request *mrq) cmdq_prep_task_desc(mrq, &data, 1, (mrq->cmdq_req->cmdq_req_flags & QBR)); *task_desc = cpu_to_le64(data); + cmdq_log_task_desc_history(cq_host, *task_desc, false); err = cmdq_prep_tran_desc(mrq, cq_host, tag); if (err) { diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index bda6fdc1ffb5..a17c5c29ccb8 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -144,6 +144,11 @@ #define CQ_VENDOR_CFG 0x100 #define CMDQ_SEND_STATUS_TRIGGER (1 << 31) +struct task_history { + u64 task; + bool is_dcmd; +}; + struct cmdq_host { const struct cmdq_host_ops *ops; void __iomem *mmio; @@ -183,6 +188,9 @@ struct cmdq_host { dma_addr_t desc_dma_base; dma_addr_t trans_desc_dma_base; + struct task_history *thist; + u8 thist_idx; + struct completion halt_comp; struct mmc_request **mrq_slot; void *private; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 3b028e566282..959713837423 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -568,6 +568,7 @@ struct mmc_host { enum dev_state dev_status; bool wakeup_on_idle; struct mmc_cmdq_context_info cmdq_ctx; + bool cmdq_thist_enabled; /* * several cmdq supporting host controllers are extensions * of legacy controllers. This variable can be used to store From 2304056223c8b1b74c54b0d69cd2a41f5bdaa37f Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 20 Oct 2015 18:01:53 -0700 Subject: [PATCH 402/472] mmc: core/card: maintain clock gating reference count integrity Clock gating logic maintains the reference count which gets incremented with *hold* API call and get decremented with *release* API call. If we want to make sure that clocks are enabled during some operation, we should call *hold* API before the operation and then call *release* API after the operation is finished. So *hold* and *release* should be in pair and called exactly once per operation, any violation of these rules may put the reference count out of sync and can cause issues (like unclocked register access). This change fixes the places where this can go out of sync. Change-Id: I2d784cc31027a551a466beec63ee8cd0a1f67af7 Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 2 +- drivers/mmc/core/mmc.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index a66cae904383..1ca30a1bb75d 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2989,7 +2989,7 @@ static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) err = mmc_cmdq_halt(host, true); if (err) { pr_err("%s: halt: failed: %d\n", __func__, err); - return; + goto out; } /* disable CQ mode in card */ diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index e9827c52e283..bf6498db2f2e 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1541,8 +1541,7 @@ static int mmc_select_cmdq(struct mmc_card *card) mmc_card_clr_cmdq(card); ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_CMDQ, 0, card->ext_csd.generic_cmd6_time); - if (ret) - goto out; + goto out; } mmc_host_clk_release(card->host); From fb4ab70fc2db99bf8319dde6e046de56dd78b1e0 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 22 Oct 2015 17:53:30 -0700 Subject: [PATCH 403/472] mmc: sdhci-msm: dump ICE registers also on error In addition to dumping all the SDCC vendor specific registers, also dump the ICE registers that helps in debugging crypto related errors. Change-Id: I804b0cfd77dec21d45016aafbbb001dafb050241 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/host/sdhci-msm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index c4f6cdd2150c..620391fe7010 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3032,6 +3032,7 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) if (host->is_crypto_en) { sdhci_msm_ice_get_status(host, &sts); pr_info("%s: ICE status %x\n", mmc_hostname(host->mmc), sts); + sdhci_msm_ice_print_regs(host); } } From b4b3041ed65694ab5005a2d53ac36778d25bcca6 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Fri, 16 Oct 2015 14:38:28 +0530 Subject: [PATCH 404/472] mmc: sdhci-msm: add the ability to fake 3.0v support for SDIO devices As SDIO spec doesn't allow advertisement of 1.8v support, some SDIO devices advertise support of only 3.0v even though they support 1.8v as well. sdhc3 host controller only supports 1.8v and rejects the initialization of SDIO devices that advertise 3.0v support. This change adds fake support for 3.0v to sdhc host controller. This will allow initialization of SDIO devices that supports 1.8v but advertise 3.0v support. Change-Id: I5a98c54ad4998e6439f83081628c9c083e95bbf0 Signed-off-by: Pavan Anamula --- Documentation/devicetree/bindings/mmc/sdhci-msm.txt | 2 ++ drivers/mmc/host/sdhci-msm.c | 11 +++++++++++ drivers/mmc/host/sdhci-msm.h | 1 + 3 files changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 6c8c13f335df..39f22c3e1ba5 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -65,6 +65,8 @@ Optional Properties: when load is high (performance mode) and the second is for low loads (power saving mode). These values will be used for cpu group voting for command-queueing mode or legacy respectively. + - qcom,core_3_0v_support: an optional property that is used to fake + 3.0V support for SDIO devices. In the following, can be vdd (flash core voltage) or vdd-io (I/O voltage). - qcom,-always-on - specifies whether supply should be kept "on" always. diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 620391fe7010..1b7185a1a56b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1738,6 +1738,9 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, sdhci_msm_pm_qos_parse(dev, pdata); + if (of_get_property(np, "qcom,core_3_0v_support", NULL)) + pdata->core_3_0v_support = true; + return pdata; out: return NULL; @@ -3724,6 +3727,14 @@ static void sdhci_set_default_hw_caps(struct sdhci_msm_host *msm_host, (minor == 0x49))) msm_host->use_14lpp_dll = true; + /* Fake 3.0V support for SDIO devices which requires such voltage */ + if (msm_host->pdata->core_3_0v_support) { + caps |= CORE_3_0V_SUPPORT; + writel_relaxed( + (readl_relaxed(host->ioaddr + SDHCI_CAPABILITIES) | + caps), host->ioaddr + CORE_VENDOR_SPEC_CAPABILITIES0); + } + if ((major == 1) && (minor >= 0x49)) msm_host->rclk_delay_fix = true; /* diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 0a90409a7409..01c07a7b881e 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -151,6 +151,7 @@ struct sdhci_msm_pltfm_data { u32 ice_clk_max; u32 ice_clk_min; struct sdhci_msm_pm_qos_data pm_qos_data; + bool core_3_0v_support; }; struct sdhci_msm_bus_vote { From d98585de729b440866fd3d39bb1f00d238274cf4 Mon Sep 17 00:00:00 2001 From: Dinesh K Garg Date: Mon, 26 Oct 2015 11:40:37 -0700 Subject: [PATCH 405/472] crypto: ice: Make ICE init & reset API synchronous ICE init & reset can be synchronous now because ICE does not need to go to secure side for any ICE configuration. This would simplify interface and make call more efficient. Change-Id: I7aa4e2d3ba3383d25758b21b8ae261a0220f35f9 Signed-off-by: Dinesh K Garg Signed-off-by: Subhash Jadavani Signed-off-by: Gilad Broner [subhashj@codeaurora.org: fixed merge conflicts, dropped changes to ICE and UFS driver as they are already present] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm-ice.c | 67 ++++---------------------------- drivers/mmc/host/sdhci-msm-ice.h | 1 - drivers/mmc/host/sdhci-msm.h | 1 - 3 files changed, 7 insertions(+), 62 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm-ice.c b/drivers/mmc/host/sdhci-msm-ice.c index 7b9516e7d451..955096b2d519 100644 --- a/drivers/mmc/host/sdhci-msm-ice.c +++ b/drivers/mmc/host/sdhci-msm-ice.c @@ -13,19 +13,6 @@ #include "sdhci-msm-ice.h" -static void sdhci_msm_ice_success_cb(void *host_ctrl, - enum ice_event_completion evt) -{ - struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)host_ctrl; - - if ((msm_host->ice.state == SDHCI_MSM_ICE_STATE_DISABLED && - evt == ICE_INIT_COMPLETION) || (msm_host->ice.state == - SDHCI_MSM_ICE_STATE_SUSPENDED && evt == ICE_RESUME_COMPLETION)) - msm_host->ice.state = SDHCI_MSM_ICE_STATE_ACTIVE; - - complete(&msm_host->ice.async_done); -} - static void sdhci_msm_ice_error_cb(void *host_ctrl, enum ice_error_code evt) { struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)host_ctrl; @@ -35,8 +22,6 @@ static void sdhci_msm_ice_error_cb(void *host_ctrl, enum ice_error_code evt) if (msm_host->ice.state == SDHCI_MSM_ICE_STATE_ACTIVE) msm_host->ice.state = SDHCI_MSM_ICE_STATE_DISABLED; - - complete(&msm_host->ice.async_done); } static struct platform_device *sdhci_msm_ice_get_pdevice(struct device *dev) @@ -117,34 +102,21 @@ int sdhci_msm_ice_init(struct sdhci_host *host) struct sdhci_msm_host *msm_host = pltfm_host->priv; int err = 0; - init_completion(&msm_host->ice.async_done); if (msm_host->ice.vops->config) { err = msm_host->ice.vops->init(msm_host->ice.pdev, msm_host, - sdhci_msm_ice_success_cb, sdhci_msm_ice_error_cb); if (err) { pr_err("%s: ice init err %d\n", mmc_hostname(host->mmc), err); - return err; + sdhci_msm_ice_print_regs(host); + goto out; } + msm_host->ice.state = SDHCI_MSM_ICE_STATE_ACTIVE; } - if (!wait_for_completion_timeout(&msm_host->ice.async_done, - msecs_to_jiffies(SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS))) { - pr_err("%s: ice init timedout after %d ms\n", - mmc_hostname(host->mmc), - SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); - sdhci_msm_ice_print_regs(host); - return -ETIMEDOUT; - } - - if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { - pr_err("%s: ice is in invalid state %d\n", - mmc_hostname(host->mmc), msm_host->ice.state); - return -EINVAL; - } - return 0; +out: + return err; } void sdhci_msm_ice_cfg_reset(struct sdhci_host *host, u32 slot) @@ -244,26 +216,16 @@ int sdhci_msm_ice_reset(struct sdhci_host *host) return -EINVAL; } - init_completion(&msm_host->ice.async_done); - if (msm_host->ice.vops->reset) { err = msm_host->ice.vops->reset(msm_host->ice.pdev); if (err) { pr_err("%s: ice reset failed %d\n", mmc_hostname(host->mmc), err); + sdhci_msm_ice_print_regs(host); return err; } } - if (!wait_for_completion_timeout(&msm_host->ice.async_done, - msecs_to_jiffies(SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS))) { - pr_err("%s: ice reset timedout after %d ms\n", - mmc_hostname(host->mmc), - SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); - sdhci_msm_ice_print_regs(host); - return -ETIMEDOUT; - } - if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { pr_err("%s: ice is in invalid state after reset %d\n", mmc_hostname(host->mmc), msm_host->ice.state); @@ -285,8 +247,6 @@ int sdhci_msm_ice_resume(struct sdhci_host *host) return -EINVAL; } - init_completion(&msm_host->ice.async_done); - if (msm_host->ice.vops->resume) { err = msm_host->ice.vops->resume(msm_host->ice.pdev); if (err) { @@ -296,20 +256,7 @@ int sdhci_msm_ice_resume(struct sdhci_host *host) } } - if (!wait_for_completion_timeout(&msm_host->ice.async_done, - msecs_to_jiffies(SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS))) { - pr_err("%s: ice resume timedout after %d ms\n", - mmc_hostname(host->mmc), - SDHCI_MSM_ICE_COMPLETION_TIMEOUT_MS); - sdhci_msm_ice_print_regs(host); - return -ETIMEDOUT; - } - - if (msm_host->ice.state != SDHCI_MSM_ICE_STATE_ACTIVE) { - pr_err("%s: ice is in invalid state after resume %d\n", - mmc_hostname(host->mmc), msm_host->ice.state); - return -EINVAL; - } + msm_host->ice.state = SDHCI_MSM_ICE_STATE_ACTIVE; return 0; } diff --git a/drivers/mmc/host/sdhci-msm-ice.h b/drivers/mmc/host/sdhci-msm-ice.h index a3007a623b78..1c4266330290 100644 --- a/drivers/mmc/host/sdhci-msm-ice.h +++ b/drivers/mmc/host/sdhci-msm-ice.h @@ -17,7 +17,6 @@ #include #include -#include #include #include diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 01c07a7b881e..1f92a285893d 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -166,7 +166,6 @@ struct sdhci_msm_bus_vote { struct sdhci_msm_ice_data { struct qcom_ice_variant_ops *vops; - struct completion async_done; struct platform_device *pdev; int state; }; From 6f89070961aaf1e3dbb8a58cdedf46e690f94516 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 29 Oct 2015 20:18:45 +0530 Subject: [PATCH 406/472] mmc: sdhci-msm: parse and configure TLMM iomem resource Some host controllers may need additional TLMM registers to be configured to enable the IO lines connected to it. Change-Id: Ic334ce653bf13fef5969f08e19f6202377b8fd2e Signed-off-by: Sahitya Tummala --- .../devicetree/bindings/mmc/sdhci-msm.txt | 1 + drivers/mmc/host/sdhci-msm.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 39f22c3e1ba5..0d62b6458e83 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -7,6 +7,7 @@ Required properties: - reg : should contain SDHC, SD Core register map. - reg-names : indicates various resources passed to driver (via reg proptery) by name. Required "reg-names" are "hc_mem" and "core_mem" + optional ones are "tlmm_mem" - interrupts : should contain SDHC interrupts. - interrupt-names : indicates interrupts passed to driver (via interrupts property) by name. Required "interrupt-names" are "hc_irq" and "pwr_irq". diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 1b7185a1a56b..f632cc11995e 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3804,6 +3804,8 @@ static int sdhci_msm_probe(struct platform_device *pdev) int ret = 0, dead = 0; u16 host_version; u32 irq_status, irq_ctl; + struct resource *tlmm_memres = NULL; + void __iomem *tlmm_mem; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -4001,6 +4003,22 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto vreg_deinit; } + tlmm_memres = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "tlmm_mem"); + if (tlmm_memres) { + tlmm_mem = devm_ioremap(&pdev->dev, tlmm_memres->start, + resource_size(tlmm_memres)); + + if (!tlmm_mem) { + dev_err(&pdev->dev, "Failed to remap tlmm registers\n"); + ret = -ENOMEM; + goto vreg_deinit; + } + writel_relaxed(readl_relaxed(tlmm_mem) | 0x2, tlmm_mem); + dev_dbg(&pdev->dev, "tlmm reg %pa value 0x%08x\n", + &tlmm_memres->start, readl_relaxed(tlmm_mem)); + } + /* * Reset the vendor spec register to power on reset state. */ From 98afc17278b691b950dd003c3a0d7958de78aec7 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Wed, 28 Oct 2015 22:20:24 +0530 Subject: [PATCH 407/472] mmc: core: Card specific custom settings for SDIO Add quirk to modify custom settings for QCA6574 and QCA9377 cards. Change-Id: I05e70efa71a8b8b931dfff758194af6220a8bc46 Signed-off-by: Pavan Anamula Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/quirks.c | 22 +++++++++++++++++ drivers/mmc/core/sdio.c | 51 +++++++++++++++++++++++++++++++++++++++ include/linux/mmc/card.h | 12 +++++++++ 3 files changed, 85 insertions(+) diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index 1fb9fe1838c7..071adc101158 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -63,6 +63,23 @@ #define SDIO_DEVICE_ID_MSM_QCA_AR6004_2 0x401 #endif +#ifndef SDIO_VENDOR_ID_QCA6574 +#define SDIO_VENDOR_ID_QCA6574 0x271 +#endif + +#ifndef SDIO_DEVICE_ID_QCA6574 +#define SDIO_DEVICE_ID_QCA6574 0x50a +#endif + +#ifndef SDIO_VENDOR_ID_QCA9377 +#define SDIO_VENDOR_ID_QCA9377 0x271 +#endif + +#ifndef SDIO_DEVICE_ID_QCA9377 +#define SDIO_DEVICE_ID_QCA9377 0x701 +#endif + + /* * This hook just adds a quirk for all sdio devices */ @@ -109,6 +126,11 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8797_F0, add_quirk, MMC_QUIRK_BROKEN_IRQ_POLLING), + SDIO_FIXUP(SDIO_VENDOR_ID_QCA6574, SDIO_DEVICE_ID_QCA6574, + add_quirk, MMC_QUIRK_QCA6574_SETTINGS), + + SDIO_FIXUP(SDIO_VENDOR_ID_QCA9377, SDIO_DEVICE_ID_QCA9377, + add_quirk, MMC_QUIRK_QCA9377_SETTINGS), END_FIXUP }; diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index b3b1526e5609..27ded39682f7 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -222,6 +222,54 @@ out: return ret; } +static void sdio_enable_vendor_specific_settings(struct mmc_card *card) +{ + int ret; + u8 settings; + + if (mmc_enable_qca6574_settings(card) || + mmc_enable_qca9377_settings(card)) { + ret = mmc_io_rw_direct(card, 1, 0, 0xF2, 0x0F, NULL); + if (ret) { + pr_crit("%s: failed to write to fn 0xf2 %d\n", + mmc_hostname(card->host), ret); + goto out; + } + + ret = mmc_io_rw_direct(card, 0, 0, 0xF1, 0, &settings); + if (ret) { + pr_crit("%s: failed to read fn 0xf1 %d\n", + mmc_hostname(card->host), ret); + goto out; + } + + settings |= 0x80; + ret = mmc_io_rw_direct(card, 1, 0, 0xF1, settings, NULL); + if (ret) { + pr_crit("%s: failed to write to fn 0xf1 %d\n", + mmc_hostname(card->host), ret); + goto out; + } + + ret = mmc_io_rw_direct(card, 0, 0, 0xF0, 0, &settings); + if (ret) { + pr_crit("%s: failed to read fn 0xf0 %d\n", + mmc_hostname(card->host), ret); + goto out; + } + + settings |= 0x20; + ret = mmc_io_rw_direct(card, 1, 0, 0xF0, settings, NULL); + if (ret) { + pr_crit("%s: failed to write to fn 0xf0 %d\n", + mmc_hostname(card->host), ret); + goto out; + } + } +out: + return; +} + static int sdio_enable_wide(struct mmc_card *card) { int ret; @@ -517,6 +565,9 @@ static int sdio_set_bus_speed_mode(struct mmc_card *card) if (err) return err; + /* Vendor specific settings based on card quirks */ + sdio_enable_vendor_specific_settings(card); + speed &= ~SDIO_SPEED_BSS_MASK; speed |= bus_speed; err = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_SPEED, speed, NULL); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 4f49a254d3f3..6fba3afcc2fb 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -384,6 +384,8 @@ struct mmc_card { #define MMC_QUIRK_BROKEN_HPI (1 << 14) /* For devices which gets */ /* broken due to HPI feature */ #define MMC_QUIRK_CACHE_DISABLE (1 << 14) /* prevent cache enable */ +#define MMC_QUIRK_QCA6574_SETTINGS (1 << 15) /* QCA6574 card settings*/ +#define MMC_QUIRK_QCA9377_SETTINGS (1 << 16) /* QCA9377 card settings*/ unsigned int erase_size; /* erase size in sectors */ @@ -678,6 +680,16 @@ static inline bool mmc_card_configured_auto_bkops(const struct mmc_card *c) return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_AUTO_EN; } +static inline bool mmc_enable_qca6574_settings(const struct mmc_card *c) +{ + return c->quirks & MMC_QUIRK_QCA6574_SETTINGS; +} + +static inline bool mmc_enable_qca9377_settings(const struct mmc_card *c) +{ + return c->quirks & MMC_QUIRK_QCA9377_SETTINGS; +} + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) From 3b0520abdd56e26dd3cf26f09083a0d3dc60fa2a Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 27 Oct 2015 15:43:11 -0700 Subject: [PATCH 408/472] mmc: card: fix race with shutdown handler There could be 2 different race conditions possible with current shutdown handler: 1. Shutdown handler sees that there is no request in queue but mmc-cmdqd might have fetched the request from queue and about to issue it to driver. 2. Shutdown handler keeps looping in the while(1) loop as it sees request being pending in the request queue. But mmc-cmdqd thread doesn't wake up to fetch the requests from the request queue. mmc-cmdqd thread isn't waking up because shutdown path has stopped queue and request came into the queue after that. Once queue is stopped, block layer won't invoke the queue's ->request_fn() callback to notify driver about pending request. Remedy to fix both these race condition is simple. In shutdown handler, make sure that we drain (& complete) all the outstanding requests from the queue and then don't allow any new requests to be queued. Block layer API blk_cleanup_queue() precisely does what we want and this change basically use the same API in shutdown handler. Change-Id: I761ba6a2e2974d955bb72ff993b1cc2c32c9ec29 Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 2 -- drivers/mmc/card/queue.c | 37 ++++++------------------------------- drivers/mmc/card/queue.h | 1 - 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 1ca30a1bb75d..bfd877cdb6e2 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3204,8 +3204,6 @@ out: if (!ctx_info->active_reqs) wake_up_interruptible(&host->cmdq_ctx.queue_empty_wq); - if (blk_queue_stopped(mq->queue) && !ctx_info->active_reqs) - complete(&mq->cmdq_shutdown_complete); return; } diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 05f456b0d1c1..fdbfdc0f4e3a 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -655,7 +655,6 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work); - init_completion(&mq->cmdq_shutdown_complete); init_completion(&mq->cmdq_pending_req_done); blk_queue_rq_timed_out(mq->queue, mmc_cmdq_rq_timed_out); @@ -704,7 +703,6 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) int rc = 0; struct mmc_card *card = mq->card; struct request *req; - #define SLEEP_TIME_BETWEEN_BLK_REQ_CHECK 100 /* microseconds */ if (card->cmdq_init && blk_queue_tagged(q)) { struct mmc_host *host = card->host; @@ -712,44 +710,21 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) if (test_and_set_bit(MMC_QUEUE_SUSPENDED, &mq->flags)) goto out; - spin_lock_irqsave(q->queue_lock, flags); - blk_stop_queue(q); - spin_unlock_irqrestore(q->queue_lock, flags); - - wake_up(&host->cmdq_ctx.wait); - if (wait) { - while (1) { - spin_lock_irqsave(q->queue_lock, flags); - req = blk_peek_request(q); - spin_unlock_irqrestore(q->queue_lock, flags); - - if (!req) - break; - - /* sleep for some time before rechecking */ - usleep_range(SLEEP_TIME_BETWEEN_BLK_REQ_CHECK, - SLEEP_TIME_BETWEEN_BLK_REQ_CHECK + 10); - } - - /* Wait for already issued requests to complete */ - if (host->cmdq_ctx.active_reqs) - wait_for_completion( - &mq->cmdq_shutdown_complete); - + blk_cleanup_queue(q); mq->cmdq_shutdown(mq); } else { spin_lock_irqsave(q->queue_lock, flags); + blk_stop_queue(q); + wake_up(&host->cmdq_ctx.wait); req = blk_peek_request(q); - spin_unlock_irqrestore(q->queue_lock, flags); - - if (req || host->cmdq_ctx.active_reqs) { + if (req || mq->cmdq_req_peeked || + host->cmdq_ctx.active_reqs) { clear_bit(MMC_QUEUE_SUSPENDED, &mq->flags); - spin_lock_irqsave(q->queue_lock, flags); blk_start_queue(q); - spin_unlock_irqrestore(q->queue_lock, flags); rc = -EBUSY; } + spin_unlock_irqrestore(q->queue_lock, flags); } goto out; diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index e67d0546346a..964262f0eb79 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -70,7 +70,6 @@ struct mmc_queue { int num_wr_reqs_to_start_packing; bool no_pack_for_random; struct work_struct cmdq_err_work; - struct completion cmdq_shutdown_complete; struct completion cmdq_pending_req_done; struct request *cmdq_req_peeked; From 6ae860507e2b7c58d9b44b608c34a3a6acf7a2c9 Mon Sep 17 00:00:00 2001 From: Pavan Anamula Date: Thu, 29 Oct 2015 23:22:12 +0530 Subject: [PATCH 409/472] mmc: sdhci-msm: disable runtime pm and clock gating for SDIO Disable power management features for SDIO as the current code is not ready to enable it. Change-Id: I234ebf6cc46d2a3b70bf9ba3f94b4b1abc1acd44 Signed-off-by: Pavan Anamula --- drivers/mmc/core/host.c | 9 +++++++-- drivers/mmc/host/sdhci-msm.c | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index fe1d90357653..c4bd0ecdf761 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -194,8 +194,13 @@ bool mmc_host_may_gate_card(struct mmc_card *card) * SDIO3.0 card allows the clock to be gated off so check if * that is the case or not. */ - if (mmc_card_sdio(card) && card->cccr.async_intr_sup) - return true; + if (mmc_card_sdio(card) && card->cccr.async_intr_sup) { + if (mmc_enable_qca6574_settings(card) || + mmc_enable_qca9377_settings(card)) + return false; + else + return true; + } /* * Don't gate SDIO cards! These need to be clocked at all times diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f632cc11995e..4848c26f4a1f 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -4323,6 +4323,12 @@ static int sdhci_msm_runtime_suspend(struct device *dev) ktime_t start = ktime_get(); int ret; + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { + if (mmc_enable_qca6574_settings(host->mmc->card) || + mmc_enable_qca9377_settings(host->mmc->card)) + return 0; + } + disable_irq(host->irq); disable_irq(msm_host->pwr_irq); @@ -4355,6 +4361,13 @@ static int sdhci_msm_runtime_resume(struct device *dev) ktime_t start = ktime_get(); int ret; + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { + if (mmc_enable_qca6574_settings(host->mmc->card) || + mmc_enable_qca9377_settings(host->mmc->card)) + return 0; + } + + if (host->is_crypto_en) { ret = sdhci_msm_enable_controller_clock(host); if (ret) { From 5dad09326ec4dc0a1b9c636297c1661a9f7efbe7 Mon Sep 17 00:00:00 2001 From: Krishna Konda Date: Fri, 23 Oct 2015 11:43:02 -0700 Subject: [PATCH 410/472] mmc: sdhci-msm: fix compilation errors The current code has some compilation errors when compiling for single core configurations. This fixes those errors. Change-Id: Idd1d1bab343cf5cf1e35f229d913d16a7854f358 Signed-off-by: Krishna Konda Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 4848c26f4a1f..55d6537f3e76 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1432,6 +1432,15 @@ out: return ret; } +#ifdef CONFIG_SMP +static inline void parse_affine_irq(struct sdhci_msm_pltfm_data *pdata) +{ + pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_IRQ; +} +#else +static inline void parse_affine_irq(struct sdhci_msm_pltfm_data *pdata) { } +#endif + static int sdhci_msm_pm_qos_parse_irq(struct device *dev, struct sdhci_msm_pltfm_data *pdata) { @@ -1445,7 +1454,7 @@ static int sdhci_msm_pm_qos_parse_irq(struct device *dev, pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_CORES; if (!of_property_read_string(np, "qcom,pm-qos-irq-type", &str) && !strcmp(str, "affine_irq")) { - pdata->pm_qos_data.irq_req_type = PM_QOS_REQ_AFFINE_IRQ; + parse_affine_irq(pdata); } /* must specify cpu for "affine_cores" type */ @@ -3299,6 +3308,17 @@ out: return count; } +#ifdef CONFIG_SMP +static inline void set_affine_irq(struct sdhci_msm_host *msm_host, + struct sdhci_host *host) +{ + msm_host->pm_qos_irq.req.irq = host->irq; +} +#else +static inline void set_affine_irq(struct sdhci_msm_host *msm_host, + struct sdhci_host *host) { } +#endif + void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -3316,8 +3336,9 @@ void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host) atomic_set(&msm_host->pm_qos_irq.counter, 0); msm_host->pm_qos_irq.req.type = msm_host->pdata->pm_qos_data.irq_req_type; - if (msm_host->pm_qos_irq.req.type == PM_QOS_REQ_AFFINE_IRQ) - msm_host->pm_qos_irq.req.irq = host->irq; + if ((msm_host->pm_qos_irq.req.type != PM_QOS_REQ_AFFINE_CORES) && + (msm_host->pm_qos_irq.req.type != PM_QOS_REQ_ALL_CORES)) + set_affine_irq(msm_host, host); else cpumask_copy(&msm_host->pm_qos_irq.req.cpus_affine, cpumask_of(msm_host->pdata->pm_qos_data.irq_cpu)); From eeff0e83b0c9f070cac9af5c5f76054f635ae987 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 21 Oct 2015 15:47:04 +0530 Subject: [PATCH 411/472] mmc: sdhci-msm: Kconfig: select devfreq ondemand for sdhci-msm SDHCI-MSM platform is using devfreq ondemand governor. Select devfreq governor for SDHCI-MSM platform. Change-Id: I0fefe7eb9a578b5d897e7f4258f7c0221950509e Signed-off-by: Ritesh Harjani --- drivers/mmc/host/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index b167ef4aa865..f41832252761 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -408,6 +408,8 @@ config MMC_SDHCI_MSM tristate "Qualcomm Technologies, Inc. SDHCI Controller Support" depends on ARCH_QCOM || ARCH_MSM || (ARM && COMPILE_TEST) depends on MMC_SDHCI_PLTFM + select PM_DEVFREQ + select DEVFREQ_GOV_SIMPLE_ONDEMAND help This selects the Secure Digital Host Controller Interface (SDHCI) support present in Qualcomm Technologies, Inc. SOCs. The controller From 0c86067ea8daf55a6a5b7f136bc930f71a2bdf76 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 3 Nov 2015 19:03:41 -0800 Subject: [PATCH 412/472] mmc: block: ensure CMDQ is empty before queuing cache flush Some devices might stop responding to new commands if device cache flush is queued to host controller while host controller is still processing outstanding requests. To workaround this issue, we are making the cmdq thread to wait for all the oustanding requests to be finished before queuing cache flush command on host controller. Change-Id: I15387734f51ca4cadfc9e11270f14d8a0806a00f Signed-off-by: Subhash Jadavani [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 15 +++++++++++++++ include/linux/mmc/card.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index bfd877cdb6e2..6dad4673090f 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3494,6 +3494,19 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) else ret = mmc_blk_cmdq_issue_discard_rq(mq, req); } else if (cmd_flags & REQ_FLUSH) { + if (card->quirks & + MMC_QUIRK_CMDQ_EMPTY_BEFORE_FLUSH) { + ret = wait_event_interruptible( + card->host->cmdq_ctx.queue_empty_wq, + (!card->host->cmdq_ctx.active_reqs)); + if (ret) { + pr_err("%s: failed while waiting for the CMDQ to be empty %s err (%d)\n", + mmc_hostname(card->host), + __func__, ret); + BUG_ON(1); + } + } + ret = mmc_blk_cmdq_issue_flush_rq(mq, req); } else { ret = mmc_blk_cmdq_issue_rw_rq(mq, req); @@ -3961,6 +3974,8 @@ static const struct mmc_fixup blk_fixups[] = MMC_QUIRK_BLK_NO_CMD23), MMC_FIXUP("MMC32G", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, MMC_QUIRK_BLK_NO_CMD23), + MMC_FIXUP(CID_NAME_ANY, CID_MANFID_TOSHIBA, CID_OEMID_ANY, + add_quirk_mmc, MMC_QUIRK_CMDQ_EMPTY_BEFORE_FLUSH), /* * Some Micron MMC cards needs longer data read timeout than diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 6fba3afcc2fb..2574e8af91fe 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -388,6 +388,9 @@ struct mmc_card { #define MMC_QUIRK_QCA9377_SETTINGS (1 << 16) /* QCA9377 card settings*/ +/* Make sure CMDQ is empty before queuing cache flush */ +#define MMC_QUIRK_CMDQ_EMPTY_BEFORE_FLUSH (1 << 17) + unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ unsigned int pref_erase; /* in sectors */ From f0623b400f017f8e45e475456689796e60d891b0 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 23 Oct 2015 16:46:29 -0700 Subject: [PATCH 413/472] mmc: sdhci: Panic after dumping SDHC registers eMMC CMDQ is pretty latest feature and we may continue to see issues due to HW & SW immaturity. To debug these issues, we should have the host controller register dumps along with current SW state from rampdumps. This change adds the BUG_ON to crash the system in case of eMMC related command timeouts. Change-Id: Ib832aef6eb9ef3941fbe9e73852929c835e97847 Signed-off-by: Venkat Gopalakrishnan [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 4 +++- drivers/mmc/host/sdhci.c | 2 ++ drivers/mmc/host/sdhci.h | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 55d6537f3e76..d02ef3f22a6d 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3895,8 +3895,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) goto pltfm_free; } - if (ret <= 2) + if (ret <= 2) { sdhci_slot[ret-1] = msm_host; + host->slot_no = ret; + } msm_host->pdata = sdhci_msm_populate_pdata(&pdev->dev, msm_host); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 9761cd7f5a80..2cfa1d0249a5 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -170,6 +170,8 @@ static void sdhci_dumpregs(struct sdhci_host *host) host->ops->dump_vendor_regs(host); sdhci_dump_state(host); pr_info(DRIVER_NAME ": ===========================================\n"); + if (host->slot_no == 1) + BUG_ON(1); } /*****************************************************************************\ diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 489aa2572692..1ec9e9210951 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -613,6 +613,8 @@ struct sdhci_host { ktime_t reset_wa_t; /* time when the reset workaround is applied */ int reset_wa_cnt; /* total number of times workaround is used */ + int slot_no; + unsigned long private[0] ____cacheline_aligned; }; From f9d5b446e37d67d6f05d8674e0005a6c428c5c2b Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 10 Nov 2015 15:13:22 -0800 Subject: [PATCH 414/472] mmc: core: kick cmdq thread after suspend eMMC runtime suspend first puts the CMDQ to halt and then sets the card state to suspended after sending out sleep command. If new requests get queued after halting the CMDQ but before the card state has changed to suspended then command queue thread will not be woken up again until some new requests are queued to request queue. And if new request gets queued, we will remain in this state forever. Fix this issue by kicking the CMDQ thread after the suspend completion. Change-Id: I37a03d9a75acf2ab3ebda57da87e246b449abf18 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index bf6498db2f2e..2f113b52e35e 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2511,6 +2511,11 @@ out: host->dev_status = DEV_UNKNOWN; else if (is_suspend) host->dev_status = DEV_SUSPENDED; + + /* Kick CMDQ thread to process any requests came in while suspending */ + if (host->card->cmdq_init) + wake_up(&host->cmdq_ctx.wait); + mmc_release_host(host); return err; } From 6c8e3e70c92fdfe2813817c401ec05f2bc6c6a9d Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Fri, 13 Nov 2015 12:15:53 -0800 Subject: [PATCH 415/472] mmc: block: workaround for timeout issue with some vendor devices Commit 66a7393a3ba9685d1eddfbce72e3ef8f4848f19f ("mmc: block: ensure CMDQ is empty before queuing cache flush") added a workaround for particular vendor's eMMC devices. Workaround was to wait for all the outstanding requests to finish up before queuing the flush request. Now detailed root cause analysis from vendor shows that original issue can happen only if DCMD command is sent to device too quickly (within less than 6 microseconds) after completion of previous small sector (less than 8 sectors) read operations. Hence with this change, we are fine tuning the previous workaround such that it would almost have no impact on the storage benchmark performance numbers. Change-Id: I1df1c5d7bbcd7b526236651077b7dade2626cb30 Signed-off-by: Subhash Jadavani [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 50 +++++++++++++++++++++++++++++----------- include/linux/mmc/card.h | 6 +++-- include/linux/mmc/host.h | 1 + 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 6dad4673090f..120e4577b678 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2912,6 +2912,15 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) mc_rq = mmc_blk_cmdq_rw_prep(active_mqrq, mq); ret = mmc_blk_cmdq_start_req(card->host, mc_rq); + + if (!ret && (card->quirks & MMC_QUIRK_CMDQ_EMPTY_BEFORE_DCMD)) { + unsigned int sectors = blk_rq_sectors(req); + + if (((sectors > 0) && (sectors < 8)) + && (rq_data_dir(req) == READ)) + host->cmdq_ctx.active_small_sector_read_reqs++; + } + return ret; } @@ -3487,6 +3496,32 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) } if (req) { + struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; + + if ((cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && + (card->quirks & MMC_QUIRK_CMDQ_EMPTY_BEFORE_DCMD) && + ctx->active_small_sector_read_reqs) { + ret = wait_event_interruptible(ctx->queue_empty_wq, + !ctx->active_reqs); + if (ret) { + pr_err("%s: failed while waiting for the CMDQ to be empty %s err (%d)\n", + mmc_hostname(host), + __func__, ret); + BUG_ON(1); + } + /* clear the counter now */ + ctx->active_small_sector_read_reqs = 0; + /* + * If there were small sector (less than 8 sectors) read + * operations in progress then we have to wait for the + * outstanding requests to finish and should also have + * atleast 6 microseconds delay before queuing the DCMD + * request. + */ + udelay(MMC_QUIRK_CMDQ_DELAY_BEFORE_DCMD); + } + if (cmd_flags & REQ_DISCARD) { if (cmd_flags & REQ_SECURE && !(card->quirks & MMC_QUIRK_SEC_ERASE_TRIM_BROKEN)) @@ -3494,19 +3529,6 @@ static int mmc_blk_cmdq_issue_rq(struct mmc_queue *mq, struct request *req) else ret = mmc_blk_cmdq_issue_discard_rq(mq, req); } else if (cmd_flags & REQ_FLUSH) { - if (card->quirks & - MMC_QUIRK_CMDQ_EMPTY_BEFORE_FLUSH) { - ret = wait_event_interruptible( - card->host->cmdq_ctx.queue_empty_wq, - (!card->host->cmdq_ctx.active_reqs)); - if (ret) { - pr_err("%s: failed while waiting for the CMDQ to be empty %s err (%d)\n", - mmc_hostname(card->host), - __func__, ret); - BUG_ON(1); - } - } - ret = mmc_blk_cmdq_issue_flush_rq(mq, req); } else { ret = mmc_blk_cmdq_issue_rw_rq(mq, req); @@ -3975,7 +3997,7 @@ static const struct mmc_fixup blk_fixups[] = MMC_FIXUP("MMC32G", CID_MANFID_TOSHIBA, CID_OEMID_ANY, add_quirk_mmc, MMC_QUIRK_BLK_NO_CMD23), MMC_FIXUP(CID_NAME_ANY, CID_MANFID_TOSHIBA, CID_OEMID_ANY, - add_quirk_mmc, MMC_QUIRK_CMDQ_EMPTY_BEFORE_FLUSH), + add_quirk_mmc, MMC_QUIRK_CMDQ_EMPTY_BEFORE_DCMD), /* * Some Micron MMC cards needs longer data read timeout than diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 2574e8af91fe..5ee1a379b82b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -335,6 +335,8 @@ enum mmc_pon_type { MMC_SHRT_PON, }; +#define MMC_QUIRK_CMDQ_DELAY_BEFORE_DCMD 6 /* microseconds */ + /* * MMC device */ @@ -388,8 +390,8 @@ struct mmc_card { #define MMC_QUIRK_QCA9377_SETTINGS (1 << 16) /* QCA9377 card settings*/ -/* Make sure CMDQ is empty before queuing cache flush */ -#define MMC_QUIRK_CMDQ_EMPTY_BEFORE_FLUSH (1 << 17) +/* Make sure CMDQ is empty before queuing DCMD */ +#define MMC_QUIRK_CMDQ_EMPTY_BEFORE_DCMD (1 << 17) unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 959713837423..6dd6add52d64 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -260,6 +260,7 @@ struct mmc_cmdq_context_info { #define CMDQ_STATE_HALT 2 wait_queue_head_t queue_empty_wq; wait_queue_head_t wait; + int active_small_sector_read_reqs; }; /** From 1fa0b8ce81f9cdc0bedda8dcde9967f6dcd85f9c Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Wed, 18 Nov 2015 10:37:56 +0530 Subject: [PATCH 416/472] mmc: core: Make sure host is active before sending pon command Add mmc_get_card() to make sure host is resumed before sending pon command. Also, add mmc_put_card() after completing pon command so that it can release the host and also vote for runtime suspend. Signed-off-by: Sahitya Tummala Change-Id: I52f4d854388a608c6a09e55a693e3b36bd801fd8 --- drivers/mmc/core/mmc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 2f113b52e35e..fda54ff43720 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2312,7 +2312,7 @@ int mmc_send_pon(struct mmc_card *card) if (!mmc_can_poweroff_notify(card)) goto out; - mmc_claim_host(host); + mmc_get_card(card); if (card->pon_type & MMC_LONG_PON) err = mmc_poweroff_notify(host->card, EXT_CSD_POWER_OFF_LONG); else if (card->pon_type & MMC_SHRT_PON) @@ -2320,7 +2320,7 @@ int mmc_send_pon(struct mmc_card *card) if (err) pr_warn("%s: error %d sending PON type %u", mmc_hostname(host), err, card->pon_type); - mmc_release_host(host); + mmc_put_card(card); out: return err; } From 9abc7e0d27d5e9eb2fb884cac0506b8e5468982b Mon Sep 17 00:00:00 2001 From: Talel Shenhar Date: Sun, 8 Nov 2015 14:21:31 +0200 Subject: [PATCH 417/472] mmc: sdhci-msm: add support for fourth drive strength This change adds support for new drive strength. Before this change sdhci-msm supported only 3 types of drive strength, starting from eMMC version 5.0 there is additional drive strength. Change-Id: Ib0bf945560107ec732e7ffeb7653d271d8d91c2f Signed-off-by: Talel Shenhar [subhashj@codeaurora.org: fixed minor merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index d02ef3f22a6d..a5be121d7f10 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -191,7 +191,7 @@ #define INVALID_TUNING_PHASE -1 #define NUM_TUNING_PHASES 16 -#define MAX_DRV_TYPES_SUPPORTED_HS200 3 +#define MAX_DRV_TYPES_SUPPORTED_HS200 4 #define MSM_AUTOSUSPEND_DELAY_MS 100 static const u32 tuning_block_64[] = { @@ -1089,6 +1089,8 @@ retry: /* set drive type to other value . default setting is 0x0 */ while (++drv_type <= MAX_DRV_TYPES_SUPPORTED_HS200) { + pr_debug("%s: trying different drive strength (%d)\n", + mmc_hostname(mmc), drv_type); if (card->ext_csd.raw_driver_strength & (1 << drv_type)) { sdhci_msm_set_mmc_drv_type(host, opcode, From a1cc4ebee231f22f72bb0140710f08185fd45d3a Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Thu, 5 Nov 2015 20:39:31 +0530 Subject: [PATCH 418/472] mmc: sdhci-msm: Remove MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE capability This removes direct enabling of MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE capability from sdhci-msm driver. This capablity needs to be enabled based on reading HOST_CAPABILITIES register. Change-Id: I74c2877d5906ee9e2180fdb8d50167af6d03b3af Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index a5be121d7f10..9a48b4d8b93b 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -4155,7 +4155,6 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= msm_host->pdata->caps2; msm_host->mmc->caps2 |= MMC_CAP2_BOOTPART_NOACC; msm_host->mmc->caps2 |= MMC_CAP2_FULL_PWR_CYCLE; - msm_host->mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; msm_host->mmc->caps2 |= MMC_CAP2_HS400_POST_TUNING; msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE; msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE; From 3aeae55f8a7688f88c952f84921337e8577d8ccc Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 25 Nov 2015 10:37:21 +0530 Subject: [PATCH 419/472] mmc: sdhci: Add asynchronous interrupt support for sdio cards SD host controller have asynchronous interrupt support capability to detect card(sdio) interrupt even when clocks are gated(to save power). This patch add support and enable this capability/feature to of SD host controllers for sdio cards. Change-Id: Ic1945355a19ebfdb3bd000bf8138d8001cea53f6 Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed merge conflicts & compilatione error] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 4 ++ drivers/mmc/core/sdio.c | 2 + drivers/mmc/core/sdio_irq.c | 2 + drivers/mmc/host/sdhci.c | 97 ++++++++++++++++++++++++++++++++----- drivers/mmc/host/sdhci.h | 5 ++ include/linux/mmc/host.h | 6 +++ 6 files changed, 105 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 90cd7c5a61d1..75490fad9851 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2255,6 +2255,10 @@ void mmc_set_ungated(struct mmc_host *host) void mmc_set_ungated(struct mmc_host *host) { } + +void mmc_gate_clock(struct mmc_host *host) +{ +} #endif int mmc_execute_tuning(struct mmc_card *card) diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 27ded39682f7..e30936281a0d 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -1007,6 +1007,8 @@ static int mmc_sdio_suspend(struct mmc_host *host) if (!mmc_card_keep_power(host)) { mmc_power_off(host); + } else if (host->ios.clock) { + mmc_gate_clock(host); } else if (host->retune_period) { mmc_retune_timer_stop(host); mmc_retune_needed(host); diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index b95f942aea0e..95589d1fef18 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -93,7 +93,9 @@ void sdio_run_irqs(struct mmc_host *host) { mmc_claim_host(host); host->sdio_irq_pending = true; + mmc_host_clk_hold(host); process_sdio_pending_irqs(host); + mmc_host_clk_release(host); mmc_release_host(host); } EXPORT_SYMBOL_GPL(sdio_run_irqs); diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2cfa1d0249a5..4d4d68fb120a 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -65,6 +65,7 @@ static int sdhci_pre_dma_transfer(struct sdhci_host *host, struct mmc_data *data); static int sdhci_do_get_cd(struct sdhci_host *host); static bool sdhci_check_state(struct sdhci_host *); +static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable); #ifdef CONFIG_PM static int sdhci_runtime_pm_get(struct sdhci_host *host); @@ -93,9 +94,10 @@ static void sdhci_dump_state(struct sdhci_host *host) struct mmc_host *mmc = host->mmc; #ifdef CONFIG_MMC_CLKGATE - pr_info("%s: clk: %d clk-gated: %d claimer: %s pwr: %d\n", + pr_info("%s: clk: %d clk-gated: %d claimer: %s pwr: %d host->irq = %d\n", mmc_hostname(mmc), host->clock, mmc->clk_gated, - mmc->claimer->comm, host->pwr); + mmc->claimer->comm, host->pwr, + (host->flags & SDHCI_HOST_IRQ_STATUS)); #else pr_info("%s: clk: %d claimer: %s pwr: %d\n", mmc_hostname(mmc), host->clock, @@ -1768,6 +1770,21 @@ void sdhci_set_uhs_signaling(struct sdhci_host *host, unsigned timing) } EXPORT_SYMBOL_GPL(sdhci_set_uhs_signaling); +void sdhci_cfg_irq(struct sdhci_host *host, bool enable, bool sync) +{ + if (enable && !(host->flags & SDHCI_HOST_IRQ_STATUS)) { + enable_irq(host->irq); + host->flags |= SDHCI_HOST_IRQ_STATUS; + } else if (!enable && (host->flags & SDHCI_HOST_IRQ_STATUS)) { + if (sync) + disable_irq(host->irq); + else + disable_irq_nosync(host->irq); + host->flags &= ~SDHCI_HOST_IRQ_STATUS; + } +} +EXPORT_SYMBOL(sdhci_cfg_irq); + static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) { unsigned long flags; @@ -1788,6 +1805,10 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) sdhci_enable_preset_value(host, false); spin_lock_irqsave(&host->lock, flags); + if (host->mmc && host->mmc->card && + mmc_card_sdio(host->mmc->card)) + sdhci_cfg_irq(host, false, false); + if (ios->clock && ((ios->clock != host->clock) || (ios->timing != host->timing))) { spin_unlock_irqrestore(&host->lock, flags); @@ -1807,6 +1828,8 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) host->mmc->max_busy_timeout /= host->timeout_clk; } } + if (ios->clock && host->sdio_irq_async_status) + sdhci_enable_sdio_irq_nolock(host, false); spin_unlock_irqrestore(&host->lock, flags); /* @@ -1830,6 +1853,9 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) spin_lock_irqsave(&host->lock, flags); if (!host->clock) { + if (host->mmc && host->mmc->card && + mmc_card_sdio(host->mmc->card)) + sdhci_cfg_irq(host, true, false); spin_unlock_irqrestore(&host->lock, flags); return; } @@ -1968,6 +1994,12 @@ static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) if (!ios->clock) host->ops->set_clock(host, ios->clock); + spin_lock_irqsave(&host->lock, flags); + if (host->mmc && host->mmc->card && + mmc_card_sdio(host->mmc->card)) + sdhci_cfg_irq(host, true, false); + spin_unlock_irqrestore(&host->lock, flags); + mmiowb(); } @@ -2080,16 +2112,28 @@ static int sdhci_get_ro(struct mmc_host *mmc) static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable) { - if (!(host->flags & SDHCI_DEVICE_DEAD)) { - if (enable) - host->ier |= SDHCI_INT_CARD_INT; - else - host->ier &= ~SDHCI_INT_CARD_INT; + u16 ctrl = 0; - sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); - sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); - mmiowb(); + if (host->flags & SDHCI_DEVICE_DEAD) + return; + + if (mmc_card_and_host_support_async_int(host->mmc)) { + ctrl = sdhci_readw(host, SDHCI_HOST_CONTROL2); + if (enable) + ctrl |= SDHCI_CTRL_ASYNC_INT_ENABLE; + else + ctrl &= ~SDHCI_CTRL_ASYNC_INT_ENABLE; + sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); } + + if (enable) + host->ier |= SDHCI_INT_CARD_INT; + else + host->ier &= ~SDHCI_INT_CARD_INT; + + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + mmiowb(); } static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) @@ -3060,6 +3104,29 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) return IRQ_NONE; } + if (!host->clock && host->mmc->card && + mmc_card_sdio(host->mmc->card)) { + if (!mmc_card_and_host_support_async_int(host->mmc)) + return IRQ_NONE; + /* + * async card interrupt is level sensitive and received + * when clocks are off. + * If sdio card has asserted async interrupt, in that + * case we need to disable host->irq. + * Later we can disable card interrupt and re-enable + * host->irq. + */ + + pr_debug("%s: %s: sdio_async intr. received\n", + mmc_hostname(host->mmc), __func__); + sdhci_cfg_irq(host, false, false); + host->sdio_irq_async_status = true; + host->thread_isr |= SDHCI_INT_CARD_INT; + result = IRQ_WAKE_THREAD; + spin_unlock(&host->lock); + return result; + } + intmask = sdhci_readl(host, SDHCI_INT_STATUS); if (!intmask || intmask == 0xffffffff) { result = IRQ_NONE; @@ -3190,8 +3257,11 @@ static irqreturn_t sdhci_thread_irq(int irq, void *dev_id) sdio_run_irqs(host->mmc); spin_lock_irqsave(&host->lock, flags); - if (host->flags & SDHCI_SDIO_IRQ_ENABLED) + if (host->flags & SDHCI_SDIO_IRQ_ENABLED) { + if (host->sdio_irq_async_status) + host->sdio_irq_async_status = false; sdhci_enable_sdio_irq_nolock(host, true); + } spin_unlock_irqrestore(&host->lock, flags); } @@ -3809,6 +3879,9 @@ int sdhci_add_host(struct sdhci_host *host) mmc->caps |= MMC_CAP_SDIO_IRQ | MMC_CAP_ERASE | MMC_CAP_CMD23; mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; + if (caps[0] & SDHCI_CAN_ASYNC_INT) + mmc->caps2 |= MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE; + if (host->quirks & SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12) host->flags |= SDHCI_AUTO_CMD12; @@ -4072,6 +4145,8 @@ int sdhci_add_host(struct sdhci_host *host) init_waitqueue_head(&host->buf_ready_int); + host->flags |= SDHCI_HOST_IRQ_STATUS; + sdhci_init(host, 0); ret = request_threaded_irq(host->irq, sdhci_irq, sdhci_thread_irq, diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 1ec9e9210951..b2e6d3958518 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -180,6 +180,7 @@ #define SDHCI_CTRL_DRV_TYPE_D 0x0030 #define SDHCI_CTRL_EXEC_TUNING 0x0040 #define SDHCI_CTRL_TUNED_CLK 0x0080 +#define SDHCI_CTRL_ASYNC_INT_ENABLE 0x4000 #define SDHCI_CTRL_PRESET_VAL_ENABLE 0x8000 #define SDHCI_CAPABILITIES 0x40 @@ -200,6 +201,7 @@ #define SDHCI_CAN_VDD_300 0x02000000 #define SDHCI_CAN_VDD_180 0x04000000 #define SDHCI_CAN_64BIT 0x10000000 +#define SDHCI_CAN_ASYNC_INT 0x20000000 #define SDHCI_SUPPORT_SDR50 0x00000001 #define SDHCI_SUPPORT_SDR104 0x00000002 @@ -531,6 +533,7 @@ struct sdhci_host { #define SDHCI_USE_64_BIT_DMA (1<<12) /* Use 64-bit DMA */ #define SDHCI_USE_ADMA_64BIT (1<<12) /* Host is 64-bit ADMA capable */ #define SDHCI_HS400_TUNING (1<<13) /* Tuning for HS400 */ +#define SDHCI_HOST_IRQ_STATUS (1<<14) /* host->irq status */ unsigned int version; /* SDHCI spec. version */ @@ -605,6 +608,7 @@ struct sdhci_host { bool is_crypto_en; bool crypto_reset_reqd; + bool sdio_irq_async_status; u32 auto_cmd_err_sts; struct ratelimit_state dbg_dump_rs; @@ -798,4 +802,5 @@ extern int sdhci_runtime_suspend_host(struct sdhci_host *host); extern int sdhci_runtime_resume_host(struct sdhci_host *host); #endif +void sdhci_cfg_irq(struct sdhci_host *host, bool enable, bool sync); #endif /* __SDHCI_HW_H */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 6dd6add52d64..e6dd9eb4ead4 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -683,6 +683,12 @@ static inline int mmc_boot_partition_access(struct mmc_host *host) return !(host->caps2 & MMC_CAP2_BOOTPART_NOACC); } +static inline bool mmc_card_and_host_support_async_int(struct mmc_host *host) +{ + return ((host->caps2 & MMC_CAP2_ASYNC_SDIO_IRQ_4BIT_MODE) && + (host->card->cccr.async_intr_sup)); +} + static inline int mmc_host_uhs(struct mmc_host *host) { return host->caps & From 215da65bf6fde62b5cd5cc3fa388e507369c28b1 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Fri, 6 Nov 2015 01:13:49 +0530 Subject: [PATCH 420/472] mmc: core: Remove disabling of clk-gating for sdio cards This removes disabling of clock-gating for qca6574 & qca9377. Now since async card interrupt support for sdio card has been added(3c5351c0643a), re-enable clk-gating support for sdio cards. Change-Id: Ib497556f10c9c6576a3e0a3592f6755c8725370c Signed-off-by: Ritesh Harjani --- drivers/mmc/core/host.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index c4bd0ecdf761..5e06ba5fbe97 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -194,13 +194,8 @@ bool mmc_host_may_gate_card(struct mmc_card *card) * SDIO3.0 card allows the clock to be gated off so check if * that is the case or not. */ - if (mmc_card_sdio(card) && card->cccr.async_intr_sup) { - if (mmc_enable_qca6574_settings(card) || - mmc_enable_qca9377_settings(card)) - return false; - else + if (mmc_card_sdio(card) && card->cccr.async_intr_sup) return true; - } /* * Don't gate SDIO cards! These need to be clocked at all times From 4940ee4c4ac518ce9cb42abd1523c9fa76694ce0 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Tue, 17 Nov 2015 17:46:51 +0530 Subject: [PATCH 421/472] mmc: sdhci-msm: Add wakeup functionality support for sdio This adds external GPIO wakeup support to sdhci-msm driver for sdio cards. Change-Id: Ic3e280b975d293ea8adadadafecfa8115fe5f428 Signed-off-by: Ritesh Harjani Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 164 ++++++++++++++++++++++++++++++++--- drivers/mmc/host/sdhci-msm.h | 2 + 2 files changed, 152 insertions(+), 14 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 9a48b4d8b93b..f51ffdc0b9cd 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -189,6 +189,7 @@ #define TCXO_FREQ 19200000 #define INVALID_TUNING_PHASE -1 +#define sdhci_is_valid_gpio_wakeup_int(_h) ((_h)->pdata->sdiowakeup_irq >= 0) #define NUM_TUNING_PHASES 16 #define MAX_DRV_TYPES_SUPPORTED_HS200 4 @@ -2254,6 +2255,39 @@ static int sdhci_msm_set_vdd_io_vol(struct sdhci_msm_pltfm_data *pdata, return ret; } +/* + * Acquire spin-lock host->lock before calling this function + */ +static void sdhci_msm_cfg_sdiowakeup_gpio_irq(struct sdhci_host *host, + bool enable) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + + if (enable && !msm_host->is_sdiowakeup_enabled) + enable_irq(msm_host->pdata->sdiowakeup_irq); + else if (!enable && msm_host->is_sdiowakeup_enabled) + disable_irq_nosync(msm_host->pdata->sdiowakeup_irq); + else + dev_warn(&msm_host->pdev->dev, "%s: wakeup to config: %d curr: %d\n", + __func__, enable, msm_host->is_sdiowakeup_enabled); + msm_host->is_sdiowakeup_enabled = enable; +} + +static irqreturn_t sdhci_msm_sdiowakeup_irq(int irq, void *data) +{ + struct sdhci_host *host = (struct sdhci_host *)data; + unsigned long flags; + + pr_debug("%s: irq (%d) received\n", __func__, irq); + + spin_lock_irqsave(&host->lock, flags); + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + spin_unlock_irqrestore(&host->lock, flags); + + return IRQ_HANDLED; +} + void sdhci_msm_dump_pwr_ctrl_regs(struct sdhci_host *host) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -3829,6 +3863,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) u32 irq_status, irq_ctl; struct resource *tlmm_memres = NULL; void __iomem *tlmm_mem; + unsigned long flags; pr_debug("%s: Enter %s\n", dev_name(&pdev->dev), __func__); msm_host = devm_kzalloc(&pdev->dev, sizeof(struct sdhci_msm_host), @@ -4160,6 +4195,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->mmc->caps2 |= MMC_CAP2_SANITIZE; msm_host->mmc->caps2 |= MMC_CAP2_MAX_DISCARD_SIZE; msm_host->mmc->caps2 |= MMC_CAP2_SLEEP_AWAKE; + msm_host->mmc->pm_caps |= MMC_PM_KEEP_POWER | MMC_PM_WAKE_SDIO_IRQ; if (msm_host->pdata->nonremovable) msm_host->mmc->caps |= MMC_CAP_NONREMOVABLE; @@ -4220,6 +4256,29 @@ static int sdhci_msm_probe(struct platform_device *pdev) dev_err(&pdev->dev, "%s: Failed to set dma mask\n", __func__); } + msm_host->pdata->sdiowakeup_irq = platform_get_irq_byname(pdev, + "sdiowakeup_irq"); + dev_info(&pdev->dev, "%s: sdiowakeup_irq = %d\n", __func__, + msm_host->pdata->sdiowakeup_irq); + if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + msm_host->is_sdiowakeup_enabled = true; + ret = request_irq(msm_host->pdata->sdiowakeup_irq, + sdhci_msm_sdiowakeup_irq, + IRQF_SHARED | IRQF_TRIGGER_HIGH, + "sdhci-msm sdiowakeup", host); + if (ret) { + dev_err(&pdev->dev, "%s: request sdiowakeup IRQ %d: failed: %d\n", + __func__, msm_host->pdata->sdiowakeup_irq, ret); + msm_host->pdata->sdiowakeup_irq = -1; + msm_host->is_sdiowakeup_enabled = false; + goto free_cd_gpio; + } else { + spin_lock_irqsave(&host->lock, flags); + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + spin_unlock_irqrestore(&host->lock, flags); + } + } + sdhci_msm_cmdq_init(host, pdev); ret = sdhci_add_host(host); if (ret) { @@ -4339,6 +4398,51 @@ static int sdhci_msm_remove(struct platform_device *pdev) } #ifdef CONFIG_PM +static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + unsigned long flags; + int ret = 0; + + if (!(host->mmc->card && mmc_card_sdio(host->mmc->card) && + sdhci_is_valid_gpio_wakeup_int(msm_host) && + mmc_card_wake_sdio_irq(host->mmc))) { + return 1; + } + + spin_lock_irqsave(&host->lock, flags); + if (enable) { + /* configure DAT1 gpio if applicable */ + if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + ret = enable_irq_wake(msm_host->pdata->sdiowakeup_irq); + if (!ret) + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, true); + goto out; + } else { + pr_err("%s: sdiowakeup_irq(%d) invalid\n", + mmc_hostname(host->mmc), enable); + } + } else { + if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + ret = disable_irq_wake(msm_host->pdata->sdiowakeup_irq); + sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + } else { + pr_err("%s: sdiowakeup_irq(%d)invalid\n", + mmc_hostname(host->mmc), enable); + + } + } +out: + if (ret) + pr_err("%s: %s: %sable wakeup: failed: %d gpio: %d\n", + mmc_hostname(host->mmc), __func__, enable ? "en" : "dis", + ret, msm_host->pdata->sdiowakeup_irq); + spin_unlock_irqrestore(&host->lock, flags); + return ret; +} + + static int sdhci_msm_runtime_suspend(struct device *dev) { struct sdhci_host *host = dev_get_drvdata(dev); @@ -4347,13 +4451,12 @@ static int sdhci_msm_runtime_suspend(struct device *dev) ktime_t start = ktime_get(); int ret; - if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { - if (mmc_enable_qca6574_settings(host->mmc->card) || - mmc_enable_qca9377_settings(host->mmc->card)) - return 0; - } + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + goto defer_disable_host_irq; - disable_irq(host->irq); + sdhci_cfg_irq(host, false, true); + +defer_disable_host_irq: disable_irq(msm_host->pwr_irq); /* @@ -4385,13 +4488,6 @@ static int sdhci_msm_runtime_resume(struct device *dev) ktime_t start = ktime_get(); int ret; - if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { - if (mmc_enable_qca6574_settings(host->mmc->card) || - mmc_enable_qca9377_settings(host->mmc->card)) - return 0; - } - - if (host->is_crypto_en) { ret = sdhci_msm_enable_controller_clock(host); if (ret) { @@ -4405,9 +4501,13 @@ static int sdhci_msm_runtime_resume(struct device *dev) mmc_hostname(host->mmc), ret); } skip_ice_resume: + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + goto defer_enable_host_irq; + sdhci_cfg_irq(host, true, true); + +defer_enable_host_irq: enable_irq(msm_host->pwr_irq); - enable_irq(host->irq); trace_sdhci_msm_runtime_resume(mmc_hostname(host->mmc), 0, ktime_to_us(ktime_sub(ktime_get(), start))); @@ -4420,6 +4520,7 @@ static int sdhci_msm_suspend(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; int ret = 0; + int sdio_cfg = 0; ktime_t start = ktime_get(); if (gpio_is_valid(msm_host->pdata->status_gpio) && @@ -4433,6 +4534,13 @@ static int sdhci_msm_suspend(struct device *dev) } ret = sdhci_msm_runtime_suspend(dev); out: + + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { + sdio_cfg = sdhci_msm_cfg_sdio_wakeup(host, true); + if (sdio_cfg) + sdhci_cfg_irq(host, false, true); + } + trace_sdhci_msm_suspend(mmc_hostname(host->mmc), ret, ktime_to_us(ktime_sub(ktime_get(), start))); return ret; @@ -4444,6 +4552,7 @@ static int sdhci_msm_resume(struct device *dev) struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); struct sdhci_msm_host *msm_host = pltfm_host->priv; int ret = 0; + int sdio_cfg = 0; ktime_t start = ktime_get(); if (gpio_is_valid(msm_host->pdata->status_gpio) && @@ -4458,15 +4567,42 @@ static int sdhci_msm_resume(struct device *dev) ret = sdhci_msm_runtime_resume(dev); out: + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) { + sdio_cfg = sdhci_msm_cfg_sdio_wakeup(host, false); + if (sdio_cfg) + sdhci_cfg_irq(host, true, true); + } + trace_sdhci_msm_resume(mmc_hostname(host->mmc), ret, ktime_to_us(ktime_sub(ktime_get(), start))); return ret; } +static int sdhci_msm_suspend_noirq(struct device *dev) +{ + struct sdhci_host *host = dev_get_drvdata(dev); + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + int ret = 0; + + /* + * ksdioirqd may be running, hence retry + * suspend in case the clocks are ON + */ + if (atomic_read(&msm_host->clks_on)) { + pr_warn("%s: %s: clock ON after suspend, aborting suspend\n", + mmc_hostname(host->mmc), __func__); + ret = -EAGAIN; + } + + return ret; +} + static const struct dev_pm_ops sdhci_msm_pmops = { SET_SYSTEM_SLEEP_PM_OPS(sdhci_msm_suspend, sdhci_msm_resume) SET_RUNTIME_PM_OPS(sdhci_msm_runtime_suspend, sdhci_msm_runtime_resume, NULL) + .suspend_noirq = sdhci_msm_suspend_noirq, }; #define SDHCI_MSM_PMOPS (&sdhci_msm_pmops) diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 1f92a285893d..87914446c508 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -146,6 +146,7 @@ struct sdhci_msm_pltfm_data { struct sdhci_msm_bus_voting_data *voting_data; u32 *sup_clk_table; unsigned char sup_clk_cnt; + int sdiowakeup_irq; u32 *sup_ice_clk_table; unsigned char sup_ice_clk_cnt; u32 ice_clk_max; @@ -195,6 +196,7 @@ struct sdhci_msm_host { u8 saved_tuning_phase; bool en_auto_cmd21; struct device_attribute auto_cmd21_attr; + bool is_sdiowakeup_enabled; atomic_t controller_clock; bool use_cdclp533; bool use_updated_dll_reset; From f6f99b96c30abe5cc04693163853b19d94c64564 Mon Sep 17 00:00:00 2001 From: Gilad Broner Date: Wed, 28 Oct 2015 17:57:14 +0200 Subject: [PATCH 422/472] crypto: ice: general driver clean-up * Removed spinlock as it was not locking against anything * Removed conversion of interrupt status to error number as it is not used by API client, and in case several bits are set only 1 error is ever handled and the rest get lost. Instead pass to the client the complete status. * Removed redundant includes, variables * vops structure is returned after performing a lookup in the DTS. There's no need for that as we already know the structure to return. * Other minor corrections Change-Id: I6d2549ce04c9e4b19fdd8fe3dfee03d83bfd9d77 Signed-off-by: Gilad Broner [subhashj@codeaurora.org: fixed merge conflicts, dropped ICE & UFS driver changes as they are already present] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm-ice.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm-ice.c b/drivers/mmc/host/sdhci-msm-ice.c index 955096b2d519..abf2ae2020c9 100644 --- a/drivers/mmc/host/sdhci-msm-ice.c +++ b/drivers/mmc/host/sdhci-msm-ice.c @@ -13,12 +13,12 @@ #include "sdhci-msm-ice.h" -static void sdhci_msm_ice_error_cb(void *host_ctrl, enum ice_error_code evt) +static void sdhci_msm_ice_error_cb(void *host_ctrl, u32 error) { struct sdhci_msm_host *msm_host = (struct sdhci_msm_host *)host_ctrl; - dev_err(&msm_host->pdev->dev, "%s: Error in ice operation %d", - __func__, evt); + dev_err(&msm_host->pdev->dev, "%s: Error in ice operation 0x%x", + __func__, error); if (msm_host->ice.state == SDHCI_MSM_ICE_STATE_ACTIVE) msm_host->ice.state = SDHCI_MSM_ICE_STATE_DISABLED; From bad8692dfc73ddea1dc83b39176542f0f6ca7045 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 1 Dec 2015 12:19:58 +0530 Subject: [PATCH 423/472] mmc: sdhci: delay the QoS vote removal By delaying the QoS vote removal, there is some improvement in performance in single threaded use cases. Change-Id: I80545486057c55c697b72b56d57e2ea47cff86b9 Signed-off-by: Asutosh Das --- drivers/mmc/host/sdhci-msm.c | 26 ++++++++++++++++---------- drivers/mmc/host/sdhci-msm.h | 4 ++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f51ffdc0b9cd..e9e041922442 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -43,6 +43,7 @@ #include "sdhci-msm-ice.h" #include "cmdq_hci.h" +#define QOS_REMOVE_DELAY_MS 10 #define CORE_POWER 0x0 #define CORE_SW_RST (1 << 7) @@ -3226,7 +3227,8 @@ void sdhci_msm_reset_workaround(struct sdhci_host *host, u32 enable) static void sdhci_msm_pm_qos_irq_unvote_work(struct work_struct *work) { struct sdhci_msm_pm_qos_irq *pm_qos_irq = - container_of(work, struct sdhci_msm_pm_qos_irq, unvote_work); + container_of(work, struct sdhci_msm_pm_qos_irq, + unvote_work.work); if (atomic_read(&pm_qos_irq->counter)) return; @@ -3252,7 +3254,7 @@ void sdhci_msm_pm_qos_irq_vote(struct sdhci_host *host) && counter > 1) return; - cancel_work_sync(&msm_host->pm_qos_irq.unvote_work); + cancel_delayed_work_sync(&msm_host->pm_qos_irq.unvote_work); msm_host->pm_qos_irq.latency = latency->latency[host->power_policy]; pm_qos_update_request(&msm_host->pm_qos_irq.req, msm_host->pm_qos_irq.latency); @@ -3278,7 +3280,8 @@ void sdhci_msm_pm_qos_irq_unvote(struct sdhci_host *host, bool async) return; if (async) { - schedule_work(&msm_host->pm_qos_irq.unvote_work); + schedule_delayed_work(&msm_host->pm_qos_irq.unvote_work, + msecs_to_jiffies(QOS_REMOVE_DELAY_MS)); return; } @@ -3333,7 +3336,7 @@ sdhci_msm_pm_qos_irq_enable_store(struct device *dev, msm_host->pm_qos_irq.enabled = enable; if (!enable) { - cancel_work_sync(&msm_host->pm_qos_irq.unvote_work); + cancel_delayed_work_sync(&msm_host->pm_qos_irq.unvote_work); atomic_set(&msm_host->pm_qos_irq.counter, 0); msm_host->pm_qos_irq.latency = PM_QOS_DEFAULT_VALUE; pm_qos_update_request(&msm_host->pm_qos_irq.req, @@ -3379,7 +3382,7 @@ void sdhci_msm_pm_qos_irq_init(struct sdhci_host *host) cpumask_copy(&msm_host->pm_qos_irq.req.cpus_affine, cpumask_of(msm_host->pdata->pm_qos_data.irq_cpu)); - INIT_WORK(&msm_host->pm_qos_irq.unvote_work, + INIT_DELAYED_WORK(&msm_host->pm_qos_irq.unvote_work, sdhci_msm_pm_qos_irq_unvote_work); /* For initialization phase, set the performance latency */ irq_latency = &msm_host->pdata->pm_qos_data.irq_latency; @@ -3473,7 +3476,8 @@ static ssize_t sdhci_msm_pm_qos_group_enable_store(struct device *dev, msm_host->pm_qos_group_enable = enable; if (!enable) { for (i = 0; i < nr_groups; i++) { - cancel_work_sync(&msm_host->pm_qos[i].unvote_work); + cancel_delayed_work_sync( + &msm_host->pm_qos[i].unvote_work); atomic_set(&msm_host->pm_qos[i].counter, 0); msm_host->pm_qos[i].latency = PM_QOS_DEFAULT_VALUE; pm_qos_update_request(&msm_host->pm_qos[i].req, @@ -3522,7 +3526,7 @@ void sdhci_msm_pm_qos_cpu_vote(struct sdhci_host *host, && counter > 1) return; - cancel_work_sync(&pm_qos_group->unvote_work); + cancel_delayed_work_sync(&pm_qos_group->unvote_work); pm_qos_group->latency = latency->latency[host->power_policy]; pm_qos_update_request(&pm_qos_group->req, pm_qos_group->latency); @@ -3531,7 +3535,8 @@ void sdhci_msm_pm_qos_cpu_vote(struct sdhci_host *host, static void sdhci_msm_pm_qos_cpu_unvote_work(struct work_struct *work) { struct sdhci_msm_pm_qos_group *group = - container_of(work, struct sdhci_msm_pm_qos_group, unvote_work); + container_of(work, struct sdhci_msm_pm_qos_group, + unvote_work.work); if (atomic_read(&group->counter)) return; @@ -3551,7 +3556,8 @@ bool sdhci_msm_pm_qos_cpu_unvote(struct sdhci_host *host, int cpu, bool async) return false; if (async) { - schedule_work(&msm_host->pm_qos[group].unvote_work); + schedule_delayed_work(&msm_host->pm_qos[group].unvote_work, + msecs_to_jiffies(QOS_REMOVE_DELAY_MS)); return true; } @@ -3581,7 +3587,7 @@ void sdhci_msm_pm_qos_cpu_init(struct sdhci_host *host, for (i = 0; i < nr_groups; i++) { group = &msm_host->pm_qos[i]; - INIT_WORK(&group->unvote_work, + INIT_DELAYED_WORK(&group->unvote_work, sdhci_msm_pm_qos_cpu_unvote_work); atomic_set(&group->counter, 0); group->req.type = PM_QOS_REQ_AFFINE_CORES; diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 87914446c508..09949465d0cb 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -111,7 +111,7 @@ struct sdhci_msm_pm_qos_data { */ struct sdhci_msm_pm_qos_group { struct pm_qos_request req; - struct work_struct unvote_work; + struct delayed_work unvote_work; atomic_t counter; s32 latency; }; @@ -119,7 +119,7 @@ struct sdhci_msm_pm_qos_group { /* PM QoS HW IRQ voting */ struct sdhci_msm_pm_qos_irq { struct pm_qos_request req; - struct work_struct unvote_work; + struct delayed_work unvote_work; struct device_attribute enable_attr; struct device_attribute status_attr; atomic_t counter; From f47dba1b3132907fdafe320ecd052d6b4e239d60 Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 9 Dec 2015 10:48:18 +0530 Subject: [PATCH 424/472] mmc: core: support DDR52 bus-speed during eMMC clock scaling Add support for DDR52 bus-speed mode during clock scaling. The reason for this change is DDR52 can be supported at SVS mode. Change-Id: I68e5fca57ae5cbc154f5dd7001df368900cb3f57 Signed-off-by: Asutosh Das --- drivers/mmc/core/mmc.c | 32 ++++++++++++++++++++++++++++++-- include/linux/mmc/host.h | 2 ++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index fda54ff43720..39bf4455d16a 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -1550,6 +1550,24 @@ out: return ret; } +static int mmc_select_hs_ddr52(struct mmc_host *host) +{ + int err; + + mmc_select_hs(host->card); + mmc_set_clock(host, MMC_HIGH_52_MAX_DTR); + err = mmc_select_bus_width(host->card); + if (err < 0) { + pr_err("%s: %s: select_bus_width failed(%d)\n", + mmc_hostname(host), __func__, err); + return err; + } + + err = mmc_select_hs_ddr(host->card); + + return err; +} + /* * Scale down from HS400 to HS in order to allow frequency change. * This is needed for cards that doesn't support changing frequency in HS400 @@ -1561,10 +1579,20 @@ static int mmc_scale_low(struct mmc_host *host, unsigned long freq) mmc_set_timing(host, MMC_TIMING_LEGACY); mmc_set_clock(host, MMC_HIGH_26_MAX_DTR); + if (host->clk_scaling.lower_bus_speed_mode & + MMC_SCALING_LOWER_DDR52_MODE) { + err = mmc_select_hs_ddr52(host); + if (err) + pr_err("%s: %s: failed to switch to DDR52: err: %d\n", + mmc_hostname(host), __func__, err); + else + return err; + } + err = mmc_select_hs(host->card); if (err) { - pr_err("%s: %s: selecting HS (52Mhz) failed (%d)\n", - mmc_hostname(host), __func__, err); + pr_err("%s: %s: scaling low: failed (%d)\n", + mmc_hostname(host), __func__, err); return err; } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index e6dd9eb4ead4..38731725cc80 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -335,6 +335,8 @@ struct mmc_devfeq_clk_scaling { unsigned long polling_delay_ms; unsigned int upthreshold; unsigned int downthreshold; + unsigned int lower_bus_speed_mode; +#define MMC_SCALING_LOWER_DDR52_MODE 1 bool need_freq_change; bool clk_scaling_in_progress; bool is_busy_started; From 421d4bfa089cbf23927cba3dda912c77dfa15408 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 23 Jul 2015 13:05:54 +0530 Subject: [PATCH 425/472] mmc: sdhci-msm: get lower bus speed mode for clock scaling The lower bus speed mode to be used during clock scaling may vary based on the target. Hence, add a new dtsi property to define this bus speed mode. Change-Id: If8e2d125b8246ca479f816a475940bb357138297 Signed-off-by: Sahitya Tummala Signed-off-by: Asutosh Das --- .../devicetree/bindings/mmc/sdhci-msm.txt | 6 ++++++ drivers/mmc/host/sdhci-msm.c | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt index 0d62b6458e83..3fc5d6cda7c9 100644 --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt @@ -68,6 +68,10 @@ Optional Properties: command-queueing mode or legacy respectively. - qcom,core_3_0v_support: an optional property that is used to fake 3.0V support for SDIO devices. + - qcom,scaling-lower-bus-speed-mode: specifies the lower bus speed mode to be used + during clock scaling. If this property is not + defined, then it falls back to the default HS + bus speed mode to maintain backward compatibility. In the following, can be vdd (flash core voltage) or vdd-io (I/O voltage). - qcom,-always-on - specifies whether supply should be kept "on" always. @@ -138,6 +142,8 @@ Example: qcom,bus-speed-mode = "HS200_1p8v", "DDR_1p8v"; qcom,ice-clk-rates = <300000000>; + qcom,scaling-lower-bus-speed-mode = "DDR52"; + gpios = <&msmgpio 40 0>, /* CLK */ <&msmgpio 39 0>, /* CMD */ <&msmgpio 38 0>, /* DATA0 */ diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index e9e041922442..644906b5252a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -1622,6 +1622,7 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, int ice_clk_table_len; u32 *ice_clk_table = NULL; enum of_gpio_flags flags = OF_GPIO_ACTIVE_LOW; + const char *lower_bus_speed = NULL; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { @@ -1652,6 +1653,19 @@ struct sdhci_msm_pltfm_data *sdhci_msm_populate_pdata(struct device *dev, !msm_host->mmc->clk_scaling.freq_table_sz) dev_err(dev, "bad dts clock scaling frequencies\n"); + /* + * Few hosts can support DDR52 mode at the same lower + * system voltage corner as high-speed mode. In such cases, + * it is always better to put it in DDR mode which will + * improve the performance without any power impact. + */ + if (!of_property_read_string(np, "qcom,scaling-lower-bus-speed-mode", + &lower_bus_speed)) { + if (!strcmp(lower_bus_speed, "DDR52")) + msm_host->mmc->clk_scaling.lower_bus_speed_mode |= + MMC_SCALING_LOWER_DDR52_MODE; + } + if (sdhci_msm_dt_get_array(dev, "qcom,clk-rates", &clk_table, &clk_table_len, 0)) { dev_err(dev, "failed parsing supported clock rates\n"); From 31c01a3450f309561149bcbd30948542094c7755 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 5 Nov 2015 10:07:42 +0530 Subject: [PATCH 426/472] mmc: block: fix partition switch failure observed during shutdown If an RPMB request is the last request to be done before shutdown, then the card will be already switched to legacy mode as part of RPMB request handling. Later, in the shutdown handler, we get this error if we try to switch to legacy mode again. mmc0: failed to switch card to legacy mode: -74 CRs-Fixed: 935717 Change-Id: Ie75ba225412d0fecce9c98f07334b570a6cd5772 Signed-off-by: Sahitya Tummala --- drivers/mmc/card/block.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 120e4577b678..50b8a4bda7b2 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3002,18 +3002,19 @@ static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) } /* disable CQ mode in card */ - err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_CMDQ, 0, - card->ext_csd.generic_cmd6_time); - if (err) { - pr_err("%s: failed to switch card to legacy mode: %d\n", - __func__, err); - goto out; - } else { + if (mmc_card_cmdq(card)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CMDQ, 0, + card->ext_csd.generic_cmd6_time); + if (err) { + pr_err("%s: failed to switch card to legacy mode: %d\n", + __func__, err); + goto out; + } mmc_card_clr_cmdq(card); - host->cmdq_ops->disable(host, false); - host->card->cmdq_init = false; } + host->cmdq_ops->disable(host, false); + host->card->cmdq_init = false; out: mmc_host_clk_release(host); mmc_put_card(card); From 035148f45e7e02ee692800ea73c90b0a0e7c0ecc Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 5 Nov 2015 14:20:08 +0530 Subject: [PATCH 427/472] mmc: core: fix debugfs path to read ext_csd After reading ext_csd, the host is not claimed while doing unhalt if the card is in CQ mode. This may race with RPMB context which tries to halt and disable CQ in the card. This may unhalt the controller while RPMB is still going on, resulting in software request timeout for RPMB commands that are supposed to be sent in legacy mode with controller in halt state. Fix this by claiming the host till the unhalt is done in mmc_ext_csd_open() so as to prevent race with RPMB context. CRs-Fixed: 935719 Change-Id: I6d2738b21c3cd44c8fb6c99f63291059d5b18ee1 Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/debugfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 0c55824a449b..c894f64c2e38 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -461,7 +461,6 @@ static int mmc_ext_csd_open(struct inode *inode, struct file *filp) } err = mmc_get_ext_csd(card, &ext_csd); - mmc_put_card(card); if (err) goto out_free; @@ -478,6 +477,7 @@ static int mmc_ext_csd_open(struct inode *inode, struct file *filp) mmc_hostname(card->host), __func__); } + mmc_put_card(card); kfree(ext_csd); return 0; @@ -487,6 +487,7 @@ out_free: pr_err("%s: %s: cmdq unhalt failed\n", mmc_hostname(card->host), __func__); } + mmc_put_card(card); out_free_halt: kfree(buf); return err; From e974ac15b5a145fcd0d522e69f3d6ab79dcf1c46 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 21 Sep 2015 19:23:41 +0530 Subject: [PATCH 428/472] mmc: block: Fix invalid data from freed request pointer When we do blk_end_request, request pointer may get freed. Therefore cache request_queue pointer and tag value at start itself instead of dereferencing already freed request_queue pointer. Change-Id: I35def3ef6a260ebe78e92874ac121aca529f00cf Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 50b8a4bda7b2..a9c72d7213d1 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3070,6 +3070,8 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) struct mmc_request *mrq = host->err_mrq; struct mmc_card *card = mq->card; struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + struct request_queue *q = mrq->req->q; + int tag = mrq->req->tag; pm_runtime_get_sync(&card->dev); mmc_host_clk_hold(host); @@ -3110,13 +3112,13 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) mrq->req, &gen_err, &status); if (err) { pr_err("%s: error %d sending stop (%d) command\n", - mrq->req->rq_disk->disk_name, + mmc_hostname(host), err, status); goto reset; } } - if (mmc_cmdq_discard_queue(host, mrq->req->tag)) + if (mmc_cmdq_discard_queue(host, tag)) goto reset; else goto unhalt; @@ -3138,7 +3140,7 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) reset: spin_lock_irq(mq->queue->queue_lock); - blk_queue_invalidate_tags(mrq->req->q); + blk_queue_invalidate_tags(q); spin_unlock_irq(mq->queue->queue_lock); mmc_blk_cmdq_reset(host, true); goto out; From f33ab72df235e4802ba9e95e3d0978f5903fb0ed Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 18 Nov 2015 15:39:02 -0800 Subject: [PATCH 429/472] mmc: block: serialize the requests if we are scaled down When in SVS2 on low load scenario and there are lots of requests queued for CMDQ we need to wait till the queue is empty to scale back up to Nominal even if there is a sudden increase in load. This impacts performance where lots of IO get executed in SVS2 frequency since the queue is full. As SVS2 is a low load use case we can serialize the requests and not queue them in parallel without impacting other use cases. This makes sure the queue gets empty faster and we will be able to scale up to Nominal frequency when needed. Change-Id: Idbe7e939e01327061dfa5de93c0eaed59b910592 Signed-off-by: Subhash Jadavani Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index a9c72d7213d1..79211da9117e 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2896,6 +2896,7 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) struct mmc_queue_req *active_mqrq; struct mmc_card *card = mq->card; struct mmc_host *host = card->host; + struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; struct mmc_cmdq_req *mc_rq; int ret = 0; @@ -2920,6 +2921,20 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) && (rq_data_dir(req) == READ)) host->cmdq_ctx.active_small_sector_read_reqs++; } + /* + * When in SVS2 on low load scenario and there are lots of requests + * queued for CMDQ we need to wait till the queue is empty to scale + * back up to Nominal even if there is a sudden increase in load. + * This impacts performance where lots of IO get executed in SVS2 + * frequency since the queue is full. As SVS2 is a low load use case + * we can serialize the requests and not queue them in parallel + * without impacting other use cases. This makes sure the queue gets + * empty faster and we will be able to scale up to Nominal frequency + * when needed. + */ + if (!ret && (host->clk_scaling.state == MMC_LOAD_LOW)) + wait_event_interruptible(ctx->queue_empty_wq, + (!ctx->active_reqs)); return ret; } From 8b5ff5dc3ecaddaf70be69406bfc92cb168f8992 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Sun, 20 Dec 2015 15:16:40 +0200 Subject: [PATCH 430/472] mmc: core: resolve deadlock between devfreq update and suspend contexts When mmc request timeout occurs (eg. card is not responding), it is possible that devfreq update poll interval ended and devfreq update started. Update context takes devfreq lock and block on __mmc_claim_host(). Error handling flow (same context that executes mmc request) tries to suspend clock scaling, the flow is trying to take devfreq lock while holding (and not releasing) mmc host. Although it is incrementing devfreq_abort counter, but it is not enough to cause devfreq update context to release devfreq lock, because the context scheduled out from execution. This patch wakes up devfreq update context, it causes to break polling loop waiting for mmc host exclusive access (because devfreq_abourt countr > 0), so devfreq lock will be released and clock scaling may be successfully suspended. Change-Id: I3d1e7b38d7d281594b49d8452198ed4c1e550b73 Signed-off-by: Konstantin Dorfman --- drivers/mmc/core/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 75490fad9851..0f2c693696d5 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -746,6 +746,7 @@ int mmc_suspend_clk_scaling(struct mmc_host *host) } atomic_inc(&host->clk_scaling.devfreq_abort); + wake_up(&host->wq); err = devfreq_suspend_device(host->clk_scaling.devfreq); if (err) { pr_err("%s: %s: failed to suspend devfreq\n", From 828371cf1651bada888a4c52d99dbb2999abe17c Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Tue, 6 Oct 2015 09:53:33 +0530 Subject: [PATCH 431/472] mmc: cmdq: decrease the QSR polling period Configure the controller to send the QSR at 1 clock period. This would send SQS(CMD13) when no data transfer is in progress. This decrease in the polling period increases the performance of the device. CRs-fixed: 891366 Change-Id: Ic2807c6334a778b5f0c89fb605c6923a44f7624a Signed-off-by: Asutosh Das Signed-off-by: Sahitya Tummala --- drivers/mmc/host/cmdq_hci.c | 3 +-- drivers/mmc/host/cmdq_hci.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index de55c0649ea3..e68e94ad909d 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -357,8 +357,7 @@ static int cmdq_enable(struct mmc_host *mmc) cmdq_writel(cq_host, mmc->card->rca, CQSSC2); /* send QSR at lesser intervals than the default */ - cmdq_writel(cq_host, cmdq_readl(cq_host, CQSSC1) | SEND_QSR_INTERVAL, - CQSSC1); + cmdq_writel(cq_host, SEND_QSR_INTERVAL, CQSSC1); /* enable bkops exception indication */ if (mmc_card_configured_manual_bkops(mmc->card) && diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index a17c5c29ccb8..53096fb3a2aa 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -78,7 +78,7 @@ * Value n means CQE would send CMD13 during the transfer of data block * BLOCK_CNT-n */ -#define SEND_QSR_INTERVAL 0x70000 +#define SEND_QSR_INTERVAL 0x70001 /* send status config 2 */ #define CQSSC2 0x44 From 13e2446bf85d8679095c24bb450cc6007d4bbb24 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 21 Dec 2015 14:43:42 +0530 Subject: [PATCH 432/472] mmc: sdhci: revert MMC_PM_KEEP_POWER changes related to emmc This change reverts the following gerrits as they cause the timeout issues with Hynix cards. Also, the latest code supports sleep/awake through CMD5 and hence MMC_PM_KEEP_POWER need not be set for Hynix eMMC cards. edcf5be "mmc: host: add detect vops chain" 09287cb "mmc: sdhci-msm: configure MMC_PM_KEEP_POWER for SDIO" 7cf603a "mmc: schci: add support for MMC_PM_KEEP_POWER in eMMC" c085820 "mmc: core: set MMC_PM_KEEP_POWER for certain Hynix mmc cards" CRs-Fixed: 947299 Change-Id: If863771191ee7c2b717d5817f4a88e4ad936653a Signed-off-by: Sahitya Tummala [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 22 ---------------------- drivers/mmc/core/sdio.c | 3 --- drivers/mmc/host/sdhci-msm.c | 14 -------------- drivers/mmc/host/sdhci.c | 19 ------------------- drivers/mmc/host/sdhci.h | 1 - include/linux/mmc/card.h | 1 - include/linux/mmc/host.h | 2 -- 7 files changed, 62 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 39bf4455d16a..818a70b4007d 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -126,12 +126,6 @@ static void mmc_set_erase_size(struct mmc_card *card) mmc_init_erase(card); } -static void add_pm_flag_mmc(struct mmc_card *card, int data) -{ - if (mmc_card_mmc(card)) - card->host->pm_flags |= data; -} - static const struct mmc_fixup mmc_fixups[] = { /* avoid HPI for specific cards */ @@ -142,9 +136,6 @@ static const struct mmc_fixup mmc_fixups[] = { MMC_FIXUP("MMC16G", CID_MANFID_KINGSTON, CID_OEMID_ANY, add_quirk_mmc, MMC_QUIRK_CACHE_DISABLE), - MMC_FIXUP_EXT_CSD_REV(CID_NAME_ANY, CID_MANFID_HYNIX, CID_OEMID_ANY, - add_pm_flag_mmc, MMC_PM_KEEP_POWER, MMC_V5_0), - END_FIXUP }; @@ -391,9 +382,6 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) */ card->ext_csd.rev = ext_csd[EXT_CSD_REV]; - /* fixup device after ext_csd revision field is updated */ - mmc_fixup_device(card, mmc_fixups); - card->ext_csd.raw_sectors[0] = ext_csd[EXT_CSD_SEC_CNT + 0]; card->ext_csd.raw_sectors[1] = ext_csd[EXT_CSD_SEC_CNT + 1]; card->ext_csd.raw_sectors[2] = ext_csd[EXT_CSD_SEC_CNT + 2]; @@ -2490,9 +2478,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) if (mmc_card_suspended(host->card)) goto out; - if (is_suspend) - host->dev_status = DEV_SUSPENDING; - if (host->card->cmdq_init) { BUG_ON(host->cmdq_ctx.active_reqs); @@ -2535,11 +2520,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend) mmc_card_set_suspended(host->card); } out: - if (err) - host->dev_status = DEV_UNKNOWN; - else if (is_suspend) - host->dev_status = DEV_SUSPENDED; - /* Kick CMDQ thread to process any requests came in while suspending */ if (host->card->cmdq_init) wake_up(&host->cmdq_ctx.wait); @@ -2693,8 +2673,6 @@ static int _mmc_resume(struct mmc_host *host) mmc_hostname(host), __func__, err); out: - if (!err) - host->dev_status = DEV_RESUMED; return err; } diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index e30936281a0d..059c39f62e27 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -941,9 +941,6 @@ static void mmc_sdio_detect(struct mmc_host *host) */ err = _mmc_detect_card_removed(host); - if (host->ops && host->ops->detect) - host->ops->detect(host, err); - mmc_release_host(host); /* diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 644906b5252a..f7607a73880a 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3157,19 +3157,6 @@ static void sdhci_msm_clear_set_dumpregs(struct sdhci_host *host, bool set) } } -static void sdhci_msm_detect(struct sdhci_host *host, bool detected) -{ - struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); - struct sdhci_msm_host *msm_host = pltfm_host->priv; - struct mmc_host *mmc = msm_host->mmc; - struct mmc_card *card = mmc->card; - - if (detected && mmc_card_sdio(card)) - mmc->pm_caps |= MMC_PM_KEEP_POWER; - else - mmc->pm_caps &= ~MMC_PM_KEEP_POWER; -} - int sdhci_msm_notify_load(struct sdhci_host *host, enum mmc_load state) { struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); @@ -3722,7 +3709,6 @@ static struct sdhci_ops sdhci_msm_ops = { .reset = sdhci_msm_reset, .clear_set_dumpregs = sdhci_msm_clear_set_dumpregs, .enhanced_strobe_mask = sdhci_msm_enhanced_strobe_mask, - .detect = sdhci_msm_detect, .notify_load = sdhci_msm_notify_load, .reset_workaround = sdhci_msm_reset_workaround, .init = sdhci_msm_init, diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 4d4d68fb120a..50aa706d1d01 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1402,16 +1402,6 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned char mode, struct mmc_host *mmc = host->mmc; u8 pwr = 0; - /* - * Don't disable/re-enable power to the card when running a - * suspend/resume sequence and the pm_flags are configured to preserve - * card power during suspend. - */ - if (mmc_card_keep_power(mmc) && - ((mmc->dev_status == DEV_SUSPENDED && mode == MMC_POWER_UP) || - (mmc->dev_status == DEV_SUSPENDING && mode == MMC_POWER_OFF))) - return; - if (!IS_ERR(mmc->supply.vmmc)) { spin_unlock_irq(&host->lock); mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); @@ -2632,14 +2622,6 @@ static void sdhci_card_event(struct mmc_host *mmc) spin_unlock_irqrestore(&host->lock, flags); } -static void sdhci_detect(struct mmc_host *mmc, bool detected) -{ - struct sdhci_host *host = mmc_priv(mmc); - - if (host->ops->detect) - host->ops->detect(host, detected); -} - static int sdhci_late_init(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); @@ -2682,7 +2664,6 @@ static const struct mmc_host_ops sdhci_ops = { .disable = sdhci_disable, .notify_load = sdhci_notify_load, .notify_halt = sdhci_notify_halt, - .detect = sdhci_detect, .force_err_irq = sdhci_force_err_irq, }; diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index b2e6d3958518..280eafd36ec9 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -670,7 +670,6 @@ struct sdhci_ops { int (*enable_controller_clock)(struct sdhci_host *host); void (*clear_set_dumpregs)(struct sdhci_host *host, bool set); void (*enhanced_strobe_mask)(struct sdhci_host *host, bool set); - void (*detect)(struct sdhci_host *host, bool detected); void (*dump_vendor_regs)(struct sdhci_host *host); void (*toggle_cdr)(struct sdhci_host *host, bool enable); void (*voltage_switch)(struct sdhci_host *host); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 5ee1a379b82b..d88f6a027679 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -485,7 +485,6 @@ struct mmc_fixup { #define CID_MANFID_MICRON 0x13 #define CID_MANFID_SAMSUNG 0x15 #define CID_MANFID_KINGSTON 0x70 -#define CID_MANFID_HYNIX 0x90 #define CID_MANFID_ANY (-1u) #define CID_OEMID_ANY ((unsigned short) -1) diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 38731725cc80..cd79dd903a8b 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -181,7 +181,6 @@ struct mmc_host_ops { int (*notify_load)(struct mmc_host *, enum mmc_load); void (*notify_halt)(struct mmc_host *mmc, bool halt); - void (*detect)(struct mmc_host *host, bool detected); void (*force_err_irq)(struct mmc_host *host, u64 errmask); }; @@ -291,7 +290,6 @@ enum dev_state { DEV_SUSPENDING = 1, DEV_SUSPENDED, DEV_RESUMED, - DEV_UNKNOWN, /* Device is in an unknown state */ }; /** From 68393ba3719348673f17a0b999f695bb572e0ec3 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Thu, 1 Oct 2015 11:41:29 +0530 Subject: [PATCH 433/472] mmc: sdhci: Handle cmdq_irq before clearing error interrupt. cmdq_irq should be handled before clearning error interrupt. Because in case of an error CQE needs to be halted/disabled in cmdq_irq, before we clear the error interrupt. Otherwise, CQE might start processing other requests whose doorbell is set, even in case of error in previous completed request. Change-Id: I8c77ca08dcf440293844120c1b59d2dada84bac5 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 50aa706d1d01..bb53c8eb5e6e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3047,19 +3047,21 @@ static irqreturn_t sdhci_cmdq_irq(struct sdhci_host *host, u32 intmask) { int err = 0; u32 mask = 0; + irqreturn_t ret; if (intmask & SDHCI_INT_CMD_MASK) err = sdhci_get_cmd_err(intmask); else if (intmask & SDHCI_INT_DATA_MASK) err = sdhci_get_data_err(intmask); + ret = cmdq_irq(host->mmc, err); if (err) { /* Clear the error interrupts */ mask = intmask & SDHCI_INT_ERROR_MASK; sdhci_writel(host, mask, SDHCI_INT_STATUS); } + return ret; - return cmdq_irq(host->mmc, err); } #else From 2f69844aa86701a0a4c1ea8ca8fa8e55506a1329 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Tue, 27 Oct 2015 11:51:25 +0530 Subject: [PATCH 434/472] mmc: cmdq_hci: Helper API/info in cmdq for halt This patch adds following helper API/info - 1. cmdq_halt_poll to halt the controller using polling method. This is to be mainly used in case of an error from cmdq_irq context. 2. Adds num_cq_slots & dcmd_cq_slot info to mmc_host structure. This can be useful info for mmc host structure like in case of handling of multiple error requests 3. Adds CMDQ_STATE_CQ_DISABLE for cmdq host. In case of an error if halt also fails, CQE error handling code will disable CQ. So block layer needs to know - to not pull any requests in such case. Change-Id: I8e9a8d5094db82336917fcca4361ce84316c34ef Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/cmdq_hci.c | 65 +++++++++++++++++++++++++++++++++++++ include/linux/mmc/host.h | 18 ++++++++++ 2 files changed, 83 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index e68e94ad909d..ba9ec9d85d6f 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -37,6 +37,8 @@ /* 1 sec */ #define HALT_TIMEOUT_MS 1000 +static int cmdq_halt_poll(struct mmc_host *mmc); + #ifdef CONFIG_PM_RUNTIME static int cmdq_runtime_pm_get(struct cmdq_host *host) { @@ -116,6 +118,20 @@ static void setup_trans_desc(struct cmdq_host *cq_host, u8 tag) } } +static void cmdq_set_halt_irq(struct cmdq_host *cq_host, bool enable) +{ + u32 ier; + + ier = cmdq_readl(cq_host, CQISTE); + if (enable) { + cmdq_writel(cq_host, ier | HALT, CQISTE); + cmdq_writel(cq_host, ier | HALT, CQISGE); + } else { + cmdq_writel(cq_host, ier & ~HALT, CQISTE); + cmdq_writel(cq_host, ier & ~HALT, CQISGE); + } +} + static void cmdq_clear_set_irqs(struct cmdq_host *cq_host, u32 clear, u32 set) { u32 ier; @@ -369,6 +385,7 @@ static int cmdq_enable(struct mmc_host *mmc) mb(); cq_host->enabled = true; + mmc_host_clr_cq_disable(mmc); if (cq_host->ops->set_block_size) cq_host->ops->set_block_size(cq_host->mmc); @@ -403,6 +420,7 @@ static void cmdq_disable(struct mmc_host *mmc, bool soft) cmdq_runtime_pm_put(cq_host); cq_host->enabled = false; + mmc_host_set_cq_disable(mmc); } static void cmdq_reset(struct mmc_host *mmc, bool soft) @@ -448,6 +466,7 @@ static void cmdq_reset(struct mmc_host *mmc, bool soft) cmdq_writel(cq_host, cqcfg, CQCFG); cmdq_runtime_pm_put(cq_host); cq_host->enabled = true; + mmc_host_clr_cq_disable(mmc); } static void cmdq_prep_task_desc(struct mmc_request *mrq, @@ -729,6 +748,7 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); unsigned long err_info = 0; struct mmc_request *mrq; + int ret; status = cmdq_readl(cq_host, CQIS); cmdq_writel(cq_host, status, CQIS); @@ -741,6 +761,17 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) pr_err("%s: err: %d status: 0x%08x task-err-info (0x%08lx)\n", mmc_hostname(mmc), err, status, err_info); + /* + * Need to halt CQE in case of error in interrupt context itself + * otherwise CQE may proceed with sending CMD to device even if + * CQE/card is in error state. + * CMDQ error handling will make sure that it is unhalted after + * handling all the errors. + */ + ret = cmdq_halt_poll(mmc); + if (ret) + pr_err("%s: %s: halt failed ret=%d\n", + mmc_hostname(mmc), __func__, ret); cmdq_dumpregs(cq_host); if (err_info & CQ_RMEFV) { @@ -823,6 +854,38 @@ out: } EXPORT_SYMBOL(cmdq_irq); +/* cmdq_halt_poll - Halting CQE using polling method. + * @mmc: struct mmc_host + * This is used mainly from interrupt context to halt + * CQE engine. + */ +static int cmdq_halt_poll(struct mmc_host *mmc) +{ + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + int retries = 100; + + cmdq_set_halt_irq(cq_host, false); + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) | HALT, CQCTL); + while (retries) { + if (!(cmdq_readl(cq_host, CQCTL) & HALT)) { + udelay(5); + retries--; + continue; + } else { + if (cq_host->ops->post_cqe_halt) + cq_host->ops->post_cqe_halt(mmc); + /* halt done: re-enable legacy interrupts */ + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, + false); + mmc_host_set_halt(mmc); + break; + } + } + cmdq_set_halt_irq(cq_host, true); + return retries ? 0 : -ETIMEDOUT; +} + /* May sleep */ static int cmdq_halt(struct mmc_host *mmc, bool halt) { @@ -965,6 +1028,8 @@ int cmdq_init(struct cmdq_host *cq_host, struct mmc_host *mmc, cq_host->dcmd_slot = DCMD_SLOT; mmc->cmdq_ops = &cmdq_host_ops; + mmc->num_cq_slots = NUM_SLOTS; + mmc->dcmd_cq_slot = DCMD_SLOT; cq_host->mrq_slot = kzalloc(sizeof(cq_host->mrq_slot) * cq_host->num_slots, GFP_KERNEL); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index cd79dd903a8b..4743f46bf9b3 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -257,6 +257,7 @@ struct mmc_cmdq_context_info { #define CMDQ_STATE_ERR 0 #define CMDQ_STATE_DCMD_ACTIVE 1 #define CMDQ_STATE_HALT 2 +#define CMDQ_STATE_CQ_DISABLE 3 wait_queue_head_t queue_empty_wq; wait_queue_head_t wait; int active_small_sector_read_reqs; @@ -569,6 +570,8 @@ struct mmc_host { enum dev_state dev_status; bool wakeup_on_idle; struct mmc_cmdq_context_info cmdq_ctx; + int num_cq_slots; + int dcmd_cq_slot; bool cmdq_thist_enabled; /* * several cmdq supporting host controllers are extensions @@ -717,6 +720,21 @@ static inline int mmc_host_halt(struct mmc_host *host) return test_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); } +static inline void mmc_host_set_cq_disable(struct mmc_host *host) +{ + set_bit(CMDQ_STATE_CQ_DISABLE, &host->cmdq_ctx.curr_state); +} + +static inline void mmc_host_clr_cq_disable(struct mmc_host *host) +{ + clear_bit(CMDQ_STATE_CQ_DISABLE, &host->cmdq_ctx.curr_state); +} + +static inline int mmc_host_cq_disable(struct mmc_host *host) +{ + return test_bit(CMDQ_STATE_CQ_DISABLE, &host->cmdq_ctx.curr_state); +} + #ifdef CONFIG_MMC_CLKGATE void mmc_host_clk_hold(struct mmc_host *host); void mmc_host_clk_release(struct mmc_host *host); From d906a5c18b0366692215cbdc74fb5fd45cb01114 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 18 Dec 2015 17:52:54 -0800 Subject: [PATCH 435/472] mmc: block: Fix use after free issue with request pointer Accessing the request pointer after submitting the request could result in use after free as the request could be completed and freed by the time its accessed. Fix the usage appropriately. Kasan report: [ 55.025818] ================================================================== [ 55.032035] BUG: KASAN: use-after-free in mmc_blk_cmdq_issue_rq+0xd58/0xe20 at addr ffffffc04c5119ac [ 55.041134] Read of size 4 by task mmc-cmdqd/0/343 [ 55.045905] ============================================================================= [ 55.054069] BUG blkdev_requests (Tainted: G W ): kasan: bad access detected [ 55.061958] ----------------------------------------------------------------------------- [ 55.061958] [ 55.071609] INFO: Allocated in mempool_alloc_slab+0x18/0x20 age=2 cpu=1 pid=1105 [ 55.078975] alloc_debug_processing+0x118/0x170 [ 55.083491] __slab_alloc.isra.20.constprop.22+0x2a4/0x3a0 [ 55.088954] kmem_cache_alloc+0xb0/0x228 [ 55.092865] mempool_alloc_slab+0x14/0x20 [ 55.096853] mempool_alloc+0xdc/0x1ec [ 55.100507] get_request+0x3c4/0x838 [ 55.104060] blk_queue_bio+0x1f0/0x448 [ 55.107791] generic_make_request+0x13c/0x1bc [ 55.112136] submit_bio+0x154/0x2b4 [ 55.115606] mpage_bio_submit+0x3c/0x50 [ 55.119423] mpage_readpages+0x140/0x17c [ 55.123334] blkdev_readpages+0x1c/0x28 [ 55.127153] __do_page_cache_readahead+0x218/0x2ec [ 55.131930] ondemand_readahead+0x2cc/0x2f0 [ 55.136091] page_cache_sync_readahead+0x7c/0x94 [ 55.140697] ext4_readdir+0xb34/0xb78 [ 55.144347] INFO: Freed in mempool_free_slab+0x18/0x20 age=12 cpu=0 pid=603 [ 55.151287] free_debug_processing+0x240/0x2f0 [ 55.155709] __slab_free+0x44/0x374 [ 55.159179] kmem_cache_free+0x1d8/0x264 [ 55.163092] mempool_free_slab+0x14/0x20 [ 55.166991] mempool_free+0xd0/0xec [ 55.170468] __blk_put_request+0x168/0x1ac [ 55.174546] blk_finish_request+0x110/0x124 [ 55.178713] blk_end_bidi_request+0x70/0xa0 [ 55.182880] blk_end_request+0xc/0x18 [ 55.186527] mmc_blk_cmdq_complete_rq+0x1fc/0x284 [ 55.191216] mmc_cmdq_softirq_done+0x38/0x48 [ 55.195467] blk_done_softirq+0x130/0x160 [ 55.199461] __do_softirq+0x280/0x528 [ 55.203105] irq_exit+0x9c/0x114 [ 55.206317] __handle_domain_irq+0xc4/0x110 [ 55.210486] gic_handle_irq+0x5c/0xd8 [ 55.214130] INFO: Slab 0xffffffba48c77b00 objects=25 used=1 fp=0xffffffc04c510798 flags=0x4080 [ 55.222723] INFO: Object 0xffffffc04c511950 @offset=6480 fp=0xffffffc0aed4e408 [ 55.222723] [ 55.231407] Bytes b4 ffffffc04c511940: 00 00 00 00 00 00 00 00 a8 9f ff ff 00 00 00 00 ................ [ 55.240870] Object ffffffc04c511950: 08 e4 d4 ae c0 ff ff ff 08 e4 d4 ae c0 ff ff ff ................ [ 55.250161] Object ffffffc04c511960: 5d a0 ff ff 00 00 00 00 00 00 00 00 00 00 00 00 ]............... [ 55.259442] Object ffffffc04c511970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [ 55.268730] Object ffffffc04c511980: 00 39 6a 56 c0 ff ff ff 00 00 00 00 00 00 00 00 .9jV............ [ 55.278017] Object ffffffc04c511990: 00 00 41 24 01 00 00 00 01 00 00 00 00 00 00 00 ..A$............ [ 55.287306] Object ffffffc04c5119a0: 00 00 00 00 00 00 00 00 01 00 00 00 00 10 00 00 ................ [ 55.296595] Object ffffffc04c5119b0: 88 64 32 00 00 00 00 00 00 ea c2 b5 c0 ff ff ff .d2............. [ 55.305882] Object ffffffc04c5119c0: 00 ea c2 b5 c0 ff ff ff 00 00 00 00 00 00 00 00 ................ [ 55.315172] Object ffffffc04c5119d0: d8 75 c2 55 c0 ff ff ff 01 00 00 00 00 00 00 00 .u.U............ [ 55.324459] Object ffffffc04c5119e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [ 55.333747] Object ffffffc04c5119f0: 00 8d fb 54 c0 ff ff ff 98 e3 d4 ae c0 ff ff ff ...T............ [ 55.343035] Object ffffffc04c511a00: 00 80 f7 b6 c0 ff ff ff 00 00 00 00 00 00 00 00 ................ [ 55.352323] Object ffffffc04c511a10: 40 53 e3 b6 c0 ff ff ff 80 0a 56 55 c0 ff ff ff @S........VU.... [ 55.361613] Object ffffffc04c511a20: 51 a0 ff ff 00 00 00 00 01 00 00 00 00 00 00 00 Q............... [ 55.370901] Object ffffffc04c511a30: 00 00 00 00 00 00 00 00 ff ff ff ff 00 00 00 00 ................ [ 55.380187] Object ffffffc04c511a40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [ 55.389475] Object ffffffc04c511a50: 40 1a 51 4c c0 ff ff ff 10 00 00 00 00 00 00 00 @.QL............ [ 55.398764] Object ffffffc04c511a60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [ 55.408053] Object ffffffc04c511a70: 00 00 00 00 00 00 00 00 78 1a 51 4c c0 ff ff ff ........x.QL.... [ 55.417342] Object ffffffc04c511a80: 78 1a 51 4c c0 ff ff ff 00 00 00 00 00 00 00 00 x.QL............ [ 55.426628] Object ffffffc04c511a90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [ 55.435917] Object ffffffc04c511aa0: 00 00 00 00 00 00 00 00 ........ [ 55.444534] Call trace: [ 55.447073] Memory state around the buggy address: [ 55.451719] ffffffc04c511880: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc [ 55.458920] ffffffc04c511900: fc fc fc fc fc fc fc fc fc fc 00 00 00 00 00 00 [ 55.466126] >ffffffc04c511980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 55.473328] ^ [ 55.477844] ffffffc04c511a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 55.485050] ffffffc04c511a80: 00 00 00 00 00 fc fc fc fc fc fc fc fc fc fc fc Change-Id: I24fdca1b4562fd7c1f3a1584d1efccd94ed6698a Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/block.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 79211da9117e..3d6def7f44be 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2898,6 +2898,7 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) struct mmc_host *host = card->host; struct mmc_cmdq_context_info *ctx = &host->cmdq_ctx; struct mmc_cmdq_req *mc_rq; + u8 active_small_sector_read = 0; int ret = 0; mmc_deferred_scaling(host); @@ -2912,15 +2913,16 @@ static int mmc_blk_cmdq_issue_rw_rq(struct mmc_queue *mq, struct request *req) mc_rq = mmc_blk_cmdq_rw_prep(active_mqrq, mq); - ret = mmc_blk_cmdq_start_req(card->host, mc_rq); - - if (!ret && (card->quirks & MMC_QUIRK_CMDQ_EMPTY_BEFORE_DCMD)) { + if (card->quirks & MMC_QUIRK_CMDQ_EMPTY_BEFORE_DCMD) { unsigned int sectors = blk_rq_sectors(req); if (((sectors > 0) && (sectors < 8)) && (rq_data_dir(req) == READ)) - host->cmdq_ctx.active_small_sector_read_reqs++; + active_small_sector_read = 1; } + ret = mmc_blk_cmdq_start_req(card->host, mc_rq); + if (!ret && active_small_sector_read) + host->cmdq_ctx.active_small_sector_read_reqs++; /* * When in SVS2 on low load scenario and there are lots of requests * queued for CMDQ we need to wait till the queue is empty to scale From c45bebe32dc3743b8b92a273b509e5e4f624a9d0 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Thu, 17 Dec 2015 19:59:04 +0530 Subject: [PATCH 436/472] mmc: sdhci-msm: Fix wakeup functionality for SDIO There could be a case where after platform device (sdhci_msm) suspend, (where external gpio to IRQ is configured for wakeup in case of sdio) external gpioIRQ is raised before system suspend is completed. To solve this problem we use a flag to signal sdhci_msm_suspend_noirq to abort suspend with -EBUSY signal. Change-Id: I82617d5a02674af24d330601e41fb3c20278f672 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 14 ++++++++++++-- drivers/mmc/host/sdhci-msm.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index f7607a73880a..36abd920a991 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2292,6 +2292,9 @@ static void sdhci_msm_cfg_sdiowakeup_gpio_irq(struct sdhci_host *host, static irqreturn_t sdhci_msm_sdiowakeup_irq(int irq, void *data) { struct sdhci_host *host = (struct sdhci_host *)data; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; + unsigned long flags; pr_debug("%s: irq (%d) received\n", __func__, irq); @@ -2299,6 +2302,7 @@ static irqreturn_t sdhci_msm_sdiowakeup_irq(int irq, void *data) spin_lock_irqsave(&host->lock, flags); sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); spin_unlock_irqrestore(&host->lock, flags); + msm_host->sdio_pending_processing = true; return IRQ_HANDLED; } @@ -4264,9 +4268,9 @@ static int sdhci_msm_probe(struct platform_device *pdev) msm_host->pdata->sdiowakeup_irq = platform_get_irq_byname(pdev, "sdiowakeup_irq"); - dev_info(&pdev->dev, "%s: sdiowakeup_irq = %d\n", __func__, - msm_host->pdata->sdiowakeup_irq); if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + dev_info(&pdev->dev, "%s: sdiowakeup_irq = %d\n", __func__, + msm_host->pdata->sdiowakeup_irq); msm_host->is_sdiowakeup_enabled = true; ret = request_irq(msm_host->pdata->sdiowakeup_irq, sdhci_msm_sdiowakeup_irq, @@ -4421,6 +4425,7 @@ static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable) if (enable) { /* configure DAT1 gpio if applicable */ if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { + msm_host->sdio_pending_processing = false; ret = enable_irq_wake(msm_host->pdata->sdiowakeup_irq); if (!ret) sdhci_msm_cfg_sdiowakeup_gpio_irq(host, true); @@ -4433,6 +4438,7 @@ static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable) if (sdhci_is_valid_gpio_wakeup_int(msm_host)) { ret = disable_irq_wake(msm_host->pdata->sdiowakeup_irq); sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + msm_host->sdio_pending_processing = false; } else { pr_err("%s: sdiowakeup_irq(%d)invalid\n", mmc_hostname(host->mmc), enable); @@ -4601,6 +4607,10 @@ static int sdhci_msm_suspend_noirq(struct device *dev) ret = -EAGAIN; } + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + if (msm_host->sdio_pending_processing) + ret = -EBUSY; + return ret; } diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 09949465d0cb..00785765a1ec 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -197,6 +197,7 @@ struct sdhci_msm_host { bool en_auto_cmd21; struct device_attribute auto_cmd21_attr; bool is_sdiowakeup_enabled; + bool sdio_pending_processing; atomic_t controller_clock; bool use_cdclp533; bool use_updated_dll_reset; From 808a742047376a331da608610ef495eb03205096 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 23 Nov 2015 13:04:35 +0530 Subject: [PATCH 437/472] mmc: queue: Check for CQE disable state Add a check in mmc_cmdq_ready_wait to not pull any requests in case if CQE is disabled and card is not suspended. This means that CQE must be in error state and so mmc_cmdq_thread should stop issuing requests. Change-Id: I0c5e6556d3d881f1a675db4fff4f995de024ddf8 Signed-off-by: Ritesh Harjani --- drivers/mmc/card/queue.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index fdbfdc0f4e3a..01e87f4480ae 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -100,6 +100,8 @@ static inline void mmc_cmdq_ready_wait(struct mmc_host *host, && test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) && !(!host->card->part_curr && !mmc_card_suspended(host->card) && mmc_host_halt(host)) + && !(!host->card->part_curr && mmc_host_cq_disable(host) && + !mmc_card_suspended(host->card)) && !test_bit(CMDQ_STATE_ERR, &ctx->curr_state) && !mmc_check_blk_queue_start_tag(q, mq->cmdq_req_peeked)); } From fc272f2bf29afe51979ca1c5d9cc72ae398db4b8 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Thu, 1 Oct 2015 20:34:42 +0530 Subject: [PATCH 438/472] mmc: cmdq_hci: Add CQE WA for CQE HW bugs This adds WA to handle few CQE error which can cause HALT to fail or can give CQTERRI info as NULL. Like - currently there is a HW bug that in case of ADMA error CQE will not stop and CQTERRI register will not be updated with any valid values. Halt will also most likely fail in case of ADMA error. Thus possible WA for this is to disable CQE, do reset_all and requeue all the requests in flight. Change-Id: I5aaddbb7bec1de7dbc959144dc2f1a5ad16789ff Signed-off-by: Ritesh Harjani --- drivers/mmc/host/cmdq_hci.c | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index ba9ec9d85d6f..a5f121516417 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -38,6 +38,7 @@ #define HALT_TIMEOUT_MS 1000 static int cmdq_halt_poll(struct mmc_host *mmc); +static int cmdq_halt(struct mmc_host *mmc, bool halt); #ifdef CONFIG_PM_RUNTIME static int cmdq_runtime_pm_get(struct cmdq_host *host) @@ -173,6 +174,27 @@ static void cmdq_dump_task_history(struct cmdq_host *cq_host) pr_err("-------------------------\n"); } +static void cmdq_dump_adma_mem(struct cmdq_host *cq_host) +{ + struct mmc_host *mmc = cq_host->mmc; + dma_addr_t desc_dma; + int tag = 0; + unsigned long data_active_reqs = + mmc->cmdq_ctx.data_active_reqs; + unsigned long desc_size = + (cq_host->mmc->max_segs * cq_host->trans_desc_len); + + for_each_set_bit(tag, &data_active_reqs, cq_host->num_slots) { + desc_dma = get_trans_desc_dma(cq_host, tag); + pr_err("%s: %s: tag = %d, trans_dma(phys) = %pad, trans_desc(virt) = 0x%p\n", + mmc_hostname(mmc), __func__, tag, + &desc_dma, get_trans_desc(cq_host, tag)); + print_hex_dump(KERN_ERR, "cmdq-adma:", DUMP_PREFIX_ADDRESS, + 32, 8, get_trans_desc(cq_host, tag), + (desc_size), false); + } +} + static void cmdq_dumpregs(struct cmdq_host *cq_host) { struct mmc_host *mmc = cq_host->mmc; @@ -749,6 +771,7 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) unsigned long err_info = 0; struct mmc_request *mrq; int ret; + u32 dbr_set = 0; status = cmdq_readl(cq_host, CQIS); cmdq_writel(cq_host, status, CQIS); @@ -774,6 +797,43 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) mmc_hostname(mmc), __func__, ret); cmdq_dumpregs(cq_host); + if (!err_info) { + /* + * It may so happen sometimes for few errors(like ADMA) + * that HW cannot give CQTERRI info. + * Thus below is a HW WA for recovering from such + * scenario. + * - To halt/disable CQE and do reset_all. + * Since there is no way to know which tag would + * have caused such error, so check for any first + * bit set in doorbell and proceed with an error. + */ + dbr_set = cmdq_readl(cq_host, CQTDBR); + if (!dbr_set) { + pr_err("%s: spurious/force error interrupt\n", + mmc_hostname(mmc)); + cmdq_halt(mmc, false); + mmc_host_clr_halt(mmc); + return IRQ_HANDLED; + } + + tag = ffs(dbr_set) - 1; + pr_err("%s: error tag selected: tag = %lu\n", + mmc_hostname(mmc), tag); + mrq = get_req_by_tag(cq_host, tag); + if (mrq->data) + mrq->data->error = err; + else + mrq->cmd->error = err; + /* + * Get ADMA descriptor memory in case of ADMA + * error for debug. + */ + if (err == -EIO) + cmdq_dump_adma_mem(cq_host); + goto skip_cqterri; + } + if (err_info & CQ_RMEFV) { tag = GET_CMD_ERR_TAG(err_info); pr_err("%s: CMD err tag: %lu\n", __func__, tag); @@ -791,6 +851,14 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) mrq->data->error = err; } +skip_cqterri: + /* + * If CQE halt fails then, disable CQE + * from processing any further requests + */ + if (ret) + cmdq_disable(mmc, true); + /* * CQE detected a reponse error from device * In most cases, this would require a reset. From 84de99e1ded4e96ac36c2287b7d9e4b9cdd8dcc4 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Sun, 27 Sep 2015 21:51:01 +0530 Subject: [PATCH 439/472] mmc: core: Call cmdq_post_req with tag info instead of mrq Call mmc_/cmdq_post_req with tag number instead of mrq. This is needed in error handling as part of multiple request error handling. Change-Id: I175432639d28378ec74669e31deb4d1667c49bb8 Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 2 +- drivers/mmc/core/core.c | 6 +++--- drivers/mmc/host/cmdq_hci.c | 18 +++++++++++++----- include/linux/mmc/core.h | 3 +-- include/linux/mmc/host.h | 3 +-- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 3d6def7f44be..a353130806ec 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3200,7 +3200,7 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) BUG_ON(!test_and_clear_bit(cmdq_req->tag, &ctx_info->data_active_reqs)); - mmc_cmdq_post_req(host, mrq, err); + mmc_cmdq_post_req(host, cmdq_req->tag, err); if (err) { pr_err("%s: %s: txfr error: %d\n", mmc_hostname(mrq->host), __func__, err); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0f2c693696d5..1a972207f299 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1512,13 +1512,13 @@ EXPORT_SYMBOL(mmc_cmdq_discard_queue); /** * mmc_cmdq_post_req - post process of a completed request * @host: host instance - * @mrq: the request to be processed + * @tag: the request tag. * @err: non-zero is error, success otherwise */ -void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, int err) +void mmc_cmdq_post_req(struct mmc_host *host, int tag, int err) { if (likely(host->cmdq_ops->post_req)) - host->cmdq_ops->post_req(host, mrq, err); + host->cmdq_ops->post_req(host, tag, err); } EXPORT_SYMBOL(mmc_cmdq_post_req); diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index a5f121516417..4c5817776dfe 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -992,15 +992,23 @@ static int cmdq_halt(struct mmc_host *mmc, bool halt) return ret; } -static void cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, - int err) +static void cmdq_post_req(struct mmc_host *mmc, int tag, int err) { - struct mmc_data *data = mrq->data; - struct sdhci_host *sdhci_host = mmc_priv(host); + struct cmdq_host *cq_host; + struct mmc_request *mrq; + struct mmc_data *data; + struct sdhci_host *sdhci_host = mmc_priv(mmc); + + if (WARN_ON(!mmc)) + return; + + cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + mrq = get_req_by_tag(cq_host, tag); + data = mrq->data; if (data) { data->error = err; - dma_unmap_sg(mmc_dev(host), data->sg, data->sg_len, + dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len, (data->flags & MMC_DATA_READ) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); if (err) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 87332656e0fd..8b5197ba6759 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -136,8 +136,7 @@ struct mmc_cmdq_req; extern int mmc_cmdq_discard_queue(struct mmc_host *host, u32 tasks); extern int mmc_cmdq_halt(struct mmc_host *host, bool enable); extern int mmc_cmdq_halt_on_empty_queue(struct mmc_host *host); -extern void mmc_cmdq_post_req(struct mmc_host *host, struct mmc_request *mrq, - int err); +extern void mmc_cmdq_post_req(struct mmc_host *host, int tag, int err); extern int mmc_cmdq_start_req(struct mmc_host *host, struct mmc_cmdq_req *cmdq_req); extern int mmc_cmdq_prepare_flush(struct mmc_command *cmd); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 4743f46bf9b3..b923a74add1d 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -95,8 +95,7 @@ struct mmc_cmdq_host_ops { int (*enable)(struct mmc_host *host); void (*disable)(struct mmc_host *host, bool soft); int (*request)(struct mmc_host *host, struct mmc_request *mrq); - void (*post_req)(struct mmc_host *host, struct mmc_request *mrq, - int err); + void (*post_req)(struct mmc_host *host, int tag, int err); int (*halt)(struct mmc_host *host, bool halt); void (*reset)(struct mmc_host *host, bool soft); void (*dumpstate)(struct mmc_host *host); From bbd570a9b13e15b7b16034157b67f464f88f9546 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 28 Sep 2015 15:55:01 +0530 Subject: [PATCH 440/472] mmc: core: cmdq helper for reset and claim host context This patch does following- This adds an API(mmc_cmdq_hw_reset), for RESET_ALL of SDHCI, power cycle mmc card and reintialize it, which enables cmdq as well(if supported). This acquires claim_host before calling mmc_power_restore. mmc_power_restore should be called with claim_host acquired, since this function is required from non-claim-host context in cmdq error handling. Change-Id: I31c4449dead1d4ad4e10a4822cce2298ec5fb4b6 Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed merge conflicts, dropped some changes related to mmc_do_hw_reset] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 19 +++++++++++++++++++ include/linux/mmc/core.h | 1 + 2 files changed, 20 insertions(+) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 1a972207f299..64d5d57eb087 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -3826,6 +3826,23 @@ static void mmc_hw_reset_for_init(struct mmc_host *host) mmc_host_clk_release(host); } +/* + * mmc_cmdq_hw_reset: Helper API for doing + * reset_all of host and reinitializing card. + * This must be called with mmc_claim_host + * acquired by the caller. + */ +int mmc_cmdq_hw_reset(struct mmc_host *host) +{ + if (!host->bus_ops->power_restore) + return -EOPNOTSUPP; + + mmc_power_cycle(host, host->ocr_avail); + mmc_select_voltage(host, host->card->ocr); + return host->bus_ops->power_restore(host); +} +EXPORT_SYMBOL(mmc_cmdq_hw_reset); + int mmc_hw_reset(struct mmc_host *host) { int ret; @@ -4117,7 +4134,9 @@ int mmc_power_restore_host(struct mmc_host *host) } mmc_power_up(host, host->card->ocr); + mmc_claim_host(host); ret = host->bus_ops->power_restore(host); + mmc_release_host(host); mmc_bus_put(host); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 8b5197ba6759..89efaa67cc59 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -191,6 +191,7 @@ extern int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen); extern int mmc_set_blockcount(struct mmc_card *card, unsigned int blockcount, bool is_rel_write); extern int mmc_hw_reset(struct mmc_host *host); +extern int mmc_cmdq_hw_reset(struct mmc_host *host); extern int mmc_can_reset(struct mmc_card *card); extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); From b90443c63bacb73da137a13811500816a07194ae Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 7 Oct 2015 16:42:47 +0530 Subject: [PATCH 441/472] mmc: block: Error handling fixes and support of reset_all This does following: 1. Adds support to handle multiple cmdq software requests timeout. Currently we schedule error handling work for the first timedout request. We requeue all pending tasks, knock off all tasks from device queue and reset card and controller as part of this, then for all other software requests timeout, if it comes while executing error work, we return BLK_EH_NOT_HANDLED. This fixes BUG_ON in case of multiple requests timesout together. 2. Current code resets CQE, power cycle the card and requeue all the requests in case of any error. 3. mmc_blk_cmdq_err work takes care freeing up all resource allocation like clk and rpm hold, in case of reset_all. 4. Currently we were never clearing error CMDQ_STATE_ERR in case of any error. This patch takes care of this bug. Change-Id: I83d483c11fe2d7f2e462086cc3c0932057304c0d Signed-off-by: Ritesh Harjani [subhashj@codeaurora.org: fixed compilation error] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 260 ++++++++++++++++++++++++------------ drivers/mmc/host/cmdq_hci.c | 1 - include/linux/mmc/host.h | 1 + 3 files changed, 173 insertions(+), 89 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index a353130806ec..026886b949bb 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -2984,24 +2984,122 @@ EXPORT_SYMBOL(mmc_blk_cmdq_issue_flush_rq); static void mmc_blk_cmdq_reset(struct mmc_host *host, bool clear_all) { - if (!host->cmdq_ops->reset) - return; + int err = 0; - if (!test_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state)) { - if (mmc_cmdq_halt(host, true)) { - pr_err("%s: halt failed\n", mmc_hostname(host)); - goto reset; - } + if (mmc_cmdq_halt(host, true)) { + pr_err("%s: halt failed\n", mmc_hostname(host)); + goto reset; } if (clear_all) mmc_cmdq_discard_queue(host, 0); reset: - mmc_hw_reset(host); mmc_host_clk_hold(host); - host->cmdq_ops->reset(host, true); + host->cmdq_ops->disable(host, true); mmc_host_clk_release(host); - clear_bit(CMDQ_STATE_HALT, &host->cmdq_ctx.curr_state); + err = mmc_cmdq_hw_reset(host); + if (err && err != -EOPNOTSUPP) { + pr_err("%s: failed to cmdq_hw_reset err = %d\n", + mmc_hostname(host), err); + mmc_host_clk_hold(host); + host->cmdq_ops->enable(host); + mmc_host_clk_release(host); + mmc_cmdq_halt(host, false); + goto out; + } + /* + * CMDQ HW reset would have already made CQE + * in unhalted state, but reflect the same + * in software state of cmdq_ctx. + */ + mmc_host_clr_halt(host); +out: + return; +} + +/** + * is_cmdq_dcmd_req - Checks if tag belongs to DCMD request. + * @q: request_queue pointer. + * @tag: tag number of request to check. + * + * This function checks if the request with tag number "tag" + * is a DCMD request or not based on cmdq_req_flags set. + * + * returns true if DCMD req, otherwise false. + */ +static bool is_cmdq_dcmd_req(struct request_queue *q, int tag) +{ + struct request *req; + struct mmc_queue_req *mq_rq; + struct mmc_cmdq_req *cmdq_req; + + req = blk_queue_find_tag(q, tag); + if (WARN_ON(!req)) + goto out; + mq_rq = req->special; + if (WARN_ON(!mq_rq)) + goto out; + cmdq_req = &(mq_rq->cmdq_req); + return (cmdq_req->cmdq_req_flags & DCMD); +out: + return -ENOENT; +} + +/** + * mmc_blk_cmdq_reset_all - Reset everything for CMDQ block request. + * @host: mmc_host pointer. + * @err: error for which reset is performed. + * + * This function implements reset_all functionality for + * cmdq. It resets the controller, power cycle the card, + * and invalidate all busy tags(requeue all request back to + * elevator). + */ +static void mmc_blk_cmdq_reset_all(struct mmc_host *host, int err) +{ + struct mmc_request *mrq = host->err_mrq; + struct mmc_card *card = host->card; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; + struct request_queue *q; + int itag = 0; + int ret = 0; + + if (WARN_ON(!mrq)) + return; + + q = mrq->req->q; + WARN_ON(!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)); + + #ifdef CONFIG_MMC_CLKGATE + pr_debug("%s: %s: active_reqs = %lu, clk_requests = %d\n", + mmc_hostname(host), __func__, + ctx_info->active_reqs, host->clk_requests); + #endif + + mmc_blk_cmdq_reset(host, false); + + for_each_set_bit(itag, &ctx_info->active_reqs, + host->num_cq_slots) { + ret = is_cmdq_dcmd_req(q, itag); + if (WARN_ON(ret == -ENOENT)) + continue; + if (!ret) { + WARN_ON(!test_and_clear_bit(itag, + &ctx_info->data_active_reqs)); + } else { + clear_bit(CMDQ_STATE_DCMD_ACTIVE, + &ctx_info->curr_state); + } + mmc_cmdq_post_req(host, itag, err); + WARN_ON(!test_and_clear_bit(itag, + &ctx_info->active_reqs)); + mmc_host_clk_release(host); + mmc_put_card(card); + } + + spin_lock_irq(q->queue_lock); + blk_queue_invalidate_tags(q); + spin_unlock_irq(q->queue_lock); } static void mmc_blk_cmdq_shutdown(struct mmc_queue *mq) @@ -3044,6 +3142,7 @@ static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) struct mmc_queue_req *mq_rq = req->special; struct mmc_request *mrq; struct mmc_cmdq_req *cmdq_req; + struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; BUG_ON(!host); @@ -3069,32 +3168,40 @@ static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) else mrq->data->error = -ETIMEDOUT; - BUG_ON(host->err_mrq != NULL); - host->err_mrq = mrq; + if (test_bit(CMDQ_STATE_REQ_TIMED_OUT, &ctx_info->curr_state) || + test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) + return BLK_EH_NOT_HANDLED; - mmc_host_clk_release(mrq->host); + set_bit(CMDQ_STATE_REQ_TIMED_OUT, &ctx_info->curr_state); return BLK_EH_HANDLED; } +/* + * mmc_blk_cmdq_err: error handling of cmdq error requests. + * Function should be called in context of error out request + * which has claim_host and rpm acquired. + * This may be called with CQ engine halted. Make sure to + * unhalt it after error recovery. + * + * TODO: Currently cmdq error handler does reset_all in case + * of any erorr. Need to optimize error handling. + */ static void mmc_blk_cmdq_err(struct mmc_queue *mq) { - int err; - int retry = 0; - int gen_err; - u32 status; - struct mmc_host *host = mq->card->host; struct mmc_request *mrq = host->err_mrq; - struct mmc_card *card = mq->card; struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; - struct request_queue *q = mrq->req->q; - int tag = mrq->req->tag; + struct request_queue *q; + int err; - pm_runtime_get_sync(&card->dev); mmc_host_clk_hold(host); host->cmdq_ops->dumpstate(host); mmc_host_clk_release(host); + if (WARN_ON(!mrq)) + return; + + q = mrq->req->q; err = mmc_cmdq_halt(host, true); if (err) { pr_err("halt: failed: %d\n", err); @@ -3103,74 +3210,44 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) /* RED error - Fatal: requires reset */ if (mrq->cmdq_req->resp_err) { + err = mrq->cmdq_req->resp_err; pr_crit("%s: Response error detected: Device in bad state\n", mmc_hostname(host)); - blk_end_request_all(mrq->req, -EIO); goto reset; } - if (mrq->data && mrq->data->error) { - blk_end_request_all(mrq->req, mrq->data->error); - for (; retry < MAX_RETRIES; retry++) { - err = get_card_status(card, &status, 0); - if (!err) - break; - } + /* + * In case of software request time-out, we schedule err work only for + * the first error out request and handles all other request in flight + * here. + */ + if (test_bit(CMDQ_STATE_REQ_TIMED_OUT, &ctx_info->curr_state)) { + err = -ETIMEDOUT; + } else if (mrq->data && mrq->data->error) { + err = mrq->data->error; + } else if (mrq->cmd && mrq->cmd->error) { + /* DCMD commands */ + err = mrq->cmd->error; - if (err) { - pr_err("%s: No response from card !!!\n", - mmc_hostname(host)); - goto reset; - } - - if (R1_CURRENT_STATE(status) == R1_STATE_DATA || - R1_CURRENT_STATE(status) == R1_STATE_RCV) { - err = send_stop(card, MMC_CMDQ_STOP_TIMEOUT_MS, - mrq->req, &gen_err, &status); - if (err) { - pr_err("%s: error %d sending stop (%d) command\n", - mmc_hostname(host), - err, status); - goto reset; - } - } - - if (mmc_cmdq_discard_queue(host, tag)) - goto reset; - else - goto unhalt; - } - - /* DCMD commands */ - if (mrq->cmd && mrq->cmd->error) { /* * Notify completion for non flush commands like discard * that wait for DCMD finish. */ if (!(mrq->req->cmd_flags & REQ_FLUSH)) { complete(&mrq->completion); - goto reset; } - clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); - blk_end_request_all(mrq->req, mrq->cmd->error); } reset: - spin_lock_irq(mq->queue->queue_lock); - blk_queue_invalidate_tags(q); - spin_unlock_irq(mq->queue->queue_lock); - mmc_blk_cmdq_reset(host, true); - goto out; - -unhalt: + mmc_blk_cmdq_reset_all(host, err); + if (mrq->cmdq_req->resp_err) + mrq->cmdq_req->resp_err = false; mmc_cmdq_halt(host, false); -out: host->err_mrq = NULL; - pm_runtime_mark_last_busy(&card->dev); - clear_bit(CMDQ_STATE_ERR, &ctx_info->curr_state); + clear_bit(CMDQ_STATE_REQ_TIMED_OUT, &ctx_info->curr_state); + WARN_ON(!test_and_clear_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)); wake_up(&ctx_info->wait); - __mmc_put_card(card); } /* invoked by block layer in softirq context */ @@ -3190,31 +3267,38 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) else if (mrq->data && mrq->data->error) err = mrq->data->error; - /* clear pending request */ - BUG_ON(!test_and_clear_bit(cmdq_req->tag, - &ctx_info->active_reqs)); - - if (cmdq_req->cmdq_req_flags & DCMD) - is_dcmd = true; - else - BUG_ON(!test_and_clear_bit(cmdq_req->tag, - &ctx_info->data_active_reqs)); - - mmc_cmdq_post_req(host, cmdq_req->tag, err); - if (err) { - pr_err("%s: %s: txfr error: %d\n", mmc_hostname(mrq->host), - __func__, err); + if (err || cmdq_req->resp_err) { + pr_err("%s: %s: txfr error(%d)/resp_err(%d)\n", + mmc_hostname(mrq->host), __func__, err, + cmdq_req->resp_err); if (test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) { pr_err("%s: CQ in error state, ending current req: %d\n", __func__, err); - blk_end_request_all(rq, err); } else { set_bit(CMDQ_STATE_ERR, &ctx_info->curr_state); + BUG_ON(host->err_mrq != NULL); + host->err_mrq = mrq; schedule_work(&mq->cmdq_err_work); } goto out; } + /* + * In case of error CMDQ is expected to be either in halted + * or disable state so cannot receive any completion of + * other requests. + */ + BUG_ON(test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)); + /* clear pending request */ + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->active_reqs)); + if (cmdq_req->cmdq_req_flags & DCMD) + is_dcmd = true; + else + BUG_ON(!test_and_clear_bit(cmdq_req->tag, + &ctx_info->data_active_reqs)); + + mmc_cmdq_post_req(host, cmdq_req->tag, err); if (cmdq_req->cmdq_req_flags & DCMD) { clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); blk_end_request_all(rq, err); @@ -3226,10 +3310,11 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) out: mmc_cmdq_clk_scaling_stop_busy(host, true, is_dcmd); - if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) + if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) { wake_up(&ctx_info->wait); + mmc_put_card(host->card); + } - mmc_put_card(host->card); if (!ctx_info->active_reqs) wake_up_interruptible(&host->cmdq_ctx.queue_empty_wq); @@ -3244,7 +3329,6 @@ void mmc_blk_cmdq_req_done(struct mmc_request *mrq) { struct request *req = mrq->req; - mmc_host_clk_release(mrq->host); blk_complete_request(req); } EXPORT_SYMBOL(mmc_blk_cmdq_req_done); diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 4c5817776dfe..dd75efe6cf97 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -881,7 +881,6 @@ skip_cqterri: mrq->cmdq_req->resp_arg = cmdq_readl(cq_host, CQCRA); } - mmc->err_mrq = mrq; cmdq_finish_data(mmc, tag); } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index b923a74add1d..4438833ca935 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -257,6 +257,7 @@ struct mmc_cmdq_context_info { #define CMDQ_STATE_DCMD_ACTIVE 1 #define CMDQ_STATE_HALT 2 #define CMDQ_STATE_CQ_DISABLE 3 +#define CMDQ_STATE_REQ_TIMED_OUT 4 wait_queue_head_t queue_empty_wq; wait_queue_head_t wait; int active_small_sector_read_reqs; From 2ce873cba6043d2cc45e70e487f74851002c625a Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Thu, 8 Oct 2015 17:54:01 +0530 Subject: [PATCH 442/472] mmc: block: cmdq discard should use softirq completion path Currently cmdq discard requests are not using same softirq completion path as other cmdq request. So there is no way to detect any error and handle it in case of error of DCMD requests. Make cmdq discard requests use blk_complete_requests which complete via softirq completion path. Change-Id: I1e03c81bc6fee8266cf1c86bada015e548770d7a Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 49 ++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 026886b949bb..6364a777db80 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1602,13 +1602,12 @@ static int mmc_blk_cmdq_issue_discard_rq(struct mmc_queue *mq, struct mmc_blk_data *md = mq->data; struct mmc_card *card = md->queue.card; struct mmc_cmdq_req *cmdq_req = NULL; - struct mmc_host *host = card->host; - struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; unsigned int from, nr, arg; int err = 0; if (!mmc_can_erase(card)) { err = -EOPNOTSUPP; + blk_end_request(req, err, blk_rq_bytes(req)); goto out; } @@ -1637,16 +1636,9 @@ static int mmc_blk_cmdq_issue_discard_rq(struct mmc_queue *mq, } err = mmc_cmdq_erase(cmdq_req, card, from, nr, arg); clear_dcmd: - /* clear pending request */ - if (cmdq_req) { - BUG_ON(!test_and_clear_bit(cmdq_req->tag, - &ctx_info->active_reqs)); - clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); - } + mmc_host_clk_hold(card->host); + blk_complete_request(req); out: - blk_end_request(req, err, blk_rq_bytes(req)); - wake_up(&ctx_info->wait); - mmc_put_card(card); return err ? 1 : 0; } @@ -1700,12 +1692,11 @@ static int mmc_blk_cmdq_issue_secdiscard_rq(struct mmc_queue *mq, struct mmc_card *card = md->queue.card; struct mmc_cmdq_req *cmdq_req = NULL; unsigned int from, nr, arg; - struct mmc_host *host = card->host; - struct mmc_cmdq_context_info *ctx_info = &host->cmdq_ctx; int err = 0; if (!(mmc_can_secure_erase_trim(card))) { err = -EOPNOTSUPP; + blk_end_request(req, err, blk_rq_bytes(req)); goto out; } @@ -1751,16 +1742,9 @@ static int mmc_blk_cmdq_issue_secdiscard_rq(struct mmc_queue *mq, MMC_SECURE_TRIM2_ARG); } clear_dcmd: - /* clear pending request */ - if (cmdq_req) { - BUG_ON(!test_and_clear_bit(cmdq_req->tag, - &ctx_info->active_reqs)); - clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); - } + mmc_host_clk_hold(card->host); + blk_complete_request(req); out: - blk_end_request(req, err, blk_rq_bytes(req)); - wake_up(&ctx_info->wait); - mmc_put_card(card); return err ? 1 : 0; } @@ -3168,6 +3152,19 @@ static enum blk_eh_timer_return mmc_blk_cmdq_req_timed_out(struct request *req) else mrq->data->error = -ETIMEDOUT; + if (mrq->cmd && mrq->cmd->error) { + if (!(mrq->req->cmd_flags & REQ_FLUSH)) { + /* + * Notify completion for non flush commands like + * discard that wait for DCMD finish. + */ + set_bit(CMDQ_STATE_REQ_TIMED_OUT, + &ctx_info->curr_state); + complete(&mrq->completion); + return BLK_EH_NOT_HANDLED; + } + } + if (test_bit(CMDQ_STATE_REQ_TIMED_OUT, &ctx_info->curr_state) || test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) return BLK_EH_NOT_HANDLED; @@ -3228,14 +3225,6 @@ static void mmc_blk_cmdq_err(struct mmc_queue *mq) } else if (mrq->cmd && mrq->cmd->error) { /* DCMD commands */ err = mrq->cmd->error; - - /* - * Notify completion for non flush commands like discard - * that wait for DCMD finish. - */ - if (!(mrq->req->cmd_flags & REQ_FLUSH)) { - complete(&mrq->completion); - } } reset: From ed7cdb2aea11461f3f353e634ca63e241e47b0fd Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 14 Dec 2015 09:58:09 +0530 Subject: [PATCH 443/472] mmc: core: Fix mmc_set_ios w.r.t. error handling sequence In case of error, software may power cycle/reset both controller and card. Thus even if auto bkops is enabled, mmc_set_ios can be used to power-off/power-on. Fix this by removing this condition from mmc_set_ios. Change-Id: I88a610ecbcc4036ac8ba99bc54706243b40db391 Signed-off-by: Ritesh Harjani --- drivers/mmc/core/core.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 64d5d57eb087..e65e16bf92ea 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2136,13 +2136,6 @@ void mmc_set_ios(struct mmc_host *host) { struct mmc_ios *ios = &host->ios; - if (unlikely(ios->power_mode == MMC_POWER_OFF && - host->card && mmc_card_doing_auto_bkops(host->card))) { - pr_err("%s: %s: illegal to disable power to card when running auto bkops\n", - mmc_hostname(host), __func__); - return; - } - pr_debug("%s: clock %uHz busmode %u powermode %u cs %u Vdd %u " "width %u timing %u\n", mmc_hostname(host), ios->clock, ios->bus_mode, From 313d5a3851a40ea408b20f1184ff067aa7a4f386 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 23 Nov 2015 11:46:45 +0530 Subject: [PATCH 444/472] mmc: block: Do not call post_req in DCMD case No need to call post_req if it's a DCMD request completion. Change-Id: Id11165967e316b1e556aaeb6d67bd18844cee6e1 Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 6364a777db80..02f2d323704e 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3070,11 +3070,11 @@ static void mmc_blk_cmdq_reset_all(struct mmc_host *host, int err) if (!ret) { WARN_ON(!test_and_clear_bit(itag, &ctx_info->data_active_reqs)); + mmc_cmdq_post_req(host, itag, err); } else { clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); } - mmc_cmdq_post_req(host, itag, err); WARN_ON(!test_and_clear_bit(itag, &ctx_info->active_reqs)); mmc_host_clk_release(host); @@ -3286,8 +3286,8 @@ void mmc_blk_cmdq_complete_rq(struct request *rq) else BUG_ON(!test_and_clear_bit(cmdq_req->tag, &ctx_info->data_active_reqs)); - - mmc_cmdq_post_req(host, cmdq_req->tag, err); + if (!is_dcmd) + mmc_cmdq_post_req(host, cmdq_req->tag, err); if (cmdq_req->cmdq_req_flags & DCMD) { clear_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx_info->curr_state); blk_end_request_all(rq, err); From f34fd7c3def3e89640d8b4dec01e0d2e6d8bea96 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 14 Dec 2015 10:07:33 +0530 Subject: [PATCH 445/472] mmc: cmdq_hci: Add atomic context support in certain cmdq APIs cmdq_halt and cmdq_disable gets called from cmdq_irq in case of error. Thus add cmdq_disable_nosync and unhalt support in cmdq_halt_poll which can be called from irq context. Change-Id: I172e0e29a5584f02dd96c8af5ea1b97dc8c46083 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/cmdq_hci.c | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index dd75efe6cf97..17c5f9d69801 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -37,7 +37,7 @@ /* 1 sec */ #define HALT_TIMEOUT_MS 1000 -static int cmdq_halt_poll(struct mmc_host *mmc); +static int cmdq_halt_poll(struct mmc_host *mmc, bool halt); static int cmdq_halt(struct mmc_host *mmc, bool halt); #ifdef CONFIG_PM_RUNTIME @@ -427,11 +427,10 @@ out: return err; } -static void cmdq_disable(struct mmc_host *mmc, bool soft) +static void cmdq_disable_nosync(struct mmc_host *mmc, bool soft) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); - cmdq_runtime_pm_get(cq_host); if (soft) { cmdq_writel(cq_host, cmdq_readl( cq_host, CQCFG) & ~(CQ_ENABLE), @@ -440,11 +439,19 @@ static void cmdq_disable(struct mmc_host *mmc, bool soft) if (cq_host->ops->enhanced_strobe_mask) cq_host->ops->enhanced_strobe_mask(mmc, false); - cmdq_runtime_pm_put(cq_host); cq_host->enabled = false; mmc_host_set_cq_disable(mmc); } +static void cmdq_disable(struct mmc_host *mmc, bool soft) +{ + struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); + + cmdq_runtime_pm_get(cq_host); + cmdq_disable_nosync(mmc, soft); + cmdq_runtime_pm_put(cq_host); +} + static void cmdq_reset(struct mmc_host *mmc, bool soft) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); @@ -791,7 +798,7 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) * CMDQ error handling will make sure that it is unhalted after * handling all the errors. */ - ret = cmdq_halt_poll(mmc); + ret = cmdq_halt_poll(mmc, true); if (ret) pr_err("%s: %s: halt failed ret=%d\n", mmc_hostname(mmc), __func__, ret); @@ -812,7 +819,7 @@ irqreturn_t cmdq_irq(struct mmc_host *mmc, int err) if (!dbr_set) { pr_err("%s: spurious/force error interrupt\n", mmc_hostname(mmc)); - cmdq_halt(mmc, false); + cmdq_halt_poll(mmc, false); mmc_host_clr_halt(mmc); return IRQ_HANDLED; } @@ -857,7 +864,7 @@ skip_cqterri: * from processing any further requests */ if (ret) - cmdq_disable(mmc, true); + cmdq_disable_nosync(mmc, true); /* * CQE detected a reponse error from device @@ -923,14 +930,25 @@ EXPORT_SYMBOL(cmdq_irq); /* cmdq_halt_poll - Halting CQE using polling method. * @mmc: struct mmc_host - * This is used mainly from interrupt context to halt + * @halt: bool halt + * This is used mainly from interrupt context to halt/unhalt * CQE engine. */ -static int cmdq_halt_poll(struct mmc_host *mmc) +static int cmdq_halt_poll(struct mmc_host *mmc, bool halt) { struct cmdq_host *cq_host = (struct cmdq_host *)mmc_cmdq_private(mmc); int retries = 100; + if (!halt) { + if (cq_host->ops->set_data_timeout) + cq_host->ops->set_data_timeout(mmc, 0xf); + if (cq_host->ops->clear_set_irqs) + cq_host->ops->clear_set_irqs(mmc, true); + cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) & ~HALT, + CQCTL); + return 0; + } + cmdq_set_halt_irq(cq_host, false); cmdq_writel(cq_host, cmdq_readl(cq_host, CQCTL) | HALT, CQCTL); while (retries) { From 88f87410285ec89cfb1214b1e10777f31f641b3c Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 23 Dec 2015 13:21:02 +0530 Subject: [PATCH 446/472] mmc: sdhci-msm: Fix NULL pointer dereference If during early boot register dump is called, then there could be a NULL pointer dereference since mmc_host structure might not be populated with cq_host info. Thus fix this by using sdhci_host to get cq_host structure info in sdhci_msm_cmdq_dump_debug_ram. Change-Id: Ieaca7887001daabd4a6a0b05f7b0048dc11bbeee Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci-msm.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 36abd920a991..bb48eb666be5 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -3020,10 +3020,13 @@ static void sdhci_msm_set_uhs_signaling(struct sdhci_host *host, #define MAX_TEST_BUS 60 #define DRV_NAME "cmdq-host" -static void sdhci_msm_cmdq_dump_debug_ram(struct sdhci_msm_host *msm_host) +static void sdhci_msm_cmdq_dump_debug_ram(struct sdhci_host *host) { + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_msm_host *msm_host = pltfm_host->priv; int i = 0; - struct cmdq_host *cq_host = mmc_cmdq_private(msm_host->mmc); + struct cmdq_host *cq_host = host->cq_host; + u32 version = readl_relaxed(msm_host->core_mem + CORE_MCI_VERSION); u16 minor = version & CORE_VERSION_TARGET_MASK; /* registers offset changed starting from 4.2.0 */ @@ -3054,7 +3057,7 @@ void sdhci_msm_dump_vendor_regs(struct sdhci_host *host) pr_info("----------- VENDOR REGISTER DUMP -----------\n"); if (host->cq_host) - sdhci_msm_cmdq_dump_debug_ram(msm_host); + sdhci_msm_cmdq_dump_debug_ram(host); pr_info("Data cnt: 0x%08x | Fifo cnt: 0x%08x | Int sts: 0x%08x\n", readl_relaxed(msm_host->core_mem + CORE_MCI_DATA_CNT), From 211eedb1b548ee26f50946599265fc91765f6c26 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 23 Dec 2015 13:21:51 +0530 Subject: [PATCH 447/472] mmc: sdhci: Fix timeout mdelay bug in sdhci_reset Change mdelay to udelay to fix 100sec waiting timeout bug in sdhci_reset. sdhci_reset is intended to only wait for 100msec. Change-Id: I6b5c9b10daf7cd8a7faf16ac6bdb09c5079a39e9 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/sdhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index bb53c8eb5e6e..2b34aec58c2c 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -269,7 +269,7 @@ retry_reset: return; } timeout--; - mdelay(1); + udelay(1); } if ((host->quirks2 & SDHCI_QUIRK2_USE_RESET_WORKAROUND) && From 9726132b51fd877e2fcf571bc9d7f3b7e873e63b Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Wed, 30 Dec 2015 15:53:49 +0530 Subject: [PATCH 448/472] mmc: cmdq_hci: Fix NULL pointer dereference crash Add a check on cmdq_host->ops to avoid NULL pointer dereference, if sdhci_dumpregs is called before initializing cmdq_host->ops structure. Change-Id: Idd1794e162c7a53cc63504e15e6e490481f104a3 Signed-off-by: Ritesh Harjani --- drivers/mmc/host/cmdq_hci.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mmc/host/cmdq_hci.h b/drivers/mmc/host/cmdq_hci.h index 53096fb3a2aa..91cb25b78481 100644 --- a/drivers/mmc/host/cmdq_hci.h +++ b/drivers/mmc/host/cmdq_hci.h @@ -215,7 +215,7 @@ struct cmdq_host_ops { static inline void cmdq_writel(struct cmdq_host *host, u32 val, int reg) { - if (unlikely(host->ops->write_l)) + if (unlikely(host->ops && host->ops->write_l)) host->ops->write_l(host, val, reg); else writel_relaxed(val, host->mmio + reg); @@ -223,7 +223,7 @@ static inline void cmdq_writel(struct cmdq_host *host, u32 val, int reg) static inline u32 cmdq_readl(struct cmdq_host *host, int reg) { - if (unlikely(host->ops->read_l)) + if (unlikely(host->ops && host->ops->read_l)) return host->ops->read_l(host, reg); else return readl_relaxed(host->mmio + reg); From e78c71343571f0d59d3577226440388782cd890b Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 12 Jan 2016 16:40:50 +0530 Subject: [PATCH 449/472] mmc: sdhci-msm: Set sdio_pending_processing default state to false It seems that when we are configuring sdiowakeup_irq, we are receiving a spurious interrupt which sets sdio_pending_processing state to true. Now, if the sdio card is physically connected but wifi not enabled in that case suspend functionality will be broken sdhci_msm_suspend_noirq will return -EBUSY if sdio_pending_processing is set to true. Thus fix it by setting this flag to false after we have disabled sdiowakeup_irq. CRs-Fixed: 957968 Change-Id: I755b0b5602345ad6bf557c6055b9057012de0797 Signed-off-by: Ritesh Harjani Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index bb48eb666be5..7f4d22276194 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2,7 +2,7 @@ * drivers/mmc/host/sdhci-msm.c - Qualcomm Technologies, Inc. MSM SDHCI Platform * driver source file * - * Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -4288,6 +4288,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) } else { spin_lock_irqsave(&host->lock, flags); sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); + msm_host->sdio_pending_processing = false; spin_unlock_irqrestore(&host->lock, flags); } } @@ -4421,6 +4422,7 @@ static int sdhci_msm_cfg_sdio_wakeup(struct sdhci_host *host, bool enable) if (!(host->mmc->card && mmc_card_sdio(host->mmc->card) && sdhci_is_valid_gpio_wakeup_int(msm_host) && mmc_card_wake_sdio_irq(host->mmc))) { + msm_host->sdio_pending_processing = false; return 1; } From 861ecf399bd1c94c0b640e1b8da7691b1d0b08f3 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 28 Jan 2016 18:57:07 -0800 Subject: [PATCH 450/472] mmc: queue: Don't peek requests when queue is stopped Make sure we don't peek the block layer queue and act on it when block queue is stopped. The block queue will be stopped when mmc block is suspended, wait till it is properly resumed before pulling new requests. Change-Id: Ifc369687c13dae904271e8f92d3604edbd667d82 Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/card/queue.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 01e87f4480ae..bc5ed3924c98 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -59,9 +59,11 @@ static int mmc_prep_request(struct request_queue *q, struct request *req) static struct request *mmc_peek_request(struct mmc_queue *mq) { struct request_queue *q = mq->queue; + mq->cmdq_req_peeked = NULL; spin_lock_irq(q->queue_lock); - mq->cmdq_req_peeked = blk_peek_request(q); + if (!blk_queue_stopped(q)) + mq->cmdq_req_peeked = blk_peek_request(q); spin_unlock_irq(q->queue_lock); return mq->cmdq_req_peeked; From bb0fb32d8a10e6a38f5d615a8c604f9c2bf6d290 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 17 Nov 2015 09:24:53 +0530 Subject: [PATCH 451/472] mmc: sdhci-msm: Fix recursive tuning issue For any commands, that are sent during tuning sequence CRC errors are expected. But if SDHCI_NEEDS_RETUNING flag is set, then it will recursively do the tuning and gets stuck in tuning. Fix this by not allowing the re-tuning to happen while it is already in tuning process. Change-Id: I9cc39f03a01c34f2f5639d4c20776fd575c25231 Signed-off-by: Sahitya Tummala --- drivers/mmc/host/sdhci-msm.c | 8 ++++++++ drivers/mmc/host/sdhci-msm.h | 1 + 2 files changed, 9 insertions(+) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 7f4d22276194..39de38908718 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -966,6 +966,13 @@ int sdhci_msm_execute_tuning(struct sdhci_host *host, u32 opcode) (ios.timing == MMC_TIMING_UHS_SDR104))) return 0; + /* + * Don't allow re-tuning for CRC errors observed for any commands + * that are sent during tuning sequence itself. + */ + if (msm_host->tuning_in_progress) + return 0; + msm_host->tuning_in_progress = true; pr_debug("%s: Enter %s\n", mmc_hostname(mmc), __func__); /* CDC/SDC4 DLL HW calibration is only required for HS400 mode*/ @@ -1142,6 +1149,7 @@ out: if (!rc) msm_host->tuning_done = true; spin_unlock_irqrestore(&host->lock, flags); + msm_host->tuning_in_progress = false; pr_debug("%s: Exit %s, err(%d)\n", mmc_hostname(mmc), __func__, rc); return rc; } diff --git a/drivers/mmc/host/sdhci-msm.h b/drivers/mmc/host/sdhci-msm.h index 00785765a1ec..e47a5083e8a6 100644 --- a/drivers/mmc/host/sdhci-msm.h +++ b/drivers/mmc/host/sdhci-msm.h @@ -213,6 +213,7 @@ struct sdhci_msm_host { struct device_attribute pm_qos_group_status_attr; bool pm_qos_group_enable; struct sdhci_msm_pm_qos_irq pm_qos_irq; + bool tuning_in_progress; }; extern char *saved_command_line; From 34167033d4ab084678f0adc1bf893c80ed290c84 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Mon, 25 Jan 2016 14:08:32 +0530 Subject: [PATCH 452/472] mmc: card: set dma_mask as the queue bounce limit Some controllers doesn't have any limitation on the memory it can address. Hence, the bounce limit parameter must be taken based on the device dma_mask. Currently it is set to BLK_BOUNCE_HIGH, which may cause bouncing of memory higher than this limit. Use dma_mask as the limit for queue bounce parameter to avoid this unncessary memory bouncing in the block layer. CRs-Fixed: 964435 Change-Id: I0955438a540ca9adf5bcd3a0dbf9201a5ef456a5 Signed-off-by: Sahitya Tummala --- drivers/mmc/card/queue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index bc5ed3924c98..5c90d46fb65d 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -295,6 +295,9 @@ void mmc_cmdq_setup_queue(struct mmc_queue *mq, struct mmc_card *card) u64 limit = BLK_BOUNCE_HIGH; struct mmc_host *host = card->host; + if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask) + limit = *mmc_dev(host)->dma_mask; + queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue); if (mmc_can_erase(card)) mmc_queue_setup_discard(mq->queue, card); From d6a8e003e07e1ebafc596e051defb84194c527a2 Mon Sep 17 00:00:00 2001 From: xiaonian Date: Thu, 16 Jul 2015 14:39:32 +0800 Subject: [PATCH 453/472] mmc: core: set REL_WR_SEC_C register to 0x1 per eMMC5.0 spec Some eMMC vendors violate eMMC 5.0 spec and set REL_WR_SEC_C register to 0x10 to indicate the ability of RPMB throughput improvement thus lead to failure when TZ module write data to RPMB partition. This change will check bit[4] of EXT_CSD[166] and if it is not set then change value of REL_WR_SEC_C to 0x1 directly ignoring value of EXT_CSD[222]. CRs-Fixed: 866059 Change-Id: Ibd12c94ad691eca1fa3ea2049b750a6e98178678 Signed-off-by: xiaonian Signed-off-by: Pavan Anamula --- drivers/mmc/core/mmc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 818a70b4007d..052cd335490d 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -563,6 +563,19 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM]; card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; + /* + * Some eMMC vendors violate eMMC 5.0 spec and set + * REL_WR_SEC_C register to 0x10 to indicate the + * ability of RPMB throughput improvement thus lead + * to failure when TZ module write data to RPMB + * partition. So check bit[4] of EXT_CSD[166] and + * if it is not set then change value of REL_WR_SEC_C + * to 0x1 directly ignoring value of EXT_CSD[222]. + */ + if (!(card->ext_csd.rel_param & + EXT_CSD_WR_REL_PARAM_EN_RPMB_REL_WR)) + card->ext_csd.rel_sectors = 0x1; + /* * RPMB regions are defined in multiples of 128K. */ From cc6df31db6b39446b338b61932b0fc8f614baf89 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Fri, 12 Feb 2016 16:53:04 +0530 Subject: [PATCH 454/472] mmc: core: fix issue with devfreq clock scaling Due to recent DDR52 lower bus speed mode in clock scaling, there is a mismatch between the clock frequencies used by the devfreq framework and the MMC driver. Due to this, SDCC clock is sometimes running at DDR25 and ICE clock is running at 100MHz causing the power regression. Fix this mismatch by initializing the frequencies properly during MMC resume based on the current ios.clock. CRs-Fixed: 974940 Change-Id: I09fe888d0fbd1fde3f6a6f32806315ddbb5bf6e1 Signed-off-by: Sahitya Tummala --- drivers/mmc/core/core.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index e65e16bf92ea..d93b57670a69 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -774,6 +774,9 @@ EXPORT_SYMBOL(mmc_suspend_clk_scaling); int mmc_resume_clk_scaling(struct mmc_host *host) { int err = 0; + u32 max_clk_idx = 0; + u32 devfreq_max_clk = 0; + u32 devfreq_min_clk = 0; if (!host) { WARN(1, "bad host parameter\n"); @@ -790,7 +793,15 @@ int mmc_resume_clk_scaling(struct mmc_host *host) } atomic_set(&host->clk_scaling.devfreq_abort, 0); - host->clk_scaling.curr_freq = host->ios.clock; + + max_clk_idx = host->clk_scaling.freq_table_sz - 1; + devfreq_max_clk = host->clk_scaling.freq_table[max_clk_idx]; + devfreq_min_clk = host->clk_scaling.freq_table[0]; + + host->clk_scaling.curr_freq = devfreq_max_clk; + if (host->ios.clock < host->card->clk_scaling_highest) + host->clk_scaling.curr_freq = devfreq_min_clk; + host->clk_scaling.clk_scaling_in_progress = false; host->clk_scaling.need_freq_change = false; From 284f0655519f1a81a470bb5df396b151b207b6a4 Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Mon, 15 Feb 2016 14:20:21 +0530 Subject: [PATCH 455/472] mmc: queue: Fix queue_lock spinlock bug from CMDQ shutdown path CMDQ shutdown path calls blk_cleanup_queue, which changes queue_lock from driver lock to it's original request_queue lock. Hence during above shutdown process if below sequence is exercised as well then may see below spinlock bug. a) Some process say iozoneA has already acquired queue_lock (which is md->lock). b) adb reboot has been issued and CMDQ driver has completed calling blk_cleanup_queue which switches the queue_lock from md->lock to q->__queue_lock. c) ProcessA tries to release queue_lock but finds an unbalance that the lock is already released Hence remove blk_cleanup_queue and instead make sure there are no active_reqs in flight by mmccmdqd before this kthread is exited. Callstack: <6> BUG: spinlock already unlocked on CPU#6, iozone/4391 <6> lock: 0xffffffc06ab8be80, .magic: dead4ead, .owner: /-1, .owner_cpu: -1 [ffffffc0420e3b28] __delay at ffffffc00031a328 [ffffffc0420e3b38] __const_udelay at ffffffc00031a304 [ffffffc0420e3b58] msm_trigger_wdog_bite at ffffffc0004476cc [ffffffc0420e3b68] spin_bug at ffffffc0000e4554 [ffffffc0420e3b98] do_raw_spin_unlock at ffffffc0000e47a0 [ffffffc0420e3bc8] _raw_spin_unlock_irq at ffffffc000db3ee0 [ffffffc0420e3be8] blk_queue_bio at ffffffc0002ff1e4 [ffffffc0420e3bf8] generic_make_request at ffffffc0002fd210 [ffffffc0420e3c58] submit_bio at ffffffc0002fd328 [ffffffc0420e3ca8] submit_bio_wait at ffffffc0002f5768 [ffffffc0420e3d00] compat_sys_call_table at ffffffc00008e000 [ffffffc0420e3d18] submit_bio_wait at ffffffc0002f574c [ffffffc0420e3d38] __blkdev_issue_flush at ffffffc00030043c [ffffffc0420e3da8] blkdev_issue_flush at ffffffc000300494 [ffffffc0420e3dd8] ext4_sync_fs at ffffffc0002597a4 CRs-fixed: 953541 Change-Id: I769cc25c14b6d873f64a898d6b73f33cc59d9c5d Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 3 +++ drivers/mmc/card/queue.c | 25 ++++++++++++++++++++++--- drivers/mmc/card/queue.h | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 02f2d323704e..97fcd1cf82b6 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3307,6 +3307,9 @@ out: if (!ctx_info->active_reqs) wake_up_interruptible(&host->cmdq_ctx.queue_empty_wq); + if (blk_queue_stopped(mq->queue) && !ctx_info->active_reqs) + complete(&mq->cmdq_shutdown_complete); + return; } diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index 5c90d46fb65d..75a51bd2fc0b 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -97,7 +97,8 @@ static inline void mmc_cmdq_ready_wait(struct mmc_host *host, * 4. cmdq state shouldn't be in error state. * 5. free tag available to process the new request. */ - wait_event(ctx->wait, mmc_peek_request(mq) && + wait_event(ctx->wait, kthread_should_stop() + || (mmc_peek_request(mq) && !((mq->cmdq_req_peeked->cmd_flags & (REQ_FLUSH | REQ_DISCARD)) && test_bit(CMDQ_STATE_DCMD_ACTIVE, &ctx->curr_state)) && !(!host->card->part_curr && !mmc_card_suspended(host->card) @@ -105,7 +106,7 @@ static inline void mmc_cmdq_ready_wait(struct mmc_host *host, && !(!host->card->part_curr && mmc_host_cq_disable(host) && !mmc_card_suspended(host->card)) && !test_bit(CMDQ_STATE_ERR, &ctx->curr_state) - && !mmc_check_blk_queue_start_tag(q, mq->cmdq_req_peeked)); + && !mmc_check_blk_queue_start_tag(q, mq->cmdq_req_peeked))); } static int mmc_cmdq_thread(void *d) @@ -122,6 +123,8 @@ static int mmc_cmdq_thread(void *d) int ret = 0; mmc_cmdq_ready_wait(host, mq); + if (kthread_should_stop()) + break; ret = mq->cmdq_issue_fn(mq, mq->cmdq_req_peeked); /* @@ -662,6 +665,7 @@ int mmc_cmdq_init(struct mmc_queue *mq, struct mmc_card *card) blk_queue_softirq_done(mq->queue, mmc_cmdq_softirq_done); INIT_WORK(&mq->cmdq_err_work, mmc_cmdq_error_work); + init_completion(&mq->cmdq_shutdown_complete); init_completion(&mq->cmdq_pending_req_done); blk_queue_rq_timed_out(mq->queue, mmc_cmdq_rq_timed_out); @@ -718,7 +722,22 @@ int mmc_queue_suspend(struct mmc_queue *mq, int wait) goto out; if (wait) { - blk_cleanup_queue(q); + + /* + * After blk_stop_queue is called, wait for all + * active_reqs to complete. + * Then wait for cmdq thread to exit before calling + * cmdq shutdown to avoid race between issuing + * requests and shutdown of cmdq. + */ + spin_lock_irqsave(q->queue_lock, flags); + blk_stop_queue(q); + spin_unlock_irqrestore(q->queue_lock, flags); + + if (host->cmdq_ctx.active_reqs) + wait_for_completion( + &mq->cmdq_shutdown_complete); + kthread_stop(mq->thread); mq->cmdq_shutdown(mq); } else { spin_lock_irqsave(q->queue_lock, flags); diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h index 964262f0eb79..d8a33ac55c91 100644 --- a/drivers/mmc/card/queue.h +++ b/drivers/mmc/card/queue.h @@ -72,6 +72,7 @@ struct mmc_queue { struct work_struct cmdq_err_work; struct completion cmdq_pending_req_done; + struct completion cmdq_shutdown_complete; struct request *cmdq_req_peeked; int (*err_check_fn) (struct mmc_card *, struct mmc_async_req *); void (*packed_test_fn) (struct request_queue *, struct mmc_queue_req *); From 47bbd75b7b86f1c96208fd128f81a6f442d09bd2 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Fri, 19 Feb 2016 17:41:42 -0800 Subject: [PATCH 456/472] mmc: host: Set max frequency when disabling clock scaling The mmc host needs to perform at its peak when clock scaling is disabled, hence switch the frequency to max. CRs-fixed: 981387 Change-Id: Ie959b8e565ee2dad53cdd9d913bcb8696519d7ca Signed-off-by: Venkat Gopalakrishnan --- drivers/mmc/core/host.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 5e06ba5fbe97..657c08ef573d 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -644,11 +644,16 @@ static ssize_t store_enable(struct device *dev, if (!host || kstrtoul(buf, 0, &value)) return -EINVAL; - mmc_claim_host(host); - if (!value && host->clk_scaling.enable) { - /*turnning off clock scaling*/ + mmc_get_card(host->card); + + if (!value) { + /*turning off clock scaling*/ mmc_exit_clk_scaling(host); host->caps2 &= ~MMC_CAP2_CLK_SCALE; + host->clk_scaling.state = MMC_LOAD_HIGH; + /* Set to max. frequency when disabling */ + mmc_clk_update_freq(host, host->card->clk_scaling_highest, + host->clk_scaling.state); } else if (value) { /* starting clock scaling, will restart in case started */ host->caps2 |= MMC_CAP2_CLK_SCALE; @@ -657,7 +662,7 @@ static ssize_t store_enable(struct device *dev, mmc_init_clk_scaling(host); } - mmc_release_host(host); + mmc_put_card(host->card); return count; } From f4c7f1c57398a1ed41b888617817b1a85388c386 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 31 Dec 2015 13:25:40 +0200 Subject: [PATCH 457/472] mmc: revert runtime idle state Remove the runtime idle state. It was introduced by 217cf95511d23f703d0e7e597d3132739739654b Instead of checking BKOPS logic in the runtime idle state, all relevant logic should be performed in runtime suspend callback. CRs-Fixed: 979630 Change-Id: Iaf0d8326c0e3fd6507b075339f2cc87ae1bdf6b2 Signed-off-by: Konstantin Dorfman [subhashj@codeaurora.org: fixed trivial merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/card/block.c | 9 +----- drivers/mmc/core/bus.c | 13 +------- drivers/mmc/core/core.c | 15 +--------- drivers/mmc/core/mmc.c | 64 ---------------------------------------- 4 files changed, 3 insertions(+), 98 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 97fcd1cf82b6..9971055b9ffd 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -4207,14 +4207,7 @@ static int mmc_blk_probe(struct mmc_card *card) pm_runtime_use_autosuspend(&card->dev); pm_runtime_set_autosuspend_delay(&card->dev, MMC_AUTOSUSPEND_DELAY_MS); - /* - * If there is a runtime_idle function, it should take care of - * suspending the card - */ - if (card->host->bus_ops->runtime_idle) - pm_runtime_dont_use_autosuspend(&card->dev); - else - pm_runtime_use_autosuspend(&card->dev); + pm_runtime_use_autosuspend(&card->dev); /* * Don't enable runtime PM for SD-combo cards here. Leave that diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index b29097d7b3db..9128d6fb6169 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -201,21 +201,10 @@ static int mmc_runtime_resume(struct device *dev) return host->bus_ops->runtime_resume(host); } -static int mmc_runtime_idle(struct device *dev) -{ - struct mmc_card *card = mmc_dev_to_card(dev); - struct mmc_host *host = card->host; - - if (host->bus_ops->runtime_idle) - return host->bus_ops->runtime_idle(host); - return 0; -} - #endif /* !CONFIG_PM */ static const struct dev_pm_ops mmc_bus_pm_ops = { - SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, - mmc_runtime_idle) + SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, NULL) SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume) }; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d93b57670a69..6c80a0c41e34 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2113,19 +2113,6 @@ void mmc_get_card(struct mmc_card *card) } EXPORT_SYMBOL(mmc_get_card); -/* - * This is a helper function, which drops the runtime - * pm reference for the card device. - */ -void __mmc_put_card(struct mmc_card *card) -{ - /* In case of runtime_idle, it will handle the suspend */ - if (card->host->bus_ops->runtime_idle) - pm_runtime_put(&card->dev); - else - pm_runtime_put_autosuspend(&card->dev); -} -EXPORT_SYMBOL(__mmc_put_card); /* * This is a helper function, which releases the host and drops the runtime @@ -2135,7 +2122,7 @@ void mmc_put_card(struct mmc_card *card) { mmc_release_host(card->host); pm_runtime_mark_last_busy(&card->dev); - __mmc_put_card(card); + pm_runtime_put_autosuspend(&card->dev); } EXPORT_SYMBOL(mmc_put_card); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 052cd335490d..719e80595ecf 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2786,69 +2786,6 @@ static int mmc_reset(struct mmc_host *host) return mmc_init_card(host, card->ocr, card); } -#define NO_AUTOSUSPEND (-1) -static int mmc_runtime_idle(struct mmc_host *host) -{ - int err = 0; - bool halt_cmdq; - - BUG_ON(!host->card); - - mmc_claim_host(host); - - halt_cmdq = mmc_card_cmdq(host->card) && - (host->card->bkops.needs_check || - host->card->bkops.needs_manual); - - /* - * If there are active requests, can't check for bkops. Postpone - * until the next time that runtime_idle is scheduled. - */ - if (host->cmdq_ctx.active_reqs) - goto no_suspend; - - if (halt_cmdq) { - err = mmc_cmdq_halt(host, true); - if (err) { - pr_err("%s: %s failed to halt cmdq (%d)\n", - mmc_hostname(host), __func__, err); - goto out; - } - } - - if (host->card->bkops.needs_manual) - host->card->bkops.needs_check = false; - - if (host->card->bkops.needs_check) { - mmc_check_bkops(host->card); - host->card->bkops.needs_check = false; - - } - - if (host->card->bkops.needs_manual) - mmc_start_manual_bkops(host->card); - - if (halt_cmdq) { - err = mmc_cmdq_halt(host, false); - if (err) - pr_err("%s: %s failed to unhalt cmdq (%d)\n", - mmc_hostname(host), __func__, err); - } - -out: - /* - * TODO: consider prolonging suspend when bkops - * is running in order to allow a longer time for - * bkops to complete - * */ - pm_schedule_suspend(&host->card->dev, MMC_AUTOSUSPEND_DELAY_MS); -no_suspend: - mmc_release_host(host); - pm_runtime_mark_last_busy(&host->card->dev); - /* return negative value in order to avoid autosuspend */ - return (err) ? err : NO_AUTOSUSPEND; -} - static const struct mmc_bus_ops mmc_ops = { .remove = mmc_remove, .detect = mmc_detect, @@ -2856,7 +2793,6 @@ static const struct mmc_bus_ops mmc_ops = { .resume = mmc_resume, .runtime_suspend = mmc_runtime_suspend, .runtime_resume = mmc_runtime_resume, - .runtime_idle = mmc_runtime_idle, .alive = mmc_alive, .change_bus_speed = mmc_change_bus_speed, .reset = mmc_reset, From 127468f48283a1d8c782af7ebb68e9a9ce613d1f Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Mon, 23 Nov 2015 16:02:53 +0200 Subject: [PATCH 458/472] mmc: core: update AUTO_EN in BKOPS_EN field on runtime resume On runtime suspend flow, AUTO_EN must be turned off (see eMMC 5.1 specification 6.6.28). During runtime resume, it should be restored again. When partial card initialization flow is used on runtime resume (instead of full card initialization), this flow should restore AUTO_EN bit if needed. CRs-Fixed: 979630 Change-Id: I4ac3b7c45fdba36d014f4c88cb704bbf36011d59 Signed-off-by: Konstantin Dorfman --- drivers/mmc/core/mmc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 719e80595ecf..4447bc6f8e76 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2588,6 +2588,9 @@ static int mmc_partial_init(struct mmc_host *host) pr_debug("%s: %s: reading and comparing ext_csd successful\n", mmc_hostname(host), __func__); + if (mmc_card_support_auto_bkops(host->card)) + (void)mmc_set_auto_bkops(host->card, true); + if (card->ext_csd.cmdq_support && (card->host->caps2 & MMC_CAP2_CMD_QUEUE)) { err = mmc_select_cmdq(card); From 5c8d7435461c642e7d807e474bf11a6de1e9c052 Mon Sep 17 00:00:00 2001 From: Konstantin Dorfman Date: Thu, 31 Dec 2015 14:45:48 +0200 Subject: [PATCH 459/472] mmc: core: postpone runtime suspend in case BKOPS is required Some devices require long BKOPs time in order to provide high performance. In the current solution, the host disables auto BKOPs or stops manual BKOPs in runtime suspend, regardless of the device need for BKOPs. This patch adds a check for device BKOPs status and defers suspend in case when BKOPs need. CRs-Fixed: 979630 Change-Id: Ib38d1ce58e4195d4969e9a367b5738c8e598d0ba Signed-off-by: Konstantin Dorfman [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 14 ++++---- drivers/mmc/core/mmc.c | 64 +++++++++++++++++++++++++++++++++++++ drivers/mmc/host/cmdq_hci.c | 6 ++-- include/linux/mmc/card.h | 3 +- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 6c80a0c41e34..13e06f96d0c9 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1227,6 +1227,8 @@ int mmc_set_auto_bkops(struct mmc_card *card, bool enable) mmc_update_bkops_auto_off(&card->bkops.stats); } card->ext_csd.man_bkops_en = bkops_en; + pr_debug("%s: %s: bkops state %x\n", + mmc_hostname(card->host), __func__, bkops_en); } out: return ret; @@ -1246,9 +1248,7 @@ void mmc_check_bkops(struct mmc_card *card) BUG_ON(!card); - if (unlikely(!mmc_card_configured_manual_bkops(card))) - return; - if (mmc_card_doing_bkops(card) || mmc_card_doing_auto_bkops(card)) + if (mmc_card_doing_bkops(card)) return; err = mmc_read_bkops_status(card); @@ -1258,12 +1258,12 @@ void mmc_check_bkops(struct mmc_card *card) return; } + card->bkops.needs_check = false; + mmc_update_bkops_level(&card->bkops.stats, card->ext_csd.raw_bkops_status); - if (card->ext_csd.raw_bkops_status < EXT_CSD_BKOPS_LEVEL_2) - return; - card->bkops.needs_manual = true; + card->bkops.needs_bkops = card->ext_csd.raw_bkops_status > 0; } EXPORT_SYMBOL(mmc_check_bkops); @@ -1296,7 +1296,7 @@ void mmc_start_manual_bkops(struct mmc_card *card) } else { mmc_card_set_doing_bkops(card); mmc_update_bkops_start(&card->bkops.stats); - card->bkops.needs_manual = false; + card->bkops.needs_bkops = false; } mmc_retune_release(card->host); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 4447bc6f8e76..f61a3b965f55 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -2713,6 +2713,64 @@ static int mmc_resume(struct mmc_host *host) return err; } +#define MAX_DEFER_SUSPEND_COUNTER 20 +static bool mmc_process_bkops(struct mmc_host *host) +{ + int err = 0; + bool is_running = false; + u32 status; + + mmc_claim_host(host); + if (mmc_card_cmdq(host->card)) { + BUG_ON(host->cmdq_ctx.active_reqs); + + err = mmc_cmdq_halt(host, true); + if (err) { + pr_err("%s: halt: failed: %d\n", __func__, err); + goto unhalt; + } + } + + if (mmc_card_doing_bkops(host->card)) { + /* check that manual bkops finished */ + err = mmc_send_status(host->card, &status); + if (err) { + pr_err("%s: Get card status fail\n", __func__); + goto unhalt; + } + if (R1_CURRENT_STATE(status) != R1_STATE_PRG) { + mmc_card_clr_doing_bkops(host->card); + goto unhalt; + } + } else { + mmc_check_bkops(host->card); + } + + if (host->card->bkops.needs_bkops && + !mmc_card_support_auto_bkops(host->card)) + mmc_start_manual_bkops(host->card); + +unhalt: + if (mmc_card_cmdq(host->card)) { + err = mmc_cmdq_halt(host, false); + if (err) + pr_err("%s: unhalt: failed: %d\n", __func__, err); + } + mmc_release_host(host); + + if (host->card->bkops.needs_bkops || + mmc_card_doing_bkops(host->card)) { + if (host->card->bkops.retry_counter++ < + MAX_DEFER_SUSPEND_COUNTER) { + host->card->bkops.needs_check = true; + is_running = true; + } else { + host->card->bkops.retry_counter = 0; + } + } + return is_running; +} + /* * Callback for runtime_suspend. */ @@ -2724,6 +2782,12 @@ static int mmc_runtime_suspend(struct mmc_host *host) if (!(host->caps & MMC_CAP_AGGRESSIVE_PM)) return 0; + if (mmc_process_bkops(host)) { + pm_runtime_mark_last_busy(&host->card->dev); + pr_debug("%s: defered, need bkops\n", __func__); + return -EBUSY; + } + err = _mmc_suspend(host, true); if (err) pr_err("%s: error %d doing aggressive suspend\n", diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index 17c5f9d69801..ca0d98dfc643 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2015, The Linux Foundation. All rights reserved. +/* Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -876,8 +876,8 @@ skip_cqterri: * exception once the queue is empty */ BUG_ON(!mmc->card); - if (mmc_card_configured_manual_bkops(mmc->card) && - !mmc_card_configured_auto_bkops(mmc->card)) + if (mmc_card_configured_manual_bkops(mmc->card) || + mmc_card_configured_auto_bkops(mmc->card)) mmc->card->bkops.needs_check = true; mrq->cmdq_req->resp_err = true; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index d88f6a027679..0f0c9963950e 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -327,7 +327,8 @@ struct mmc_bkops_stats { struct mmc_bkops_info { struct mmc_bkops_stats stats; bool needs_check; - bool needs_manual; + bool needs_bkops; + u32 retry_counter; }; enum mmc_pon_type { From a1202f10d990ac8d3be7471f4c415f2761a1e941 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 3 Mar 2016 15:07:24 +0530 Subject: [PATCH 460/472] mmc: card: Fix broken clock gating The commit '77dacd' misses to add mmc_host_clk_release() in the completion path of a CQ request i.e., in mmc_blk_cmdq_complete_rq(). Hence, the reference counter of clocks (host->clk_requests) never becomes 0, preventing the clocks from gating. However, the clocks are still turned off through other power management features such as runtime/system suspend. Change-Id: I0032861b1e5218bdf3c5bed664869c708ce50148 Signed-off-by: Sahitya Tummala --- drivers/mmc/card/block.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 9971055b9ffd..74a7c6ca6826 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -3300,6 +3300,7 @@ out: mmc_cmdq_clk_scaling_stop_busy(host, true, is_dcmd); if (!test_bit(CMDQ_STATE_ERR, &ctx_info->curr_state)) { + mmc_host_clk_release(host); wake_up(&ctx_info->wait); mmc_put_card(host->card); } From e4db7c6cf66a4a9bbb4e9657f6852ba4cdeef8fa Mon Sep 17 00:00:00 2001 From: Ritesh Harjani Date: Thu, 25 Feb 2016 17:54:58 +0530 Subject: [PATCH 461/472] mmc: block: Add quirk and increase read data timeout for hynix emmc Hynix emmc cards are causing read data timeout. Increase timeout value to max of 4sec and add card quirk for all Hynix emmc cards. Change-Id: I78637dc787964ec5cafe297587d6a12ecf1d31c6 Signed-off-by: Ritesh Harjani --- drivers/mmc/card/block.c | 7 +++++++ drivers/mmc/core/core.c | 4 +++- include/linux/mmc/card.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 74a7c6ca6826..71caa588a019 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -4110,6 +4110,13 @@ static const struct mmc_fixup blk_fixups[] = MMC_FIXUP("Q7XSAB", CID_MANFID_SAMSUNG, 0x100, add_quirk_mmc, MMC_QUIRK_LONG_READ_TIME), + /* + * Hynix eMMC cards need longer data read timeout than + * indicated in CSD. + */ + MMC_FIXUP(CID_NAME_ANY, CID_MANFID_HYNIX, CID_OEMID_ANY, add_quirk_mmc, + MMC_QUIRK_LONG_READ_TIME), + /* * On these Samsung MoviNAND parts, performing secure erase or * secure trim can result in unrecoverable corruption due to a diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 13e06f96d0c9..65113800026c 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1962,9 +1962,11 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) * Address this by setting the read timeout to a "reasonably high" * value. For the cards tested, 300ms has proven enough. If necessary, * this value can be increased if other problematic cards require this. + * Certain Hynix 5.x cards giving read timeout even with 300ms. + * Increasing further to max value (4s). */ if (mmc_card_long_read_time(card) && data->flags & MMC_DATA_READ) { - data->timeout_ns = 300000000; + data->timeout_ns = 4000000000u; data->timeout_clks = 0; } diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 0f0c9963950e..8ebf9988b04e 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -486,6 +486,7 @@ struct mmc_fixup { #define CID_MANFID_MICRON 0x13 #define CID_MANFID_SAMSUNG 0x15 #define CID_MANFID_KINGSTON 0x70 +#define CID_MANFID_HYNIX 0x90 #define CID_MANFID_ANY (-1u) #define CID_OEMID_ANY ((unsigned short) -1) From d69edcbcaed2001b9c305a2b8668dc0599801f2a Mon Sep 17 00:00:00 2001 From: Asutosh Das Date: Wed, 16 Mar 2016 14:44:52 +0530 Subject: [PATCH 462/472] mmc: core: fix deadlock between runtime-suspend and devfreq There's a deadlock between runtime-suspend and devfreq monitor. runtime-suspend context: [] __switch_to+0x7c [] __schedule+0x53c [] schedule+0x74 [] schedule_preempt_disabled+0x14 [] __mutex_lock_slowpath+0x1dc [] mutex_lock+0x2c [] devfreq_monitor_suspend+0x1c [] devfreq_simple_ondemand_handler+0x4c [] devfreq_suspend_device+0x2c [] mmc_suspend_clk_scaling+0xac [] _mmc_suspend.isra.11+0x38 [] mmc_runtime_suspend+0x1cc [] mmc_runtime_suspend+0x18 [] __rpm_callback+0x40 [] rpm_callback+0x40 [] rpm_suspend+0x25c In the above stack, runtime-suspend is waiting on the devfreq mutex. In the below stack, the mutex has been acquired and since the device power state is DEV_SUSPENDING, devfreq is waiting for the device to resume, thus causing a deadlock. [] schedule+0x74 [] rpm_resume+0x264 [] __pm_runtime_resume+0x70 [] mmc_get_card+0x1c [] mmc_devfreq_set_target+0x154 [] update_devfreq+0xd0 [] devfreq_monitor+0x2c Fix this by not waiting for device to resume in devfreq. The suspend would ensure that this devfreq monitor thread is suspended before suspending the device. Change-Id: Ic0e89becbfded35c90c4061f0fe03d61125f66d5 Signed-off-by: Asutosh Das --- drivers/mmc/core/core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 65113800026c..749bb74cb3a6 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -511,7 +511,6 @@ static int mmc_devfreq_set_target(struct device *dev, clk_scaling->need_freq_change = false; mmc_host_clk_hold(host); - mmc_get_card(host->card); err = mmc_clk_update_freq(host, *freq, clk_scaling->state); if (err && err != -EAGAIN) pr_err("%s: clock scale to %lu failed with error %d\n", @@ -520,7 +519,6 @@ static int mmc_devfreq_set_target(struct device *dev, pr_debug("%s: clock change to %lu finished successfully (%s)\n", mmc_hostname(host), *freq, current->comm); - mmc_put_card(host->card); mmc_host_clk_release(host); mmc_release_host(host); From 8cd010a89a5207965584aa1b716591ccc9c7f525 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Tue, 2 Sep 2014 18:02:57 -0700 Subject: [PATCH 463/472] mmc: host: sdhci: don't queue zero length descriptor If zero length ADMA descriptor with "Tran" attribute is queued to ADMA descriptor table then host controller ADMA engine might get stuck. Currently we are seeing zero length descriptor getting queued in for SDIO transactions: SDHCi driver requires that any data buffer address should be 4-byte aligned for 32-bit ADMA and 8-bytes aligned for 64-bit ADMA. For 64-bit ADMA, it forces 8-byte alignment for any data buffer addresses. This aligment requirement is not an issue with eMMC/SD cards as their transactions are always in multiple of 512-bytes (block size) and hence sdhci driver would always get the data buffers whose address is properly aligned. SDIO can have transactions less than 512-bytes. Hence its quite possible for driver to receives data buffer addresses which are not properly aligned. And if they are not aligned, SDHCi driver bounces the non-aligned bytes to pre-allocated aligned buffer until the rest of the data buffer becomes aligned. But this logic has a bug where after moving the non-aligned number of bytes to aligned buffer, it assumes that we will always be left with non-zero number of bytes in original data buffer and hence driver ends up queuing one more descriptor which can be of zero length. Fix this by checking for the remaining data length before queuing up the next descriptor. Change-Id: I7af77b2a2661c00f2b1da47953717b1506bdba83 Signed-off-by: Subhash Jadavani [subhashj@codeaurora.org: fixed merge conflicts] Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2b34aec58c2c..572555876643 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -626,9 +626,11 @@ static int sdhci_adma_table_pre(struct sdhci_host *host, BUG_ON(len > 65536); - /* tran, valid */ - sdhci_adma_write_desc(host, desc, addr, len, ADMA2_TRAN_VALID); - desc += host->desc_sz; + if (len) { + /* tran, valid */ + sdhci_adma_write_desc(host, desc, addr, len, ADMA2_TRAN_VALID); + desc += host->desc_sz; + } /* * If this triggers then we have a calculation bug From b58dd25cc3b50ff27f666500c74bf57d335d8292 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 12 May 2016 16:54:20 -0700 Subject: [PATCH 464/472] Revert "mmc: core: get drive types supported by eMMC cards" This reverts commit 82ccc79fd3c4456b7b344db4576e874d324c6243. Similar change is already present and this one just added the duplicate code hence revert it. Change-Id: Ia96ebe80e4fb7987f9e98e42575ff2ebe153dd23 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/mmc.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index f61a3b965f55..2dc1e3edc11e 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -401,8 +401,6 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.raw_card_type = ext_csd[EXT_CSD_CARD_TYPE]; mmc_select_card_type(card); - card->ext_csd.raw_driver_strength = ext_csd[EXT_CSD_DRIVER_STRENGTH]; - card->ext_csd.raw_s_a_timeout = ext_csd[EXT_CSD_S_A_TIMEOUT]; card->ext_csd.raw_erase_timeout_mult = ext_csd[EXT_CSD_ERASE_TIMEOUT_MULT]; From 79d2f1626ba69198feff3376684f39b9e0838169 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 12 May 2016 11:43:55 -0700 Subject: [PATCH 465/472] mmc: card: fix quirk bit map There seems to be bit map usage overlap between few MMC_QUIRKS_*, this change fixes it. Change-Id: I717a2de1261ed2de1a8c730b005b137f0687d969 Signed-off-by: Subhash Jadavani --- include/linux/mmc/card.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 8ebf9988b04e..40665cb0ccde 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -386,9 +386,9 @@ struct mmc_card { #define MMC_QUIRK_INAND_DATA_TIMEOUT (1<<13) /* For incorrect data timeout */ #define MMC_QUIRK_BROKEN_HPI (1 << 14) /* For devices which gets */ /* broken due to HPI feature */ -#define MMC_QUIRK_CACHE_DISABLE (1 << 14) /* prevent cache enable */ -#define MMC_QUIRK_QCA6574_SETTINGS (1 << 15) /* QCA6574 card settings*/ -#define MMC_QUIRK_QCA9377_SETTINGS (1 << 16) /* QCA9377 card settings*/ +#define MMC_QUIRK_CACHE_DISABLE (1 << 15) /* prevent cache enable */ +#define MMC_QUIRK_QCA6574_SETTINGS (1 << 16) /* QCA6574 card settings*/ +#define MMC_QUIRK_QCA9377_SETTINGS (1 << 17) /* QCA9377 card settings*/ /* Make sure CMDQ is empty before queuing DCMD */ From e8032e07d1fbe7f71be0af640981f3a587df1d4d Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 12 May 2016 11:58:15 -0700 Subject: [PATCH 466/472] mmc: auto bkops fixes Change "man_bkops_en" to "bkops_en" to hold the status of both manual and auto bkops. Change-Id: I60029bae67cebb2c91147ad741b96f4caed9c1d9 Signed-off-by: Subhash Jadavani --- drivers/mmc/core/core.c | 6 +++--- drivers/mmc/core/mmc.c | 10 +++------- include/linux/mmc/card.h | 6 +++--- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 749bb74cb3a6..2197e2495b88 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1204,11 +1204,11 @@ int mmc_set_auto_bkops(struct mmc_card *card, bool enable) if (enable) { if (mmc_card_doing_auto_bkops(card)) goto out; - bkops_en = card->ext_csd.man_bkops_en | EXT_CSD_BKOPS_AUTO_EN; + bkops_en = card->ext_csd.bkops_en | EXT_CSD_BKOPS_AUTO_EN; } else { if (!mmc_card_doing_auto_bkops(card)) goto out; - bkops_en = card->ext_csd.man_bkops_en & ~EXT_CSD_BKOPS_AUTO_EN; + bkops_en = card->ext_csd.bkops_en & ~EXT_CSD_BKOPS_AUTO_EN; } ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BKOPS_EN, @@ -1224,7 +1224,7 @@ int mmc_set_auto_bkops(struct mmc_card *card, bool enable) mmc_card_clr_auto_bkops(card); mmc_update_bkops_auto_off(&card->bkops.stats); } - card->ext_csd.man_bkops_en = bkops_en; + card->ext_csd.bkops_en = bkops_en; pr_debug("%s: %s: bkops state %x\n", mmc_hostname(card->host), __func__, bkops_en); } diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 2dc1e3edc11e..fa3fd73b4bc5 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -530,17 +530,13 @@ static int mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd) if ((ext_csd[EXT_CSD_BKOPS_SUPPORT] & 0x1) && card->ext_csd.hpi) { card->ext_csd.bkops = 1; - card->ext_csd.man_bkops_en = - (ext_csd[EXT_CSD_BKOPS_EN] & - EXT_CSD_MANUAL_BKOPS_MASK); + 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.man_bkops_en) + if (!card->ext_csd.bkops_en) pr_info("%s: BKOPS_EN equals 0x%x\n", mmc_hostname(card->host), - card->ext_csd.man_bkops_en); - - + card->ext_csd.bkops_en); } /* check whether the eMMC card supports HPI */ diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 40665cb0ccde..e7578be38ddf 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -88,7 +88,7 @@ struct mmc_ext_csd { bool hpi; /* HPI support bit */ unsigned int hpi_cmd; /* cmd used as HPI */ bool bkops; /* background support bit */ - u8 man_bkops_en; /* manual bkops enable */ + u8 bkops_en; /* bkops enable */ 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 */ @@ -678,12 +678,12 @@ static inline bool mmc_card_support_auto_bkops(const struct mmc_card *c) static inline bool mmc_card_configured_manual_bkops(const struct mmc_card *c) { - return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_MANUAL_EN; + return c->ext_csd.bkops_en & EXT_CSD_BKOPS_MANUAL_EN; } static inline bool mmc_card_configured_auto_bkops(const struct mmc_card *c) { - return c->ext_csd.man_bkops_en & EXT_CSD_BKOPS_AUTO_EN; + return c->ext_csd.bkops_en & EXT_CSD_BKOPS_AUTO_EN; } static inline bool mmc_enable_qca6574_settings(const struct mmc_card *c) From a037c5f8a54879af3c9e39bc8f6620219257511c Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 11 May 2016 16:35:47 -0700 Subject: [PATCH 467/472] mmc: sdhci: Replace SDHCI_USE_ADMA_64BIT flag SDHCI_USE_ADMA_64BIT flag was being used by msm-3.18 kernel but now that upstream sdhci driver has introduced support for 64-bit ADMA, it defined new flag SDHCI_USE_64_BIT_DMA. Hence replace all the SDHCI_USE_ADMA_64BIT references with SDHCI_USE_64_BIT_DMA flag. Change-Id: I6a1e426c156d4ddb92bba00b5ec0cfb156d9550b Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 6 +++--- drivers/mmc/host/sdhci.h | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 572555876643..a25575f87b7e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1518,7 +1518,7 @@ static void sdhci_notify_halt(struct mmc_host *mmc, bool halt) pr_debug("%s: halt notification was sent, halt=%d\n", mmc_hostname(mmc), halt); - if (host->flags & SDHCI_USE_ADMA_64BIT) { + if (host->flags & SDHCI_USE_64_BIT_DMA) { if (halt) host->adma_desc_line_sz = 16; else @@ -4173,7 +4173,7 @@ int sdhci_add_host(struct sdhci_host *host) } if (mmc->caps2 & MMC_CAP2_CMD_QUEUE) { - bool dma64 = (host->flags & SDHCI_USE_ADMA_64BIT) ? + bool dma64 = (host->flags & SDHCI_USE_64_BIT_DMA) ? true : false; ret = sdhci_cmdq_init(host, mmc, dma64); if (ret) @@ -4186,7 +4186,7 @@ int sdhci_add_host(struct sdhci_host *host) pr_info("%s: SDHCI controller on %s [%s] using %s in %s mode\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), (host->flags & SDHCI_USE_ADMA) ? - ((host->flags & SDHCI_USE_ADMA_64BIT) ? + ((host->flags & SDHCI_USE_64_BIT_DMA) ? "64-bit ADMA" : "32-bit ADMA") : ((host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"), ((mmc->caps2 & MMC_CAP2_CMD_QUEUE) && !ret) ? diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 280eafd36ec9..fab63762623b 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -531,7 +531,6 @@ struct sdhci_host { #define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */ #define SDHCI_SDR104_NEEDS_TUNING (1<<10) /* SDR104/HS200 needs tuning */ #define SDHCI_USE_64_BIT_DMA (1<<12) /* Use 64-bit DMA */ -#define SDHCI_USE_ADMA_64BIT (1<<12) /* Host is 64-bit ADMA capable */ #define SDHCI_HS400_TUNING (1<<13) /* Tuning for HS400 */ #define SDHCI_HOST_IRQ_STATUS (1<<14) /* host->irq status */ From 330d3d586e0297385607aaddc192953c5e09321b Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 11 May 2016 16:47:20 -0700 Subject: [PATCH 468/472] mmc: sdhci: enable 64-bit DMA support only if chipset supports 64-bit This patch adds check to enable the 64-bit DMA only if the chipset supports the same. Change-Id: I5dbe6f91d5e530b289f4cf395ae7b673acd85fcf Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index a25575f87b7e..bb4165ae16a1 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -3629,6 +3629,25 @@ static const struct cmdq_host_ops sdhci_cmdq_ops = { .post_cqe_halt = sdhci_cmdq_post_cqe_halt, }; +#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT +static int sdhci_is_adma2_64bit(struct sdhci_host *host) +{ + u32 caps; + + caps = (host->quirks & SDHCI_QUIRK_MISSING_CAPS) ? host->caps : + sdhci_readl(host, SDHCI_CAPABILITIES); + + if (caps & SDHCI_CAN_64BIT) + return 1; + return 0; +} +#else +static int sdhci_is_adma2_64bit(struct sdhci_host *host) +{ + return 0; +} +#endif + int sdhci_add_host(struct sdhci_host *host) { struct mmc_host *mmc; @@ -3701,7 +3720,7 @@ int sdhci_add_host(struct sdhci_host *host) * SDHCI_QUIRK2_BROKEN_64_BIT_DMA must be left to the drivers to * implement. */ - if (sdhci_readl(host, SDHCI_CAPABILITIES) & SDHCI_CAN_64BIT) + if (sdhci_is_adma2_64bit(host)) host->flags |= SDHCI_USE_64_BIT_DMA; if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { From 95dee386c530bd90b84144bd2fca1adf56fa2634 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Wed, 11 May 2016 17:01:06 -0700 Subject: [PATCH 469/472] mmc: sdhci: clean up legacy adma related variables msm-3.18 kernel had our own implementation of 64-bit ADMA support but upstream kernel have now added its own implementation of 64-bit ADMA support hence this patch removes some of the remaining variables from our implementation. Change-Id: I3ea8e9e498b25b75fb1f5ccc4ffe5f6986cd564a Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci.c | 4 ++-- drivers/mmc/host/sdhci.h | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index bb4165ae16a1..9beb37b4e241 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1520,9 +1520,9 @@ static void sdhci_notify_halt(struct mmc_host *mmc, bool halt) mmc_hostname(mmc), halt); if (host->flags & SDHCI_USE_64_BIT_DMA) { if (halt) - host->adma_desc_line_sz = 16; + host->desc_sz = 16; else - host->adma_desc_line_sz = 12; + host->desc_sz = SDHCI_ADMA2_64_DESC_SZ; } } diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index fab63762623b..9b39821b57ef 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -564,11 +564,6 @@ struct sdhci_host { size_t adma_table_sz; /* ADMA descriptor table size */ size_t align_buffer_sz; /* Bounce buffer size */ - unsigned int adma_desc_sz; /* ADMA descriptor table size */ - unsigned int adma_desc_line_sz; /* ADMA descriptor line size */ - unsigned int align_buf_sz; /* Bounce buffer size */ - unsigned int adma_max_desc; /* Max ADMA descriptos (max sg segments) */ - dma_addr_t adma_addr; /* Mapped ADMA descr. table */ dma_addr_t align_addr; /* Mapped bounce buffer */ From 7043a9fe7bcb7c32a8d9fc83db2627cbc8e01b5f Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 12 May 2016 12:19:44 +0530 Subject: [PATCH 470/472] mmc: host: fix compilation issue when clk_gating config is disabled This change fixes the compilation issue seen when CONFIG_MMC_CLKGATE isn't enabled. Change-Id: Ia2765c302bd16be5ee87c42629ff8f36b41242ca Signed-off-by: Sahitya Tummala Signed-off-by: Subhash Jadavani --- drivers/mmc/core/host.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 657c08ef573d..11f824a78d29 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -312,6 +312,10 @@ static inline void mmc_host_clk_sysfs_init(struct mmc_host *host) { } +bool mmc_host_may_gate_card(struct mmc_card *card) +{ + return false; +} #endif void mmc_retune_enable(struct mmc_host *host) From 3b5d76efd7dcc27117147c6d3eacb319a8a20cc4 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 12 May 2016 12:37:59 +0530 Subject: [PATCH 471/472] mmc: cmdq_hci: fix compilation issue This change fixes the compilation issue when CMDQ feature is enabled. Change-Id: I1c792f7b32d79267f5c39f8bdbd2bd6884fb0dde Signed-off-by: Sahitya Tummala Signed-off-by: Subhash Jadavani --- drivers/mmc/host/cmdq_hci.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/mmc/host/cmdq_hci.c b/drivers/mmc/host/cmdq_hci.c index ca0d98dfc643..71d20922bd4c 100644 --- a/drivers/mmc/host/cmdq_hci.c +++ b/drivers/mmc/host/cmdq_hci.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include "cmdq_hci.h" From 2c1f2ce7a87b5c09f7a23d86b6d87bb42a9b2384 Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Thu, 12 May 2016 12:20:11 +0530 Subject: [PATCH 472/472] mmc: sdhci-msm: fix few compilation issues This change fixes few compilations issues seen if we enable the SDHCi MSM driver. Change-Id: Iaa556e189cbbc6a7f9c3d485e94a43cb21a968a7 Signed-off-by: Sahitya Tummala Signed-off-by: Subhash Jadavani --- drivers/mmc/host/sdhci-msm.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c index 39de38908718..fecb45daa827 100644 --- a/drivers/mmc/host/sdhci-msm.c +++ b/drivers/mmc/host/sdhci-msm.c @@ -2001,9 +2001,11 @@ static void sdhci_msm_bus_voting(struct sdhci_host *host, u32 enable) * after SDHCI_MSM_MMC_CLK_GATE_DELAY and thus no * additional delay is required to remove the bus vote. */ +#ifdef CONFIG_MMC_CLKGATE if (host->mmc->clkgate_delay) sdhci_msm_bus_cancel_work_and_set_vote(host, 0); else +#endif sdhci_msm_bus_queue_work(host); } } @@ -2057,13 +2059,13 @@ static int sdhci_msm_vreg_set_optimum_mode(struct sdhci_msm_reg_data * do not support regulator_set_optimum_mode */ if (vreg->set_voltage_sup) { - ret = regulator_set_optimum_mode(vreg->reg, uA_load); + ret = regulator_set_load(vreg->reg, uA_load); if (ret < 0) - pr_err("%s: regulator_set_optimum_mode(reg=%s,uA_load=%d) failed. ret=%d\n", + pr_err("%s: regulator_set_load(reg=%s,uA_load=%d) failed. ret=%d\n", __func__, vreg->name, uA_load, ret); else /* - * regulator_set_optimum_mode() can return non zero + * regulator_set_load() can return non zero * value even for success case. */ ret = 0; @@ -4200,8 +4202,10 @@ static int sdhci_msm_probe(struct platform_device *pdev) /* Enable pwr irq interrupts */ writel_relaxed(INT_MASK, (msm_host->core_mem + CORE_PWRCTL_MASK)); +#ifdef CONFIG_MMC_CLKGATE /* Set clock gating delay to be used when CONFIG_MMC_CLKGATE is set */ msm_host->mmc->clkgate_delay = SDHCI_MSM_MMC_CLK_GATE_DELAY; +#endif /* Set host capabilities */ msm_host->mmc->caps |= msm_host->pdata->mmc_bus_width; @@ -4292,7 +4296,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) __func__, msm_host->pdata->sdiowakeup_irq, ret); msm_host->pdata->sdiowakeup_irq = -1; msm_host->is_sdiowakeup_enabled = false; - goto free_cd_gpio; + goto vreg_deinit; } else { spin_lock_irqsave(&host->lock, flags); sdhci_msm_cfg_sdiowakeup_gpio_irq(host, false); @@ -4305,7 +4309,7 @@ static int sdhci_msm_probe(struct platform_device *pdev) ret = sdhci_add_host(host); if (ret) { dev_err(&pdev->dev, "Add host failed (%d)\n", ret); - goto free_cd_gpio; + goto vreg_deinit; } pm_runtime_set_active(&pdev->dev); @@ -4354,9 +4358,6 @@ remove_host: dead = (readl_relaxed(host->ioaddr + SDHCI_INT_STATUS) == 0xffffffff); pm_runtime_disable(&pdev->dev); sdhci_remove_host(host, dead); -free_cd_gpio: - if (gpio_is_valid(msm_host->pdata->status_gpio)) - mmc_gpio_free_cd(msm_host->mmc); vreg_deinit: sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); bus_unregister: @@ -4404,9 +4405,6 @@ static int sdhci_msm_remove(struct platform_device *pdev) sdhci_remove_host(host, dead); sdhci_pltfm_free(pdev); - if (gpio_is_valid(msm_host->pdata->status_gpio)) - mmc_gpio_free_cd(msm_host->mmc); - sdhci_msm_vreg_init(&pdev->dev, msm_host->pdata, false); sdhci_msm_setup_pins(pdata, true);