This change adds the use of devfreq based clock scaling to MMC.
Both eMMC and SD card can 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,
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 as per load.
Sahitya Tummala (1):
devfreq: Add new flag to do simple clock scaling
Sayali Lokhande (6):
mmc: core: devfreq: Add devfreq based clock scaling support
mmc: core: Add sysfs entries for dynamic control of clock scaling
mmc: core: add support for devfreq suspend/resume
mmc: sdhci-msm: Kconfig: select devfreq ondemand for sdhci-msm
mmc: sdhci-msm: Enable clock scaling property
mmc: core: Add a debugfs entry to set max clock rate
.../devicetree/bindings/mmc/sdhci-msm.txt | 10 +
Documentation/mmc/mmc-dev-attrs.txt | 38 ++
drivers/devfreq/governor_simpleondemand.c | 25 +-
drivers/mmc/core/core.c | 672 +++++++++++++++++++++
drivers/mmc/core/core.h | 9 +
drivers/mmc/core/debugfs.c | 90 +++
drivers/mmc/core/host.c | 163 ++++-
drivers/mmc/core/mmc.c | 227 ++++++-
drivers/mmc/core/sd.c | 85 ++-
drivers/mmc/host/Kconfig | 2 +
drivers/mmc/host/sdhci-msm.c | 38 ++
drivers/mmc/host/sdhci-pltfm.c | 11 +
drivers/mmc/host/sdhci.c | 27 +
drivers/mmc/host/sdhci.h | 8 +
include/linux/devfreq.h | 4 +
include/linux/mmc/card.h | 5 +
include/linux/mmc/host.h | 70 +++
17 files changed, 1475 insertions(+), 9 deletions(-)
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
From: Sahitya Tummala <[email protected]>
Add new flag "simple_scaling" to on demand governor so that
the clocks can be scaled up only when the load is more than
up threshold and can be scaled down only when the load is less
than down differential data as provided within
struct devfreq_simple_ondemand_data.
Signed-off-by: Sahitya Tummala <[email protected]>
Signed-off-by: Venkat Gopalakrishnan <[email protected]>
Signed-off-by: Sayali Lokhande <[email protected]>
---
drivers/devfreq/governor_simpleondemand.c | 25 +++++++++++++++++++------
include/linux/devfreq.h | 4 ++++
2 files changed, 23 insertions(+), 6 deletions(-)
diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c
index 28e0f2d..532801b 100644
--- a/drivers/devfreq/governor_simpleondemand.c
+++ b/drivers/devfreq/governor_simpleondemand.c
@@ -28,6 +28,7 @@ static int devfreq_simple_ondemand_func(struct devfreq *df,
unsigned int dfso_downdifferential = DFSO_DOWNDIFFERENCTIAL;
struct devfreq_simple_ondemand_data *data = df->data;
unsigned long max = (df->max_freq) ? df->max_freq : UINT_MAX;
+ unsigned long min = (df->min_freq) ? df->min_freq : 0;
err = devfreq_update_stats(df);
if (err)
@@ -45,18 +46,30 @@ static int devfreq_simple_ondemand_func(struct devfreq *df,
dfso_upthreshold < dfso_downdifferential)
return -EINVAL;
- /* Assume MAX if it is going to be divided by zero */
- if (stat->total_time == 0) {
- *freq = max;
- return 0;
- }
-
/* Prevent overflow */
if (stat->busy_time >= (1 << 24) || stat->total_time >= (1 << 24)) {
stat->busy_time >>= 7;
stat->total_time >>= 7;
}
+ if (data && data->simple_scaling) {
+ if (stat->busy_time * 100 >
+ stat->total_time * dfso_upthreshold)
+ *freq = max;
+ else if (stat->busy_time * 100 <
+ stat->total_time * dfso_downdifferential)
+ *freq = min;
+ else
+ *freq = df->previous_freq;
+ return 0;
+ }
+
+ /* Assume MAX if it is going to be divided by zero */
+ if (stat->total_time == 0) {
+ *freq = max;
+ return 0;
+ }
+
/* Set MAX if it's busy enough */
if (stat->busy_time * 100 >
stat->total_time * dfso_upthreshold) {
diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h
index 3aae5b3..2957fd8 100644
--- a/include/linux/devfreq.h
+++ b/include/linux/devfreq.h
@@ -236,6 +236,9 @@ extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
* the governor may consider slowing the frequency down.
* Specify 0 to use the default. Valid value = 0 to 100.
* downdifferential < upthreshold must hold.
+ * @simple_scaling: Setting this flag will scale the clocks up only if the
+ * load is above @upthreshold and will scale the clocks
+ * down only if the load is below @downdifferential.
*
* If the fed devfreq_simple_ondemand_data pointer is NULL to the governor,
* the governor uses the default values.
@@ -243,6 +246,7 @@ extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev,
struct devfreq_simple_ondemand_data {
unsigned int upthreshold;
unsigned int downdifferential;
+ unsigned int simple_scaling;
};
#endif
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
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 <[email protected]>
Signed-off-by: Sayali Lokhande <[email protected]>
---
.../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;
+
+ /* 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);
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
Add sysfs attributes to allow dynamic control of clock scaling
parameters. These attributes are used to enable/disable clock
scaling at runtime and change the up_threshold, down_threshold,
and polling_interval values. Complete documentation for these
sysfs entries are provided at "Documentation/mmc/mmc-dev-attrs.txt".
Signed-off-by: Sujit Reddy Thumma <[email protected]>
Signed-off-by: Subhash Jadavani <[email protected]>
Signed-off-by: Sayali Lokhande <[email protected]>
---
Documentation/mmc/mmc-dev-attrs.txt | 38 +++++++++
drivers/mmc/core/host.c | 151 +++++++++++++++++++++++++++++++++++-
2 files changed, 188 insertions(+), 1 deletion(-)
diff --git a/Documentation/mmc/mmc-dev-attrs.txt b/Documentation/mmc/mmc-dev-attrs.txt
index 4ad0bb1..b02b2b4 100644
--- a/Documentation/mmc/mmc-dev-attrs.txt
+++ b/Documentation/mmc/mmc-dev-attrs.txt
@@ -75,3 +75,41 @@ Note on raw_rpmb_size_mult:
"raw_rpmb_size_mult" is a multiple of 128kB block.
RPMB size in byte is calculated by using the following equation:
RPMB partition size = 128kB x raw_rpmb_size_mult
+
+SD/MMC Clock Scaling Attributes
+====================================
+
+Read and write accesses are provided to following attributes.
+
+ polling_interval Measured in milliseconds, this attribute
+ defines how often we need to check the card
+ usage and make decisions on frequency scaling.
+
+ up_threshold This attribute defines what should be the
+ average card usage between the polling
+ interval for the mmc core to make a decision
+ on whether it should increase the frequency.
+ For example when it is set to '35' it means
+ that between the checking intervals the card
+ needs to be on average more than 35% in use to
+ scale up the frequency. The value should be
+ between 0 - 100 so that it can be compared
+ against load percentage.
+
+ down_threshold Similar to up_threshold, but on lowering the
+ frequency. For example, when it is set to '2'
+ it means that between the checking intervals
+ the card needs to be on average less than 2%
+ in use to scale down the clocks to minimum
+ frequency. The value should be between 0 - 100
+ so that it can be compared against load
+ percentage.
+
+ enable Enable clock scaling for hosts (and cards)
+ that support ultrahigh speed modes
+ (SDR104, DDR50, HS200).
+
+echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/polling_interval
+echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/up_threshold
+echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/down_threshold
+echo <desired value> > /sys/class/mmc_host/mmcX/clk_scaling/enable
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 1e46aa4..0504610 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -419,6 +419,151 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
EXPORT_SYMBOL(mmc_alloc_host);
+static ssize_t show_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", mmc_can_scale_clk(host));
+}
+
+static ssize_t store_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || !host->card || kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ mmc_get_card(host->card, NULL);
+
+ if (!value) {
+ /* mask host capability */
+ host->caps2 &= ~MMC_CAP2_CLK_SCALE;
+ host->clk_scaling.state = MMC_LOAD_HIGH;
+ /* Set to max. frequency when disabling */
+ mmc_clk_update_freq(host, host->card->clk_scaling_highest,
+ host->clk_scaling.state);
+ } else if (value) {
+ /* Unmask host capability*/
+ host->caps2 |= MMC_CAP2_CLK_SCALE;
+ }
+
+ mmc_put_card(host->card, NULL);
+
+ return count;
+}
+
+static ssize_t show_up_threshold(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", host->clk_scaling.upthreshold);
+}
+
+#define MAX_PERCENTAGE 100
+static ssize_t store_up_threshold(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE))
+ return -EINVAL;
+
+ host->clk_scaling.upthreshold = value;
+
+ pr_debug("%s: clkscale_up_thresh set to %lu\n",
+ mmc_hostname(host), value);
+ return count;
+}
+
+static ssize_t show_down_threshold(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%d\n",
+ host->clk_scaling.downthreshold);
+}
+
+static ssize_t store_down_threshold(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || kstrtoul(buf, 0, &value) || (value > MAX_PERCENTAGE))
+ return -EINVAL;
+
+ host->clk_scaling.downthreshold = value;
+
+ pr_debug("%s: clkscale_down_thresh set to %lu\n",
+ mmc_hostname(host), value);
+ return count;
+}
+
+static ssize_t show_polling(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+
+ if (!host)
+ return -EINVAL;
+
+ return snprintf(buf, PAGE_SIZE, "%lu milliseconds\n",
+ host->clk_scaling.polling_delay_ms);
+}
+
+static ssize_t store_polling(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mmc_host *host = cls_dev_to_mmc_host(dev);
+ unsigned long value;
+
+ if (!host || kstrtoul(buf, 0, &value))
+ return -EINVAL;
+
+ host->clk_scaling.polling_delay_ms = value;
+
+ pr_debug("%s: clkscale_polling_delay_ms set to %lu\n",
+ mmc_hostname(host), value);
+ return count;
+}
+
+DEVICE_ATTR(enable, 0644,
+ show_enable, store_enable);
+DEVICE_ATTR(polling_interval, 0644,
+ show_polling, store_polling);
+DEVICE_ATTR(up_threshold, 0644,
+ show_up_threshold, store_up_threshold);
+DEVICE_ATTR(down_threshold, 0644,
+ show_down_threshold, store_down_threshold);
+
+static struct attribute *clk_scaling_attrs[] = {
+ &dev_attr_enable.attr,
+ &dev_attr_up_threshold.attr,
+ &dev_attr_down_threshold.attr,
+ &dev_attr_polling_interval.attr,
+ NULL,
+};
+
+static struct attribute_group clk_scaling_attr_grp = {
+ .name = "clk_scaling",
+ .attrs = clk_scaling_attrs,
+};
+
/**
* mmc_add_host - initialise host hardware
* @host: mmc host
@@ -447,6 +592,10 @@ int mmc_add_host(struct mmc_host *host)
#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
+ err = sysfs_create_group(&host->class_dev.kobj, &clk_scaling_attr_grp);
+ if (err)
+ pr_err("%s: failed to create clk scale sysfs group with err %d\n",
+ __func__, err);
mmc_start_host(host);
mmc_register_pm_notifier(host);
@@ -472,7 +621,7 @@ void mmc_remove_host(struct mmc_host *host)
#ifdef CONFIG_DEBUG_FS
mmc_remove_host_debugfs(host);
#endif
-
+ sysfs_remove_group(&host->class_dev.kobj, &clk_scaling_attr_grp);
device_del(&host->class_dev);
led_trigger_unregister_simple(host->led);
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
This change adds support for devfreq suspend/resume
to be called on each system suspend/resume, runtime
suspend/resume, power restore.
Signed-off-by: Talel Shenhar <[email protected]>
Signed-off-by: Subhash Jadavani <[email protected]>
Signed-off-by: Sayali Lokhande <[email protected]>
---
drivers/mmc/core/core.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/mmc/core/core.h | 2 +
drivers/mmc/core/host.c | 8 +++-
drivers/mmc/core/mmc.c | 27 ++++++++++++
drivers/mmc/core/sd.c | 13 ++++++
5 files changed, 160 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 0eaee42..49103cf 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -618,6 +618,111 @@ int mmc_init_clk_scaling(struct mmc_host *host)
EXPORT_SYMBOL(mmc_init_clk_scaling);
/**
+ * mmc_suspend_clk_scaling() - suspend clock scaling
+ * @host: pointer to mmc host structure
+ *
+ * This API will suspend devfreq feature for the specific host.
+ * The statistics collected by mmc will be cleared.
+ * This function is intended to be called by the pm callbacks
+ * (e.g. runtime_suspend, suspend) of the mmc device
+ */
+int mmc_suspend_clk_scaling(struct mmc_host *host)
+{
+ int err;
+
+ if (!host) {
+ WARN(1, "bad host parameter\n");
+ return -EINVAL;
+ }
+
+ if (!mmc_can_scale_clk(host) || !host->clk_scaling.enable)
+ return 0;
+
+ if (!host->clk_scaling.devfreq) {
+ pr_err("%s: %s: no devfreq is assosiated with this device\n",
+ mmc_hostname(host), __func__);
+ return -EPERM;
+ }
+
+ atomic_inc(&host->clk_scaling.devfreq_abort);
+ wake_up(&host->wq);
+ err = devfreq_suspend_device(host->clk_scaling.devfreq);
+ if (err) {
+ pr_err("%s: %s: failed to suspend devfreq\n",
+ mmc_hostname(host), __func__);
+ return err;
+ }
+ host->clk_scaling.enable = false;
+
+ host->clk_scaling.total_busy_time_us = 0;
+
+ pr_debug("%s: devfreq was removed\n", mmc_hostname(host));
+
+ return 0;
+}
+EXPORT_SYMBOL(mmc_suspend_clk_scaling);
+
+/**
+ * mmc_resume_clk_scaling() - resume clock scaling
+ * @host: pointer to mmc host structure
+ *
+ * This API will resume devfreq feature for the specific host.
+ * This API is intended to be called by the pm callbacks
+ * (e.g. runtime_suspend, suspend) of the mmc device
+ */
+int mmc_resume_clk_scaling(struct mmc_host *host)
+{
+ int err = 0;
+ u32 max_clk_idx = 0;
+ u32 devfreq_max_clk = 0;
+ u32 devfreq_min_clk = 0;
+
+ if (!host) {
+ WARN(1, "bad host parameter\n");
+ return -EINVAL;
+ }
+
+ if (!mmc_can_scale_clk(host))
+ return 0;
+
+ /*
+ * If clock scaling is already exited when resume is called, like
+ * during mmc shutdown, it is not an error and should not fail the
+ * API calling this.
+ */
+ if (!host->clk_scaling.devfreq) {
+ pr_warn("%s: %s: no devfreq is assosiated with this device\n",
+ mmc_hostname(host), __func__);
+ return 0;
+ }
+
+ atomic_set(&host->clk_scaling.devfreq_abort, 0);
+
+ max_clk_idx = host->clk_scaling.freq_table_sz - 1;
+ devfreq_max_clk = host->clk_scaling.freq_table[max_clk_idx];
+ devfreq_min_clk = host->clk_scaling.freq_table[0];
+
+ host->clk_scaling.curr_freq = devfreq_max_clk;
+ if (host->ios.clock < host->clk_scaling.freq_table[max_clk_idx])
+ host->clk_scaling.curr_freq = devfreq_min_clk;
+
+ host->clk_scaling.clk_scaling_in_progress = false;
+ host->clk_scaling.need_freq_change = false;
+
+ err = devfreq_resume_device(host->clk_scaling.devfreq);
+ if (err) {
+ pr_err("%s: %s: failed to resume devfreq (%d)\n",
+ mmc_hostname(host), __func__, err);
+ } else {
+ host->clk_scaling.enable = true;
+ pr_debug("%s: devfreq resumed\n", mmc_hostname(host));
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(mmc_resume_clk_scaling);
+
+/**
* mmc_exit_devfreq_clk_scaling() - Disable clock scaling
* @host: pointer to mmc host structure
*
@@ -642,6 +747,13 @@ int mmc_exit_clk_scaling(struct mmc_host *host)
return -EPERM;
}
+ err = mmc_suspend_clk_scaling(host);
+ if (err) {
+ pr_err("%s: %s: fail to suspend clock scaling (%d)\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
err = devfreq_remove_device(host->clk_scaling.devfreq);
if (err) {
pr_err("%s: remove devfreq failed (%d)\n",
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index fc0a9b7..249c20d 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -97,6 +97,8 @@ static inline void mmc_delay(unsigned int ms)
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);
+extern int mmc_suspend_clk_scaling(struct mmc_host *host);
+extern int mmc_resume_clk_scaling(struct mmc_host *host);
int mmc_execute_tuning(struct mmc_card *card);
int mmc_hs200_to_hs400(struct mmc_card *card);
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 0504610..c3a71a5 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -442,15 +442,19 @@ static ssize_t store_enable(struct device *dev,
mmc_get_card(host->card, NULL);
if (!value) {
- /* mask host capability */
+ /* Suspend the clock scaling and mask host capability */
+ if (host->clk_scaling.enable)
+ mmc_suspend_clk_scaling(host);
host->caps2 &= ~MMC_CAP2_CLK_SCALE;
host->clk_scaling.state = MMC_LOAD_HIGH;
/* Set to max. frequency when disabling */
mmc_clk_update_freq(host, host->card->clk_scaling_highest,
host->clk_scaling.state);
} else if (value) {
- /* Unmask host capability*/
+ /* Unmask host capability and resume scaling */
host->caps2 |= MMC_CAP2_CLK_SCALE;
+ if (!host->clk_scaling.enable)
+ mmc_resume_clk_scaling(host);
}
mmc_put_card(host->card, NULL);
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index c8aedf3..1d286af 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -2161,6 +2161,13 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
unsigned int notify_type = is_suspend ? EXT_CSD_POWER_OFF_SHORT :
EXT_CSD_POWER_OFF_LONG;
+ err = mmc_suspend_clk_scaling(host);
+ if (err) {
+ pr_err("%s: %s: fail to suspend clock scaling (%d)\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
mmc_claim_host(host);
if (mmc_card_suspended(host->card))
@@ -2190,6 +2197,8 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
}
out:
mmc_release_host(host);
+ if (err)
+ mmc_resume_clk_scaling(host);
return err;
}
@@ -2228,6 +2237,10 @@ static int _mmc_resume(struct mmc_host *host)
out:
mmc_release_host(host);
+ err = mmc_resume_clk_scaling(host);
+ if (err)
+ pr_err("%s: %s: fail to resume clock scaling (%d)\n",
+ mmc_hostname(host), __func__, err);
return err;
}
@@ -2335,6 +2348,15 @@ static int _mmc_hw_reset(struct mmc_host *host)
mmc_pwrseq_reset(host);
}
+ /* Suspend clk scaling to avoid switching frequencies intermittently */
+
+ ret = mmc_suspend_clk_scaling(host);
+ if (ret) {
+ pr_err("%s: %s: fail to suspend clock scaling (%d)\n",
+ mmc_hostname(host), __func__, ret);
+ return ret;
+ }
+
ret = mmc_init_card(host, card->ocr, card);
if (ret) {
pr_err("%s: %s: mmc_init_card failed (%d)\n",
@@ -2342,6 +2364,11 @@ static int _mmc_hw_reset(struct mmc_host *host)
return ret;
}
+ ret = mmc_resume_clk_scaling(host);
+ if (ret)
+ pr_err("%s: %s: fail to resume clock scaling (%d)\n",
+ mmc_hostname(host), __func__, ret);
+
return ret;
}
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 40144c1..5ae2916 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -1131,6 +1131,13 @@ static int _mmc_sd_suspend(struct mmc_host *host)
{
int err = 0;
+ err = mmc_suspend_clk_scaling(host);
+ if (err) {
+ pr_err("%s: %s: fail to suspend clock scaling (%d)\n",
+ mmc_hostname(host), __func__, err);
+ return err;
+ }
+
mmc_claim_host(host);
if (mmc_card_suspended(host->card))
@@ -1182,6 +1189,12 @@ static int _mmc_sd_resume(struct mmc_host *host)
err = mmc_sd_init_card(host, host->card->ocr, host->card);
mmc_card_clr_suspended(host->card);
+ err = mmc_resume_clk_scaling(host);
+ if (err) {
+ pr_err("%s: %s: fail to resume clock scaling (%d)\n",
+ mmc_hostname(host), __func__, err);
+ goto out;
+ }
out:
mmc_release_host(host);
return err;
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
SDHCI-MSM platform is using devfreq ondemand governor.
Select devfreq governor for SDHCI-MSM platform.
Signed-off-by: Ritesh Harjani <[email protected]>
Signed-off-by: Sayali Lokhande <[email protected]>
---
drivers/mmc/host/Kconfig | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index 0581c19..2ba2c62 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -442,6 +442,8 @@ config MMC_SDHCI_MSM
tristate "Qualcomm SDHCI Controller Support"
depends on ARCH_QCOM || (ARM && COMPILE_TEST)
depends on MMC_SDHCI_PLTFM
+ select PM_DEVFREQ
+ select DEVFREQ_GOV_SIMPLE_ONDEMAND
select MMC_SDHCI_IO_ACCESSORS
help
This selects the Secure Digital Host Controller Interface (SDHCI)
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
Limiting the max frequency supported by host controller
to a certain value can be useful for testing power consumption
at various frequencies that are supported by the host.
Note: If the card supports less than desired value then the
frequency of operation would be limited to that frequency.
Usage:
mount -t debugfs none /sys/kernel/debug
echo <desired_frequency> > /sys/kernel/debug/mmcX/max_clock
cat /sys/kernel/debug/mmcX/max_clock
Signed-off-by: Sujit Reddy Thumma <[email protected]>
Signed-off-by: Sayali Lokhande <[email protected]>
---
drivers/mmc/core/debugfs.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c
index 630ca8e..26499f7 100644
--- a/drivers/mmc/core/debugfs.c
+++ b/drivers/mmc/core/debugfs.c
@@ -262,6 +262,46 @@ static int mmc_scale_set(void *data, u64 val)
DEFINE_SIMPLE_ATTRIBUTE(mmc_scale_fops, mmc_scale_get, mmc_scale_set,
"%llu\n");
+static int mmc_max_clock_get(void *data, u64 *val)
+{
+ struct mmc_host *host = data;
+
+ if (!host)
+ return -EINVAL;
+
+ *val = host->f_max;
+
+ return 0;
+}
+
+static int mmc_max_clock_set(void *data, u64 val)
+{
+ struct mmc_host *host = data;
+ int err = -EINVAL;
+ unsigned long freq = val;
+ unsigned int old_freq;
+
+ if (!host || (val < host->f_min))
+ goto out;
+
+ mmc_claim_host(host);
+ if (host->bus_ops && host->bus_ops->change_bus_speed) {
+ old_freq = host->f_max;
+ host->f_max = freq;
+
+ err = host->bus_ops->change_bus_speed(host, &freq);
+
+ if (err)
+ host->f_max = old_freq;
+ }
+ mmc_release_host(host);
+out:
+ return err;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(mmc_max_clock_fops, mmc_max_clock_get,
+ mmc_max_clock_set, "%llu\n");
+
void mmc_add_host_debugfs(struct mmc_host *host)
{
struct dentry *root;
@@ -290,6 +330,10 @@ void mmc_add_host_debugfs(struct mmc_host *host)
&mmc_clock_fops))
goto err_node;
+ if (!debugfs_create_file("max_clock", 0600, root, host,
+ &mmc_max_clock_fops))
+ goto err_node;
+
if (!debugfs_create_file("scale", 0600, root, host,
&mmc_scale_fops))
goto err_node;
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
This change enables clock scaling capability for sdhci-msm
platform driver.
Signed-off-by: Sayali Lokhande <[email protected]>
---
drivers/mmc/host/sdhci-msm.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c
index e9fe8c6..8846a76 100644
--- a/drivers/mmc/host/sdhci-msm.c
+++ b/drivers/mmc/host/sdhci-msm.c
@@ -1803,6 +1803,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
msm_offset = msm_host->offset;
sdhci_get_of_property(pdev);
+ msm_host->mmc->caps2 |= MMC_CAP2_CLK_SCALE;
msm_host->saved_tuning_phase = INVALID_TUNING_PHASE;
--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project
On 13 July 2018 at 11:52, Sayali Lokhande <[email protected]> wrote:
> This change adds the use of devfreq based clock scaling to MMC.
> Both eMMC and SD card can 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,
> 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 as per load.
>
> Sahitya Tummala (1):
> devfreq: Add new flag to do simple clock scaling
>
> Sayali Lokhande (6):
> mmc: core: devfreq: Add devfreq based clock scaling support
> mmc: core: Add sysfs entries for dynamic control of clock scaling
> mmc: core: add support for devfreq suspend/resume
> mmc: sdhci-msm: Kconfig: select devfreq ondemand for sdhci-msm
> mmc: sdhci-msm: Enable clock scaling property
> mmc: core: Add a debugfs entry to set max clock rate
>
> .../devicetree/bindings/mmc/sdhci-msm.txt | 10 +
> Documentation/mmc/mmc-dev-attrs.txt | 38 ++
> drivers/devfreq/governor_simpleondemand.c | 25 +-
> drivers/mmc/core/core.c | 672 +++++++++++++++++++++
> drivers/mmc/core/core.h | 9 +
> drivers/mmc/core/debugfs.c | 90 +++
> drivers/mmc/core/host.c | 163 ++++-
> drivers/mmc/core/mmc.c | 227 ++++++-
> drivers/mmc/core/sd.c | 85 ++-
> drivers/mmc/host/Kconfig | 2 +
> drivers/mmc/host/sdhci-msm.c | 38 ++
> drivers/mmc/host/sdhci-pltfm.c | 11 +
> drivers/mmc/host/sdhci.c | 27 +
> drivers/mmc/host/sdhci.h | 8 +
> include/linux/devfreq.h | 4 +
> include/linux/mmc/card.h | 5 +
> include/linux/mmc/host.h | 70 +++
> 17 files changed, 1475 insertions(+), 9 deletions(-)
>
> --
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
>
Thanks for you patchse! Just wanted to let you know that I am current
in holiday mode, so I needs some more time to review this properly.
Apologize for the inconvenience.
Kind regards
Uffe
On Fri, Jul 13, 2018 at 03:22:58PM +0530, 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 <[email protected]>
> Signed-off-by: Sayali Lokhande <[email protected]>
> ---
> .../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.
This should be a common binding in which case will just a list of
frequencies be sufficient.
Rob
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 <[email protected]>
> Signed-off-by: Sayali Lokhande <[email protected]>
> ---
> .../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 ?
> + /* 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
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 <[email protected]>
>> Signed-off-by: Sayali Lokhande <[email protected]>
>> ---
>> .../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
Hi Rob,
On 7/20/2018 8:54 PM, Rob Herring wrote:
> On Fri, Jul 13, 2018 at 03:22:58PM +0530, 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 <[email protected]>
>> Signed-off-by: Sayali Lokhande <[email protected]>
>> ---
>> .../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.
> This should be a common binding in which case will just a list of
> frequencies be sufficient.
[Sayali] : Can you please elaborate more on what do you mean by common
binding here ? Like if you are referring to any already available
property (by devfreq ?) to pass min-max frequencies ?
Any Documentation file to refer ?
>
> Rob