summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorPavan Kunapuli <pkunapuli@nvidia.com>2013-04-04 17:45:52 +0530
committerMrutyunjay Sawant <msawant@nvidia.com>2013-04-10 03:49:44 -0700
commit18a8df49d7fc837cc805ef2b0601d3a301915060 (patch)
treeae72a68a611384c73dfecd22424ca107bb4ca810 /drivers
parent50ef135c30f3f9a0b7758068fbf31d8db4f92893 (diff)
mmc: core: Dynamic freq scaling for SD,MMC,SDIO
Added support for dynamic frequency scaling of SD,MMC,SDIO devices. The device is registered with devfreq framework after enumeration if CONFIG_MMC_FREQ_SCALING is enabled. MMC frequency governor is added to dynamically scale the frequency. The governor doesn't use central polling but schedules a work to poll the status of the device periodically. Optional callbacks are provided to have custom algorithms for determining the frequency. Bug 1238045 Bug 1044607 Change-Id: Ic7f5669c784afa759ad52bf8373011838a76c01c Signed-off-by: Pavan Kunapuli <pkunapuli@nvidia.com> Reviewed-on: http://git-master/r/213012 GVS: Gerrit_Virtual_Submit Tested-by: Naveen Kumar Arepalli <naveenk@nvidia.com> Reviewed-by: Venu Byravarasu <vbyravarasu@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mmc/core/Kconfig10
-rw-r--r--drivers/mmc/core/Makefile1
-rw-r--r--drivers/mmc/core/core.c352
-rw-r--r--drivers/mmc/core/core.h5
4 files changed, 367 insertions, 1 deletions
diff --git a/drivers/mmc/core/Kconfig b/drivers/mmc/core/Kconfig
index 85c2e1acd156..61a06faa06c4 100644
--- a/drivers/mmc/core/Kconfig
+++ b/drivers/mmc/core/Kconfig
@@ -44,3 +44,13 @@ config MMC_PARANOID_SD_INIT
about re-trying SD init requests. This can be a useful
work-around for buggy controllers and hardware. Enable
if you are experiencing issues with SD detection.
+
+config MMC_FREQ_SCALING
+ bool "Enable dynamic frequency scaling for SD/MMC/SDIO devices"
+ depends on EXPERIMENTAL
+ default N
+ help
+ If you say Y here, the MMC layer will vary the SD/MMC/SDIO
+ device frequency dynamically. Enable this config only if
+ there is a custom implementation to determine the frequency
+ using the device stats.
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index dca4428380f1..b78bf97d02cb 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -1,6 +1,7 @@
#
# Makefile for the kernel mmc core.
#
+ccflags-y := -Idrivers/devfreq
obj-$(CONFIG_MMC) += mmc_core.o
mmc_core-y := core.o bus.o host.o \
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 24ee1b556b1f..0ea2900369fc 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -28,12 +28,16 @@
#include <linux/fault-inject.h>
#include <linux/random.h>
#include <linux/wakelock.h>
+#include <linux/devfreq.h>
+#include <linux/slab.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
+#include <governor.h>
+
#include "core.h"
#include "bus.h"
#include "host.h"
@@ -136,6 +140,19 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
{
struct mmc_command *cmd = mrq->cmd;
int err = cmd->error;
+ ktime_t t;
+ unsigned long time;
+ unsigned long flags;
+
+#ifdef CONFIG_MMC_FREQ_SCALING
+ if (host->dev_stats) {
+ t = ktime_get();
+ time = ktime_us_delta(t, host->dev_stats->t_busy);
+ spin_lock_irqsave(&host->lock, flags);
+ host->dev_stats->busy_time += time;
+ spin_unlock_irqrestore(&host->lock, flags);
+ }
+#endif
if (err && cmd->retries && mmc_host_is_spi(host)) {
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
@@ -243,6 +260,20 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
}
mmc_host_clk_hold(host);
led_trigger_event(host->led, LED_FULL);
+
+#ifdef CONFIG_MMC_FREQ_SCALING
+ if (host->df && host->dev_stats) {
+ if (host->dev_stats->update_dev_freq) {
+ mmc_set_clock(host, host->ios.clock);
+ mutex_lock(&host->df->lock);
+ host->df->previous_freq = host->actual_clock;
+ mutex_unlock(&host->df->lock);
+ host->dev_stats->update_dev_freq = false;
+ }
+ host->dev_stats->t_busy = ktime_get();
+ }
+#endif
+
host->ops->request(host, mrq);
}
@@ -2325,6 +2356,295 @@ int mmc_speed_class_control(struct mmc_host *host,
}
EXPORT_SYMBOL(mmc_speed_class_control);
+#ifdef CONFIG_MMC_FREQ_SCALING
+/*
+ * This function queries the device status for the current interval and
+ * calculates the desired frequency to be set.
+ *
+ * For now, this function queries the device status and lets the platform
+ * specific implementation(if any) determine the desired frequency.
+ * If there is no such implementation, previous frequency will be
+ * maintained.
+ */
+static int mmc_get_target_freq(struct devfreq *df, unsigned long *freq)
+{
+ struct mmc_host *host = container_of(df->dev.parent,
+ struct mmc_host, class_dev);
+ int err = 0;
+
+ /* Get the device status for the current interval */
+ err = df->profile->get_dev_status(df->dev.parent, host->devfreq_stats);
+ if (err)
+ dev_err(mmc_dev(host),
+ "Failed to get the device status %d\n", err);
+
+ /* Determine the target frequency */
+ if (host->ops->dfs_governor_get_target)
+ err = host->ops->dfs_governor_get_target(host, freq);
+ else
+ *freq = df->previous_freq;
+
+ return 0;
+}
+
+/*
+ * MMC freq governor calls this function at periodic intervals to query
+ * the device status and set frequency update request if required.
+ * The default interval is 100msec. It can be changed by the platform
+ * specific callback for governor initialization to suit the algorithm
+ * implementation.
+ */
+static void mmc_update_devfreq(struct work_struct *work)
+{
+ struct mmc_host *host = container_of(work, struct mmc_host,
+ dfs_work.work);
+ unsigned long freq;
+
+ mmc_get_target_freq(host->df, &freq);
+
+ /*
+ * If the new frequency is not matching the previous frequency, call
+ * update_freq to set the new frequency.
+ */
+ if (freq != host->df->previous_freq) {
+ mutex_lock(&host->df->lock);
+ update_devfreq(host->df);
+ mutex_unlock(&host->df->lock);
+ }
+
+ /* Schedule work to query the device status for the next interval */
+ schedule_delayed_work(&host->dfs_work,
+ msecs_to_jiffies(host->dev_stats->polling_interval));
+}
+
+static int mmc_freq_gov_init(struct devfreq *df)
+{
+ struct mmc_host *host = container_of(df->dev.parent,
+ struct mmc_host, class_dev);
+ int err = 0;
+
+ if (!host->devfreq_stats) {
+ host->devfreq_stats = kzalloc(
+ sizeof(struct devfreq_dev_status), GFP_KERNEL);
+ if (!host->devfreq_stats) {
+ dev_err(mmc_dev(host),
+ "Failed to initialize governor data\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* Set the default polling interval to 100 */
+ host->dev_stats->polling_interval = 100;
+
+ /*
+ * A platform specific hook for doing any necessary initialization
+ * for the mmc frequency governor.
+ */
+ if (host->ops->dfs_governor_init) {
+ err = host->ops->dfs_governor_init(host);
+ if (err) {
+ dev_err(mmc_dev(host),
+ "DFS governor init failed %d\n", err);
+ goto err_governor_init;
+ }
+ }
+
+ /*
+ * The delayed work is used to query the device status at
+ * periodic intervals.
+ */
+ INIT_DELAYED_WORK(&host->dfs_work, mmc_update_devfreq);
+
+ schedule_delayed_work(&host->dfs_work,
+ msecs_to_jiffies(host->dev_stats->polling_interval));
+
+err_governor_init:
+ kfree(host->devfreq_stats);
+ return err;
+}
+
+static void mmc_freq_gov_exit(struct devfreq *df)
+{
+ struct mmc_host *host = container_of(df->dev.parent,
+ struct mmc_host, class_dev);
+
+ /* Cancel any pending work scheduled for polling the device status */
+ cancel_delayed_work_sync(&host->dfs_work);
+
+ if (host->ops->dfs_governor_exit)
+ host->ops->dfs_governor_exit(host);
+
+ kfree(host->devfreq_stats);
+ host->devfreq_stats = NULL;
+}
+
+const struct devfreq_governor mmc_freq_governor = {
+ .name = "mmc_dfs_governor",
+ .get_target_freq = mmc_get_target_freq,
+ .init = mmc_freq_gov_init,
+ .exit = mmc_freq_gov_exit,
+ .no_central_polling = false,
+};
+
+/*
+ * This function will be called from update_devfreq and will set the
+ * desired frequency. To avoid changing the device frequency
+ * during an ongoing data transfer, this function will set the flag
+ * to indicate a need for change in devfreq. The frequency will be
+ * changed before a new command is issued.
+ */
+static int mmc_devfreq_target(struct device *dev, unsigned long *freq,
+ u32 flags)
+{
+ struct mmc_host *host = container_of(dev,
+ struct mmc_host, class_dev);
+ struct devfreq *df = host->df;
+
+ host->dev_stats->update_dev_freq = false;
+
+ /* Check if the desired frequency is same as the current frequency */
+ if (*freq == host->actual_clock)
+ return 0;
+
+ /*
+ * Check if the requested frequency falls within the supported min and
+ * max frequencies.
+ */
+ if (*freq > host->f_max)
+ *freq = host->f_max;
+ else if (*freq < host->f_min)
+ *freq = host->f_min;
+
+ /*
+ * Update the new frequency in mmc ios and set the update_dev_freq
+ * flag to indicate a freq change request.
+ */
+ host->ios.clock = *freq;
+ host->dev_stats->update_dev_freq = true;
+
+ pr_debug("%s: Changing freq from %ld to %ld\n", mmc_hostname(host),
+ df->previous_freq, *freq);
+
+ return 0;
+}
+
+static int mmc_devfreq_get_status(struct device *dev,
+ struct devfreq_dev_status *stat)
+{
+ struct mmc_host *host = container_of(dev, struct mmc_host, class_dev);
+ struct mmc_dev_stats *dev_stats = host->dev_stats;
+ ktime_t t;
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->lock, flags);
+ stat->busy_time = dev_stats->busy_time;
+ dev_stats->busy_time = 0;
+ spin_unlock_irqrestore(&host->lock, flags);
+
+ if (dev_stats) {
+ t = ktime_get();
+ dev_stats->total_time += ktime_us_delta(t,
+ dev_stats->t_interval);
+ }
+ stat->total_time = dev_stats->total_time;
+ stat->current_frequency = host->actual_clock;
+
+ /* Clear out stale data */
+ dev_stats->total_time = 0;
+ dev_stats->t_interval = t;
+
+ return 0;
+}
+
+static struct devfreq_dev_profile mmc_df_profile = {
+ .polling_ms = 0,
+ .target = mmc_devfreq_target,
+ .get_dev_status = mmc_devfreq_get_status,
+};
+
+int mmc_devfreq_init(struct mmc_host *host)
+{
+ struct devfreq *df;
+ int err;
+
+ /* Return if already registered for device frequency */
+ if (host->df) {
+ schedule_delayed_work(&host->dfs_work,
+ msecs_to_jiffies(host->dev_stats->polling_interval));
+ return 0;
+ }
+
+ /* Set the device profile */
+ host->df_profile = kzalloc(sizeof(struct devfreq_dev_profile),
+ GFP_KERNEL);
+ if (!host->df_profile) {
+ dev_err(mmc_dev(host), "Failed to create devfreq structure\n");
+ return -ENOMEM;
+ }
+ host->df_profile = &mmc_df_profile;
+ host->df_profile->initial_freq = host->actual_clock;
+
+ /* Initialize the device stats */
+ host->dev_stats = kzalloc(sizeof(struct mmc_dev_stats),
+ GFP_KERNEL);
+ if (!host->dev_stats) {
+ dev_err(mmc_dev(host),
+ "Failed to initialize the device stats\n");
+ err = -ENOMEM;
+ goto err_dev_stats;
+ } else {
+ host->dev_stats->busy_time = 0;
+ host->dev_stats->t_interval = ktime_get();
+ }
+
+ df = devfreq_add_device(&host->class_dev, host->df_profile,
+ &mmc_freq_governor, NULL);
+ if (IS_ERR_OR_NULL(df)) {
+ dev_err(mmc_dev(host),
+ "Failed to register with devfreq %ld\n", PTR_ERR(df));
+ df = NULL;
+ err = -ENODEV;
+ goto err_devfreq_add;
+ }
+
+ /* Set the frequency constraints for the device */
+ df->min_freq = host->f_min;
+ if (mmc_card_mmc(host->card)) {
+ df->max_freq = max(host->card->ext_csd.hs_max_dtr,
+ host->card->csd.max_dtr);
+ } else if (mmc_card_sd(host->card) || mmc_card_sdio(host->card)) {
+ df->max_freq = max(host->card->sw_caps.uhs_max_dtr,
+ host->card->sw_caps.hs_max_dtr);
+ } else {
+ dev_err(mmc_dev(host), "unknown card type %d\n",
+ host->card->type);
+ df->max_freq = host->actual_clock;
+ }
+
+ host->df = df;
+ return 0;
+
+err_devfreq_add:
+ kfree(host->dev_stats);
+err_dev_stats:
+ kfree(host->df_profile);
+ return err;
+}
+
+int mmc_devfreq_deinit(struct mmc_host *host)
+{
+ int err = 0;
+
+ if (host->df)
+ err = devfreq_remove_device(host->df);
+
+ kfree(host->dev_stats);
+ kfree(host->df_profile);
+
+ return err;
+}
+#endif
+
int mmc_power_save_host(struct mmc_host *host)
{
int ret = 0;
@@ -2340,6 +2660,11 @@ int mmc_power_save_host(struct mmc_host *host)
return -EINVAL;
}
+#ifdef CONFIG_MMC_FREQ_SCALING
+ if (host->df)
+ cancel_delayed_work_sync(&host->dfs_work);
+#endif
+
if (host->bus_ops->power_save)
ret = host->bus_ops->power_save(host);
@@ -2369,6 +2694,12 @@ int mmc_power_restore_host(struct mmc_host *host)
mmc_power_up(host);
ret = host->bus_ops->power_restore(host);
+#ifdef CONFIG_MMC_FREQ_SCALING
+ if (host->df)
+ schedule_delayed_work(&host->dfs_work,
+ msecs_to_jiffies(host->dev_stats->polling_interval));
+#endif
+
mmc_bus_put(host);
return ret;
@@ -2494,10 +2825,21 @@ EXPORT_SYMBOL(mmc_cache_ctrl);
int mmc_suspend_host(struct mmc_host *host)
{
int err = 0;
+ ktime_t t;
if (mmc_bus_needs_resume(host))
return 0;
+#ifdef CONFIG_MMC_FREQ_SCALING
+ if (host->df) {
+ cancel_delayed_work_sync(&host->dfs_work);
+
+ t = ktime_get();
+ host->dev_stats->total_time = ktime_us_delta(t,
+ host->dev_stats->t_interval);
+ }
+#endif
+
if (mmc_card_mmc(host->card) && mmc_card_doing_bkops(host->card))
mmc_interrupt_hpi(host->card);
mmc_card_clr_need_bkops(host->card);
@@ -2586,6 +2928,16 @@ int mmc_resume_host(struct mmc_host *host)
}
}
host->pm_flags &= ~MMC_PM_KEEP_POWER;
+
+#ifdef CONFIG_MMC_FREQ_SCALING
+ if (host->df) {
+ host->dev_stats->t_interval = ktime_get();
+
+ schedule_delayed_work(&host->dfs_work,
+ msecs_to_jiffies(host->dev_stats->polling_interval));
+ }
+#endif
+
mmc_bus_put(host);
return err;
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index 3bdafbca354f..8c5b48276370 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -45,7 +45,10 @@ int mmc_set_signal_voltage(struct mmc_host *host, int signal_voltage,
void mmc_set_timing(struct mmc_host *host, unsigned int timing);
void mmc_set_driver_type(struct mmc_host *host, unsigned int drv_type);
void mmc_power_off(struct mmc_host *host);
-
+#ifdef CONFIG_MMC_FREQ_SCALING
+int mmc_devfreq_init(struct mmc_host *host);
+int mmc_devfreq_deinit(struct mmc_host *host);
+#endif
static inline void mmc_delay(unsigned int ms)
{
if (ms < 1000 / HZ) {