Hi Sayali, On 7/13/18 6:52 PM, Sayali Lokhande wrote: > 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. > > Signed-off-by: Talel Shenhar <tatias@xxxxxxxxxxxxxx> > Signed-off-by: Sayali Lokhande <sayalil@xxxxxxxxxxxxxx> > --- > .../devicetree/bindings/mmc/sdhci-msm.txt | 10 + > drivers/mmc/core/core.c | 560 +++++++++++++++++++++ > drivers/mmc/core/core.h | 7 + > drivers/mmc/core/debugfs.c | 46 ++ > drivers/mmc/core/host.c | 8 + > drivers/mmc/core/mmc.c | 200 +++++++- > drivers/mmc/core/sd.c | 72 ++- > drivers/mmc/host/sdhci-msm.c | 37 ++ > drivers/mmc/host/sdhci-pltfm.c | 11 + > drivers/mmc/host/sdhci.c | 27 + > drivers/mmc/host/sdhci.h | 8 + > include/linux/mmc/card.h | 5 + > include/linux/mmc/host.h | 70 +++ > 13 files changed, 1059 insertions(+), 2 deletions(-) > > diff --git a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt > index 502b3b8..bd8470a 100644 > --- a/Documentation/devicetree/bindings/mmc/sdhci-msm.txt > +++ b/Documentation/devicetree/bindings/mmc/sdhci-msm.txt > @@ -26,6 +26,15 @@ Required properties: > "cal" - reference clock for RCLK delay calibration (optional) > "sleep" - sleep clock for RCLK delay calibration (optional) > > +Optional Properties: > +- 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. > Example: > > sdhc_1: sdhci@f9824900 { > @@ -43,6 +52,7 @@ Example: > > clocks = <&gcc GCC_SDCC1_APPS_CLK>, <&gcc GCC_SDCC1_AHB_CLK>; > clock-names = "core", "iface"; > + qcom,devfreq,freq-table = <52000000 200000000>; OPP framwork already support the DT binding method to get the frequency. You can refer to opp.txt[1] [1] /Documentation/devicetree/bindings/opp/opp.txt For example, can use 'operating-points-v2' defined property instead of qcom,devfreq,freq-table'. operating-points-v2 = <&bus_dmc_opp_table>; mmc_opp_table: opp_table0 { compatible = "operating-points-v2"; opp-52000000 { opp-hz = /bits/ 64 <52000000>; }; opp-200000000 { opp-hz = /bits/ 64 <200000000>; }; }; Also, if you need the reference code using OPP, you can check them[3][4] [3] drivers/devfreq/exynos-bus.c [4] Documentation/devicetree/bindings/devfreq/exynos-bus.txt > }; > > sdhc_2: sdhci@f98a4900 { > diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c > index 281826d..0eaee42 100644 > --- a/drivers/mmc/core/core.c > +++ b/drivers/mmc/core/core.c > @@ -14,6 +14,7 @@ > #include <linux/init.h> > #include <linux/interrupt.h> > #include <linux/completion.h> > +#include <linux/devfreq.h> > #include <linux/device.h> > #include <linux/delay.h> > #include <linux/pagemap.h> > @@ -112,6 +113,556 @@ static inline void mmc_should_fail_request(struct mmc_host *host, > > #endif /* CONFIG_FAIL_MMC_REQUEST */ > > +static bool mmc_is_data_request(struct mmc_request *mmc_request) > +{ > + 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) > +{ > + struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; > + > + if (!clk_scaling->enable) > + return; > + > + if (lock_needed) > + spin_lock_bh(&clk_scaling->lock); > + > + clk_scaling->start_busy = ktime_get(); > + clk_scaling->is_busy_started = true; > + > + if (lock_needed) > + spin_unlock_bh(&clk_scaling->lock); > +} > + > +static void mmc_clk_scaling_stop_busy(struct mmc_host *host, bool lock_needed) > +{ > + struct mmc_devfeq_clk_scaling *clk_scaling = &host->clk_scaling; > + > + if (!clk_scaling->enable) > + return; > + > + if (lock_needed) > + spin_lock_bh(&clk_scaling->lock); > + > + 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_bh(&clk_scaling->lock); > +} > + > +/** > + * 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) > +{ > + 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_bh(&clk_scaling->lock); > + > + /* 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_bh(&clk_scaling->lock); > + > + return 0; > +} > + > +static bool mmc_is_valid_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 || > + mmc_card_doing_bkops(card)))) > + 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; > + } > + > + /* 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 (freq == host->clk_scaling.curr_freq) > + goto out; > + > + 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; > + } > + } > + > + 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 (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) > +{ > + 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 pflags = current->flags; > + > + /* Ensure scaling would happen even in memory pressure conditions */ > + current->flags |= PF_MEMALLOC; > + > + 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_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_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_bh(&clk_scaling->lock); > + > + abort = __mmc_claim_host(host, NULL, &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; > + > + 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_release_host(host); > +out: > + current->flags &= ~PF_MEMALLOC; > + current->flags |= pflags & PF_MEMALLOC; > + return err; > +} > + > +/** > + * 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; > + > + if (!host->clk_scaling.enable) > + return; > + > + spin_lock_bh(&host->clk_scaling.lock); > + > + if (host->clk_scaling.clk_scaling_in_progress || > + !(host->clk_scaling.need_freq_change)) { > + spin_unlock_bh(&host->clk_scaling.lock); > + 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_bh(&host->clk_scaling.lock); > + 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); > +} > +EXPORT_SYMBOL(mmc_deferred_scaling); > + > +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); > + > + /* > + * Create the frequency table and initialize it with default values. > + * Initialize it with platform specific frequencies if the frequency > + * table supplied by platform driver is present, otherwise initialize > + * it with min and max frequencies supported by the card. > + */ > + if (!clk_scaling->freq_table) { > + if (clk_scaling->pltfm_freq_table_sz) > + clk_scaling->freq_table_sz = > + clk_scaling->pltfm_freq_table_sz; > + else > + clk_scaling->freq_table_sz = 2; > + > + clk_scaling->freq_table = kzalloc( > + (clk_scaling->freq_table_sz * > + sizeof(*(clk_scaling->freq_table))), GFP_KERNEL); > + if (!clk_scaling->freq_table) > + return -ENOMEM; > + > + if (clk_scaling->pltfm_freq_table) { > + memcpy(clk_scaling->freq_table, > + clk_scaling->pltfm_freq_table, > + (clk_scaling->pltfm_freq_table_sz * > + sizeof(*(clk_scaling->pltfm_freq_table)))); > + } else { > + pr_debug("%s: no frequency table defined - setting default\n", > + mmc_hostname(host)); > + clk_scaling->freq_table[0] = > + host->card->clk_scaling_lowest; > + clk_scaling->freq_table[1] = > + host->card->clk_scaling_highest; > + 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: > + /** > + * devfreq requires unsigned long type freq_table while the > + * freq_table in clk_scaling is un32. Here allocates an individual > + * memory space for it and release it when exit clock scaling. > + */ > + clk_scaling->devfreq_profile.freq_table = kzalloc( > + clk_scaling->freq_table_sz * > + sizeof(*(clk_scaling->devfreq_profile.freq_table)), > + GFP_KERNEL); > + if (!clk_scaling->devfreq_profile.freq_table) > + return -ENOMEM; > + clk_scaling->devfreq_profile.max_state = clk_scaling->freq_table_sz; > + > + for (i = 0; i < clk_scaling->freq_table_sz; i++) { > + clk_scaling->devfreq_profile.freq_table[i] = > + clk_scaling->freq_table[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.upthreshold - 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("%s: bad host parameter\n", __func__); > + WARN_ON(1); > + 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; > + } > + > + err = devfreq_remove_device(host->clk_scaling.devfreq); > + if (err) { > + pr_err("%s: remove devfreq failed (%d)\n", > + mmc_hostname(host), err); > + return err; > + } > + > + kfree(host->clk_scaling.devfreq_profile.freq_table); > + > + host->clk_scaling.devfreq = NULL; > + atomic_set(&host->clk_scaling.devfreq_abort, 1); > + > + kfree(host->clk_scaling.freq_table); > + host->clk_scaling.freq_table = NULL; > + > + pr_debug("%s: devfreq was removed\n", mmc_hostname(host)); > + > + return 0; > +} > +EXPORT_SYMBOL(mmc_exit_clk_scaling); > + > static inline void mmc_complete_cmd(struct mmc_request *mrq) > { > if (mrq->cap_cmd_during_tfr && !completion_done(&mrq->cmd_completion)) > @@ -143,6 +694,9 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) > struct mmc_command *cmd = mrq->cmd; > int err = cmd->error; > > + 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 && > cmd->opcode != MMC_SEND_TUNING_BLOCK_HS200) && > @@ -354,6 +908,12 @@ int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) > return err; > > led_trigger_event(host->led, LED_FULL); > + > + if (mmc_is_data_request(mrq)) { > + mmc_deferred_scaling(host); > + mmc_clk_scaling_start_busy(host, true); > + } > + > __mmc_start_request(host, mrq); > > return 0; > diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h > index 9d8f09a..fc0a9b7 100644 > --- a/drivers/mmc/core/core.h > +++ b/drivers/mmc/core/core.h > @@ -34,6 +34,7 @@ struct mmc_bus_ops { > int (*shutdown)(struct mmc_host *); > int (*hw_reset)(struct mmc_host *); > int (*sw_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); > @@ -46,6 +47,8 @@ struct device_node *mmc_of_find_child_device(struct mmc_host *host, > > 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_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); > @@ -91,6 +94,10 @@ static inline void mmc_delay(unsigned int ms) > void mmc_add_card_debugfs(struct mmc_card *card); > void mmc_remove_card_debugfs(struct mmc_card *card); > > +extern bool mmc_can_scale_clk(struct mmc_host *host); > +extern int mmc_init_clk_scaling(struct mmc_host *host); > +extern int mmc_exit_clk_scaling(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/debugfs.c b/drivers/mmc/core/debugfs.c > index d2275c5..630ca8e 100644 > --- a/drivers/mmc/core/debugfs.c > +++ b/drivers/mmc/core/debugfs.c > @@ -225,6 +225,43 @@ 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 <linux/delay.h> > + > +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); > + > + /* 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_release_host(host); > + > + return err; > +} > + > +DEFINE_SIMPLE_ATTRIBUTE(mmc_scale_fops, mmc_scale_get, mmc_scale_set, > + "%llu\n"); > + > void mmc_add_host_debugfs(struct mmc_host *host) > { > struct dentry *root; > @@ -253,6 +290,15 @@ void mmc_add_host_debugfs(struct mmc_host *host) > &mmc_clock_fops)) > goto err_node; > > + if (!debugfs_create_file("scale", 0600, root, host, > + &mmc_scale_fops)) > + goto err_node; > + > + if (!debugfs_create_bool("skip_clk_scale_freq_update", > + 0600, root, > + &host->clk_scaling.skip_clk_scale_freq_update)) > + goto err_node; > + > #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 abf9e88..1e46aa4 100644 > --- a/drivers/mmc/core/host.c > +++ b/drivers/mmc/core/host.c > @@ -32,6 +32,10 @@ > #include "pwrseq.h" > #include "sdio_ops.h" > > +#define MMC_DEVFRQ_DEFAULT_UP_THRESHOLD 35 > +#define MMC_DEVFRQ_DEFAULT_DOWN_THRESHOLD 5 > +#define MMC_DEVFRQ_DEFAULT_POLLING_MSEC 100 > + > #define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev) > > static DEFINE_IDA(mmc_host_ida); > @@ -435,6 +439,10 @@ int mmc_add_host(struct mmc_host *host) > return err; > > 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); > diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c > index 4466f5d..c8aedf3 100644 > --- a/drivers/mmc/core/mmc.c > +++ b/drivers/mmc/core/mmc.c > @@ -1526,6 +1526,170 @@ static int mmc_hs200_tuning(struct mmc_card *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: scaling low: 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 (mmc_card_ddr52(host->card)) { > + mmc_set_timing(host, MMC_TIMING_LEGACY); > + mmc_set_clock(host, MMC_HIGH_26_MAX_DTR); > + } > + > + 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; > + } > + > + 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 err; > +} > + > +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) || > + (!mmc_card_hs200(host->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. > * > * In the case of a resume, "oldcard" will contain the card > @@ -1751,6 +1915,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 > */ > @@ -1942,6 +2116,7 @@ static int mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type) > */ > static void mmc_remove(struct mmc_host *host) > { > + mmc_exit_clk_scaling(host); > mmc_remove_card(host->card); > host->card = NULL; > } > @@ -2064,6 +2239,13 @@ static int mmc_shutdown(struct mmc_host *host) > int err = 0; > > /* > + * Exit clock scaling so that it doesn't kick in after > + * power off notification is sent > + */ > + if (host->caps2 & MMC_CAP2_CLK_SCALE) > + mmc_exit_clk_scaling(host); > + > + /* > * In a specific case for poweroff notify, we need to resume the card > * before we can shutdown it properly. > */ > @@ -2132,6 +2314,7 @@ static int mmc_can_reset(struct mmc_card *card) > static int _mmc_hw_reset(struct mmc_host *host) > { > struct mmc_card *card = host->card; > + int ret; > > /* > * In the case of recovery, we can't expect flushing the cache to work > @@ -2151,7 +2334,15 @@ static int _mmc_hw_reset(struct mmc_host *host) > mmc_power_cycle(host, card->ocr); > mmc_pwrseq_reset(host); > } > - return mmc_init_card(host, card->ocr, card); > + > + ret = mmc_init_card(host, card->ocr, card); > + if (ret) { > + pr_err("%s: %s: mmc_init_card failed (%d)\n", > + mmc_hostname(host), __func__, ret); > + return ret; > + } > + > + return ret; > } > > static const struct mmc_bus_ops mmc_ops = { > @@ -2164,6 +2355,7 @@ static int _mmc_hw_reset(struct mmc_host *host) > .alive = mmc_alive, > .shutdown = mmc_shutdown, > .hw_reset = _mmc_hw_reset, > + .change_bus_speed = mmc_change_bus_speed, > }; > > /* > @@ -2220,6 +2412,12 @@ 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; > + } > + > return 0; > > remove_card: > diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c > index d0d9f90..40144c1 100644 > --- a/drivers/mmc/core/sd.c > +++ b/drivers/mmc/core/sd.c > @@ -892,7 +892,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) { > @@ -1059,6 +1062,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; > + > if (host->caps2 & MMC_CAP2_AVOID_3_3V && > host->ios.signal_voltage == MMC_SIGNAL_VOLTAGE_330) { > pr_err("%s: Host failed to negotiate down from 3.3V\n", > @@ -1082,6 +1088,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, > */ > static void mmc_sd_remove(struct mmc_host *host) > { > + mmc_exit_clk_scaling(host); > mmc_remove_card(host->card); > host->card = NULL; > } > @@ -1228,6 +1235,62 @@ static int mmc_sd_hw_reset(struct mmc_host *host) > return mmc_sd_init_card(host, host->card->ocr, host->card); > } > > +/** > + * 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; > + } > + > + 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. > + */ > + err = card->host->ops->execute_tuning(card->host, > + MMC_SEND_TUNING_BLOCK); > + > + 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 const struct mmc_bus_ops mmc_sd_ops = { > .remove = mmc_sd_remove, > .detect = mmc_sd_detect, > @@ -1238,6 +1301,7 @@ static int mmc_sd_hw_reset(struct mmc_host *host) > .alive = mmc_sd_alive, > .shutdown = mmc_sd_suspend, > .hw_reset = mmc_sd_hw_reset, > + .change_bus_speed = mmc_sd_change_bus_speed, > }; > > /* > @@ -1292,6 +1356,12 @@ 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: > diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c > index b5519a5..e9fe8c6 100644 > --- a/drivers/mmc/host/sdhci-msm.c > +++ b/drivers/mmc/host/sdhci-msm.c > @@ -1705,6 +1705,43 @@ static int sdhci_msm_register_vreg(struct sdhci_msm_host *msm_host) > > MODULE_DEVICE_TABLE(of, sdhci_msm_dt_match); > > +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) { > + 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; > +} > + > static const struct sdhci_ops sdhci_msm_ops = { > .reset = sdhci_reset, > .set_clock = sdhci_msm_set_clock, > diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c > index 02bea61..354fc68 100644 > --- a/drivers/mmc/host/sdhci-pltfm.c > +++ b/drivers/mmc/host/sdhci-pltfm.c > @@ -36,6 +36,9 @@ > #endif > #include "sdhci-pltfm.h" > > +int sdhci_msm_dt_get_array(struct device *dev, const char *prop_name, > + u32 **out, int *len, u32 size); > + > unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host) > { > struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); > @@ -101,6 +104,14 @@ void sdhci_get_of_property(struct platform_device *pdev) > > of_property_read_u32(np, "clock-frequency", &pltfm_host->clock); > > + if (sdhci_msm_dt_get_array(&pdev->dev, "qcom,devfreq,freq-table", > + &host->mmc->clk_scaling.pltfm_freq_table, > + &host->mmc->clk_scaling.pltfm_freq_table_sz, 0)) > + pr_debug("no clock scaling frequencies were supplied\n"); > + else if (!host->mmc->clk_scaling.pltfm_freq_table || > + !host->mmc->clk_scaling.pltfm_freq_table_sz) > + pr_err("bad dts clock scaling frequencies\n"); > + > if (of_find_property(np, "keep-power-in-suspend", NULL)) > host->mmc->pm_caps |= MMC_PM_KEEP_POWER; > > diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c > index 162b9af..f0aafab 100644 > --- a/drivers/mmc/host/sdhci.c > +++ b/drivers/mmc/host/sdhci.c > @@ -2427,6 +2427,32 @@ static void sdhci_card_event(struct mmc_host *mmc) > spin_unlock_irqrestore(&host->lock, flags); > } > > +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 const struct mmc_host_ops sdhci_ops = { > .request = sdhci_request, > .post_req = sdhci_post_req, > @@ -2441,6 +2467,7 @@ static void sdhci_card_event(struct mmc_host *mmc) > .execute_tuning = sdhci_execute_tuning, > .card_event = sdhci_card_event, > .card_busy = sdhci_card_busy, > + .notify_load = sdhci_notify_load, > }; > > /*****************************************************************************\ > diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h > index 3b0c97a..740471f 100644 > --- a/drivers/mmc/host/sdhci.h > +++ b/drivers/mmc/host/sdhci.h > @@ -346,6 +346,12 @@ enum sdhci_cookie { > COOKIE_MAPPED, /* mapped by sdhci_prepare_data() */ > }; > > +enum sdhci_power_policy { > + SDHCI_PERFORMANCE_MODE, > + SDHCI_POWER_SAVE_MODE, > + SDHCI_POWER_POLICY_NUM /* Always keep this one last */ > +}; > + > struct sdhci_host { > /* Data set by hardware interface driver */ > const char *hw_name; /* Hardware bus name */ > @@ -562,6 +568,8 @@ struct sdhci_host { > /* Delay (ms) between tuning commands */ > int tuning_delay; > > + enum sdhci_power_policy power_policy; > + > /* Host SDMA buffer boundary. */ > u32 sdma_boundary; > > diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h > index de73778..c713581 100644 > --- a/include/linux/mmc/card.h > +++ b/include/linux/mmc/card.h > @@ -245,6 +245,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 */ > + > unsigned int rca; /* relative card address of device */ > unsigned int type; /* card type */ > #define MMC_TYPE_MMC 0 /* MMC card */ > @@ -308,6 +312,7 @@ struct mmc_card { > unsigned int nr_parts; > > unsigned int bouncesz; /* Bounce buffer size */ > + unsigned int part_curr; > }; > > static inline bool mmc_large_sector(struct mmc_card *card) > diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h > index 64300a4..321ab39 100644 > --- a/include/linux/mmc/host.h > +++ b/include/linux/mmc/host.h > @@ -12,6 +12,7 @@ > > #include <linux/sched.h> > #include <linux/device.h> > +#include <linux/devfreq.h> > #include <linux/fault-inject.h> > > #include <linux/mmc/core.h> > @@ -82,6 +83,12 @@ struct mmc_ios { > > struct mmc_host; > > +/* states to represent load on the host */ > +enum mmc_load { > + MMC_LOAD_HIGH, > + MMC_LOAD_LOW, > +}; > + > struct mmc_host_ops { > /* > * It is optional for the host to implement pre_req and post_req in > @@ -161,6 +168,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_cqe_ops { > @@ -260,9 +268,60 @@ struct mmc_ctx { > struct task_struct *task; > }; > > +/** > + * 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; > + int pltfm_freq_table_sz; > + u32 *freq_table; > + u32 *pltfm_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; > @@ -360,6 +419,7 @@ struct mmc_host { > #define MMC_CAP2_CQE (1 << 23) /* Has eMMC command queue engine */ > #define MMC_CAP2_CQE_DCMD (1 << 24) /* CQE can issue a direct command */ > #define MMC_CAP2_AVOID_3_3V (1 << 25) /* Host must negotiate down from 3.3V */ > +#define MMC_CAP2_CLK_SCALE (1 << 26) /* Allow dynamic clk scaling */ > > int fixed_drv_type; /* fixed driver type for non-removable media */ > > @@ -523,6 +583,16 @@ static inline int mmc_regulator_set_vqmmc(struct mmc_host *mmc, > u32 mmc_vddrange_to_ocrmask(int vdd_min, int vdd_max); > int mmc_regulator_get_supply(struct mmc_host *mmc); > > +static inline void mmc_host_clear_sdr104(struct mmc_host *host) > +{ > + host->caps &= ~MMC_CAP_UHS_SDR104; > +} > + > +static inline void mmc_host_set_sdr104(struct mmc_host *host) > +{ > + host->caps |= MMC_CAP_UHS_SDR104; > +} > + > static inline int mmc_card_is_removable(struct mmc_host *host) > { > return !(host->caps & MMC_CAP_NONREMOVABLE); > -- Best Regards, Chanwoo Choi Samsung Electronics