mmc: core: Dynamic freq scaling for SD,MMC,SDIO
Pavan Kunapuli [Thu, 4 Apr 2013 12:15:52 +0000 (17:15 +0530)]
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>

drivers/mmc/core/Kconfig
drivers/mmc/core/Makefile
drivers/mmc/core/core.c
drivers/mmc/core/core.h
include/linux/mmc/host.h

index 85c2e1a..61a06fa 100644 (file)
@@ -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.
index dca4428..b78bf97 100644 (file)
@@ -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 \
index 24ee1b5..0ea2900 100644 (file)
 #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;
index 3bdafbc..8c5b482 100644 (file)
@@ -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) {
index 3591493..fc67198 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/device.h>
 #include <linux/fault-inject.h>
 #include <linux/wakelock.h>
+#include <linux/devfreq.h>
 
 #include <linux/mmc/core.h>
 #include <linux/mmc/pm.h>
@@ -144,6 +145,15 @@ struct mmc_host_ops {
        void    (*enable_preset_value)(struct mmc_host *host, bool enable);
        int     (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv);
        void    (*hw_reset)(struct mmc_host *host);
+
+       /*
+        * Device frequency scaling optional callbacks to allow for custom
+        * algorithms to determine the target frequency.
+        */
+       int     (*dfs_governor_init)(struct mmc_host *host);
+       void    (*dfs_governor_exit)(struct mmc_host *host);
+       int     (*dfs_governor_get_target)(struct mmc_host *host,
+               unsigned long *freq);
 };
 
 struct mmc_card;
@@ -164,6 +174,19 @@ struct mmc_hotplug {
        void *handler_priv;
 };
 
+struct mmc_dev_stats {
+       /* Device busy and total times in the current interval */
+       unsigned long           busy_time;
+       unsigned long           total_time;
+
+       /* Active and polling timestamps used for time calculation */
+       ktime_t                 t_busy;
+       ktime_t                 t_interval;
+
+       unsigned int            polling_interval;
+       bool                    update_dev_freq;
+};
+
 struct mmc_host {
        struct device           *parent;
        struct device           class_dev;
@@ -323,6 +346,13 @@ struct mmc_host {
 
        mmc_pm_flag_t           pm_flags;       /* requested pm features */
 
+       /* MMC DFS and devfreq information */
+       struct devfreq                  *df;
+       struct devfreq_dev_profile      *df_profile;
+       struct devfreq_dev_status       *devfreq_stats;
+       struct mmc_dev_stats            *dev_stats;
+       struct delayed_work             dfs_work;
+
 #ifdef CONFIG_LEDS_TRIGGERS
        struct led_trigger      *led;           /* activity led */
 #endif