diff options
author | Pavan Kunapuli <pkunapuli@nvidia.com> | 2013-04-04 17:45:52 +0530 |
---|---|---|
committer | Mrutyunjay Sawant <msawant@nvidia.com> | 2013-04-10 03:49:44 -0700 |
commit | 18a8df49d7fc837cc805ef2b0601d3a301915060 (patch) | |
tree | ae72a68a611384c73dfecd22424ca107bb4ca810 /drivers | |
parent | 50ef135c30f3f9a0b7758068fbf31d8db4f92893 (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/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mmc/core/Makefile | 1 | ||||
-rw-r--r-- | drivers/mmc/core/core.c | 352 | ||||
-rw-r--r-- | drivers/mmc/core/core.h | 5 |
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) { |