lkml.org 
[lkml]   [2018]   [Oct]   [1]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
SubjectRe: [PATCH RFC 2/7] mmc: core: devfreq: Add devfreq based clock scaling support
From
Date

On 7/23/2018 3:31 PM, Vijay Viswanath wrote:
> Hi Sayali,
>
> On 7/13/2018 3:22 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@codeaurora.org>
>> Signed-off-by: Sayali Lokhande <sayalil@codeaurora.org>
>> ---
>>   .../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>;
>>       };
>>         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;
>> +
>
> Suppose devfreq issued a low_freq request while mmc is handling a
> request already issued to card (so mmc host is claimed by issuing
> context). So we will set target freq to LOW_FREQ and quit from here.
> Now, if devfreq comes again and issues a HIGH_FREQ request (before mmc
> thread had a chance to change the freq to LOW in
> mmc_deferred_scaling), we will skip this devfreq request (since
> current freq is high already). Then, when mmc thread comes, it will
> execute deferred scaling to LOW_FREQ.
>
> How about comparing *freq with clk_scaling->target_freq ?
Agree. Will update this in next patchset.
>
>> +    /* 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);
>>
>
> Thanks,
> Vijay

\
 
 \ /
  Last update: 2018-10-01 16:18    [W:0.130 / U:0.100 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site