aboutsummaryrefslogtreecommitdiff
path: root/drivers/devfreq
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/devfreq')
-rw-r--r--drivers/devfreq/Kconfig25
-rw-r--r--drivers/devfreq/Makefile3
-rw-r--r--drivers/devfreq/devfreq.c1116
-rw-r--r--drivers/devfreq/exynos/Makefile3
-rw-r--r--drivers/devfreq/exynos/exynos4_bus.c (renamed from drivers/devfreq/exynos4_bus.c)382
-rw-r--r--drivers/devfreq/exynos/exynos4_bus.h110
-rw-r--r--drivers/devfreq/exynos/exynos5_bus.c432
-rw-r--r--drivers/devfreq/exynos/exynos_ppmu.c116
-rw-r--r--drivers/devfreq/exynos/exynos_ppmu.h86
-rw-r--r--drivers/devfreq/governor.h17
-rw-r--r--drivers/devfreq/governor_performance.c38
-rw-r--r--drivers/devfreq/governor_powersave.c38
-rw-r--r--drivers/devfreq/governor_simpleondemand.c55
-rw-r--r--drivers/devfreq/governor_userspace.c45
14 files changed, 1912 insertions, 554 deletions
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index f6b0a6e2ea5..49e74c1fc63 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -30,7 +30,7 @@ if PM_DEVFREQ
comment "DEVFREQ Governors"
config DEVFREQ_GOV_SIMPLE_ONDEMAND
- bool "Simple Ondemand"
+ tristate "Simple Ondemand"
help
Chooses frequency based on the recent load on the device. Works
similar as ONDEMAND governor of CPUFREQ does. A device with
@@ -39,7 +39,7 @@ config DEVFREQ_GOV_SIMPLE_ONDEMAND
values to the governor with data field at devfreq_add_device().
config DEVFREQ_GOV_PERFORMANCE
- bool "Performance"
+ tristate "Performance"
help
Sets the frequency at the maximum available frequency.
This governor always returns UINT_MAX as frequency so that
@@ -47,7 +47,7 @@ config DEVFREQ_GOV_PERFORMANCE
at any time.
config DEVFREQ_GOV_POWERSAVE
- bool "Powersave"
+ tristate "Powersave"
help
Sets the frequency at the minimum available frequency.
This governor always returns 0 as frequency so that
@@ -55,7 +55,7 @@ config DEVFREQ_GOV_POWERSAVE
at any time.
config DEVFREQ_GOV_USERSPACE
- bool "Userspace"
+ tristate "Userspace"
help
Sets the frequency at the user specified one.
This governor returns the user configured frequency if there
@@ -67,15 +67,26 @@ comment "DEVFREQ Drivers"
config ARM_EXYNOS4_BUS_DEVFREQ
bool "ARM Exynos4210/4212/4412 Memory Bus DEVFREQ Driver"
- depends on CPU_EXYNOS4210 || CPU_EXYNOS4212 || CPU_EXYNOS4412
+ depends on (CPU_EXYNOS4210 || SOC_EXYNOS4212 || SOC_EXYNOS4412) && !ARCH_MULTIPLATFORM
select ARCH_HAS_OPP
select DEVFREQ_GOV_SIMPLE_ONDEMAND
+ select PM_OPP
help
This adds the DEVFREQ driver for Exynos4210 memory bus (vdd_int)
and Exynos4212/4412 memory interface and bus (vdd_mif + vdd_int).
It reads PPMU counters of memory controllers and adjusts
the operating frequencies and voltages with OPP support.
- To operate with optimal voltages, ASV support is required
- (CONFIG_EXYNOS_ASV).
+ This does not yet operate with optimal voltages.
+
+config ARM_EXYNOS5_BUS_DEVFREQ
+ bool "ARM Exynos5250 Bus DEVFREQ Driver"
+ depends on SOC_EXYNOS5250
+ select ARCH_HAS_OPP
+ select DEVFREQ_GOV_SIMPLE_ONDEMAND
+ select PM_OPP
+ help
+ This adds the DEVFREQ driver for Exynos5250 bus interface (vdd_int).
+ It reads PPMU counters of memory controllers and adjusts the
+ operating frequencies and voltages with OPP support.
endif # PM_DEVFREQ
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index 8c464234f7e..16138c9e0d5 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -5,4 +5,5 @@ obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
# DEVFREQ Drivers
-obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos4_bus.o
+obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos/
+obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos/
diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c
index b146d76f04c..9f90369dd6b 100644
--- a/drivers/devfreq/devfreq.c
+++ b/drivers/devfreq/devfreq.c
@@ -18,7 +18,7 @@
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/stat.h>
-#include <linux/opp.h>
+#include <linux/pm_opp.h>
#include <linux/devfreq.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
@@ -27,21 +27,17 @@
#include <linux/hrtimer.h>
#include "governor.h"
-struct class *devfreq_class;
+static struct class *devfreq_class;
/*
- * devfreq_work periodically monitors every registered device.
- * The minimum polling interval is one jiffy. The polling interval is
- * determined by the minimum polling period among all polling devfreq
- * devices. The resolution of polling interval is one jiffy.
+ * devfreq core provides delayed work based load monitoring helper
+ * functions. Governors can use these or can implement their own
+ * monitoring mechanism.
*/
-static bool polling;
static struct workqueue_struct *devfreq_wq;
-static struct delayed_work devfreq_work;
-
-/* wait removing if this is to be removed */
-static struct devfreq *wait_remove_device;
+/* The list of all device-devfreq governors */
+static LIST_HEAD(devfreq_governor_list);
/* The list of all device-devfreq */
static LIST_HEAD(devfreq_list);
static DEFINE_MUTEX(devfreq_list_lock);
@@ -73,6 +69,88 @@ static struct devfreq *find_device_devfreq(struct device *dev)
}
/**
+ * devfreq_get_freq_level() - Lookup freq_table for the frequency
+ * @devfreq: the devfreq instance
+ * @freq: the target frequency
+ */
+static int devfreq_get_freq_level(struct devfreq *devfreq, unsigned long freq)
+{
+ int lev;
+
+ for (lev = 0; lev < devfreq->profile->max_state; lev++)
+ if (freq == devfreq->profile->freq_table[lev])
+ return lev;
+
+ return -EINVAL;
+}
+
+/**
+ * devfreq_update_status() - Update statistics of devfreq behavior
+ * @devfreq: the devfreq instance
+ * @freq: the update target frequency
+ */
+static int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
+{
+ int lev, prev_lev, ret = 0;
+ unsigned long cur_time;
+
+ cur_time = jiffies;
+
+ prev_lev = devfreq_get_freq_level(devfreq, devfreq->previous_freq);
+ if (prev_lev < 0) {
+ ret = prev_lev;
+ goto out;
+ }
+
+ devfreq->time_in_state[prev_lev] +=
+ cur_time - devfreq->last_stat_updated;
+
+ lev = devfreq_get_freq_level(devfreq, freq);
+ if (lev < 0) {
+ ret = lev;
+ goto out;
+ }
+
+ if (lev != prev_lev) {
+ devfreq->trans_table[(prev_lev *
+ devfreq->profile->max_state) + lev]++;
+ devfreq->total_trans++;
+ }
+
+out:
+ devfreq->last_stat_updated = cur_time;
+ return ret;
+}
+
+/**
+ * find_devfreq_governor() - find devfreq governor from name
+ * @name: name of the governor
+ *
+ * Search the list of devfreq governors and return the matched
+ * governor's pointer. devfreq_list_lock should be held by the caller.
+ */
+static struct devfreq_governor *find_devfreq_governor(const char *name)
+{
+ struct devfreq_governor *tmp_governor;
+
+ if (unlikely(IS_ERR_OR_NULL(name))) {
+ pr_err("DEVFREQ: %s: Invalid parameters\n", __func__);
+ return ERR_PTR(-EINVAL);
+ }
+ WARN(!mutex_is_locked(&devfreq_list_lock),
+ "devfreq_list_lock must be locked.");
+
+ list_for_each_entry(tmp_governor, &devfreq_governor_list, node) {
+ if (!strncmp(tmp_governor->name, name, DEVFREQ_NAME_LEN))
+ return tmp_governor;
+ }
+
+ return ERR_PTR(-ENODEV);
+}
+
+/* Load monitoring helper functions for governors use */
+
+/**
* update_devfreq() - Reevaluate the device and configure frequency.
* @devfreq: the devfreq instance.
*
@@ -90,6 +168,9 @@ int update_devfreq(struct devfreq *devfreq)
return -EINVAL;
}
+ if (!devfreq->governor)
+ return -EINVAL;
+
/* Reevaluate the proper frequency */
err = devfreq->governor->get_target_freq(devfreq, &freq);
if (err)
@@ -116,257 +197,267 @@ int update_devfreq(struct devfreq *devfreq)
if (err)
return err;
+ if (devfreq->profile->freq_table)
+ if (devfreq_update_status(devfreq, freq))
+ dev_err(&devfreq->dev,
+ "Couldn't update frequency transition information.\n");
+
devfreq->previous_freq = freq;
return err;
}
+EXPORT_SYMBOL(update_devfreq);
/**
- * devfreq_notifier_call() - Notify that the device frequency requirements
- * has been changed out of devfreq framework.
- * @nb the notifier_block (supposed to be devfreq->nb)
- * @type not used
- * @devp not used
+ * devfreq_monitor() - Periodically poll devfreq objects.
+ * @work: the work struct used to run devfreq_monitor periodically.
*
- * Called by a notifier that uses devfreq->nb.
*/
-static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type,
- void *devp)
+static void devfreq_monitor(struct work_struct *work)
{
- struct devfreq *devfreq = container_of(nb, struct devfreq, nb);
- int ret;
+ int err;
+ struct devfreq *devfreq = container_of(work,
+ struct devfreq, work.work);
mutex_lock(&devfreq->lock);
- ret = update_devfreq(devfreq);
+ err = update_devfreq(devfreq);
+ if (err)
+ dev_err(&devfreq->dev, "dvfs failed with (%d) error\n", err);
+
+ queue_delayed_work(devfreq_wq, &devfreq->work,
+ msecs_to_jiffies(devfreq->profile->polling_ms));
mutex_unlock(&devfreq->lock);
+}
- return ret;
+/**
+ * devfreq_monitor_start() - Start load monitoring of devfreq instance
+ * @devfreq: the devfreq instance.
+ *
+ * Helper function for starting devfreq device load monitoing. By
+ * default delayed work based monitoring is supported. Function
+ * to be called from governor in response to DEVFREQ_GOV_START
+ * event when device is added to devfreq framework.
+ */
+void devfreq_monitor_start(struct devfreq *devfreq)
+{
+ INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor);
+ if (devfreq->profile->polling_ms)
+ queue_delayed_work(devfreq_wq, &devfreq->work,
+ msecs_to_jiffies(devfreq->profile->polling_ms));
}
+EXPORT_SYMBOL(devfreq_monitor_start);
/**
- * _remove_devfreq() - Remove devfreq from the device.
- * @devfreq: the devfreq struct
- * @skip: skip calling device_unregister().
+ * devfreq_monitor_stop() - Stop load monitoring of a devfreq instance
+ * @devfreq: the devfreq instance.
*
- * Note that the caller should lock devfreq->lock before calling
- * this. _remove_devfreq() will unlock it and free devfreq
- * internally. devfreq_list_lock should be locked by the caller
- * as well (not relased at return)
+ * Helper function to stop devfreq device load monitoing. Function
+ * to be called from governor in response to DEVFREQ_GOV_STOP
+ * event when device is removed from devfreq framework.
+ */
+void devfreq_monitor_stop(struct devfreq *devfreq)
+{
+ cancel_delayed_work_sync(&devfreq->work);
+}
+EXPORT_SYMBOL(devfreq_monitor_stop);
+
+/**
+ * devfreq_monitor_suspend() - Suspend load monitoring of a devfreq instance
+ * @devfreq: the devfreq instance.
*
- * Lock usage:
- * devfreq->lock: locked before call.
- * unlocked at return (and freed)
- * devfreq_list_lock: locked before call.
- * kept locked at return.
- * if devfreq is centrally polled.
+ * Helper function to suspend devfreq device load monitoing. Function
+ * to be called from governor in response to DEVFREQ_GOV_SUSPEND
+ * event or when polling interval is set to zero.
*
- * Freed memory:
- * devfreq
+ * Note: Though this function is same as devfreq_monitor_stop(),
+ * intentionally kept separate to provide hooks for collecting
+ * transition statistics.
*/
-static void _remove_devfreq(struct devfreq *devfreq, bool skip)
+void devfreq_monitor_suspend(struct devfreq *devfreq)
{
- if (!mutex_is_locked(&devfreq->lock)) {
- WARN(true, "devfreq->lock must be locked by the caller.\n");
- return;
- }
- if (!devfreq->governor->no_central_polling &&
- !mutex_is_locked(&devfreq_list_lock)) {
- WARN(true, "devfreq_list_lock must be locked by the caller.\n");
+ mutex_lock(&devfreq->lock);
+ if (devfreq->stop_polling) {
+ mutex_unlock(&devfreq->lock);
return;
}
- if (devfreq->being_removed)
- return;
+ devfreq_update_status(devfreq, devfreq->previous_freq);
+ devfreq->stop_polling = true;
+ mutex_unlock(&devfreq->lock);
+ cancel_delayed_work_sync(&devfreq->work);
+}
+EXPORT_SYMBOL(devfreq_monitor_suspend);
- devfreq->being_removed = true;
+/**
+ * devfreq_monitor_resume() - Resume load monitoring of a devfreq instance
+ * @devfreq: the devfreq instance.
+ *
+ * Helper function to resume devfreq device load monitoing. Function
+ * to be called from governor in response to DEVFREQ_GOV_RESUME
+ * event or when polling interval is set to non-zero.
+ */
+void devfreq_monitor_resume(struct devfreq *devfreq)
+{
+ unsigned long freq;
- if (devfreq->profile->exit)
- devfreq->profile->exit(devfreq->dev.parent);
+ mutex_lock(&devfreq->lock);
+ if (!devfreq->stop_polling)
+ goto out;
- if (devfreq->governor->exit)
- devfreq->governor->exit(devfreq);
+ if (!delayed_work_pending(&devfreq->work) &&
+ devfreq->profile->polling_ms)
+ queue_delayed_work(devfreq_wq, &devfreq->work,
+ msecs_to_jiffies(devfreq->profile->polling_ms));
- if (!skip && get_device(&devfreq->dev)) {
- device_unregister(&devfreq->dev);
- put_device(&devfreq->dev);
- }
+ devfreq->last_stat_updated = jiffies;
+ devfreq->stop_polling = false;
- if (!devfreq->governor->no_central_polling)
- list_del(&devfreq->node);
+ if (devfreq->profile->get_cur_freq &&
+ !devfreq->profile->get_cur_freq(devfreq->dev.parent, &freq))
+ devfreq->previous_freq = freq;
+out:
mutex_unlock(&devfreq->lock);
- mutex_destroy(&devfreq->lock);
-
- kfree(devfreq);
}
+EXPORT_SYMBOL(devfreq_monitor_resume);
/**
- * devfreq_dev_release() - Callback for struct device to release the device.
- * @dev: the devfreq device
+ * devfreq_interval_update() - Update device devfreq monitoring interval
+ * @devfreq: the devfreq instance.
+ * @delay: new polling interval to be set.
*
- * This calls _remove_devfreq() if _remove_devfreq() is not called.
- * Note that devfreq_dev_release() could be called by _remove_devfreq() as
- * well as by others unregistering the device.
+ * Helper function to set new load monitoring polling interval. Function
+ * to be called from governor in response to DEVFREQ_GOV_INTERVAL event.
*/
-static void devfreq_dev_release(struct device *dev)
+void devfreq_interval_update(struct devfreq *devfreq, unsigned int *delay)
{
- struct devfreq *devfreq = to_devfreq(dev);
- bool central_polling = !devfreq->governor->no_central_polling;
-
- /*
- * If devfreq_dev_release() was called by device_unregister() of
- * _remove_devfreq(), we cannot mutex_lock(&devfreq->lock) and
- * being_removed is already set. This also partially checks the case
- * where devfreq_dev_release() is called from a thread other than
- * the one called _remove_devfreq(); however, this case is
- * dealt completely with another following being_removed check.
- *
- * Because being_removed is never being
- * unset, we do not need to worry about race conditions on
- * being_removed.
- */
- if (devfreq->being_removed)
- return;
-
- if (central_polling)
- mutex_lock(&devfreq_list_lock);
+ unsigned int cur_delay = devfreq->profile->polling_ms;
+ unsigned int new_delay = *delay;
mutex_lock(&devfreq->lock);
+ devfreq->profile->polling_ms = new_delay;
- /*
- * Check being_removed flag again for the case where
- * devfreq_dev_release() was called in a thread other than the one
- * possibly called _remove_devfreq().
- */
- if (devfreq->being_removed) {
- mutex_unlock(&devfreq->lock);
+ if (devfreq->stop_polling)
goto out;
+
+ /* if new delay is zero, stop polling */
+ if (!new_delay) {
+ mutex_unlock(&devfreq->lock);
+ cancel_delayed_work_sync(&devfreq->work);
+ return;
}
- /* devfreq->lock is unlocked and removed in _removed_devfreq() */
- _remove_devfreq(devfreq, true);
+ /* if current delay is zero, start polling with new delay */
+ if (!cur_delay) {
+ queue_delayed_work(devfreq_wq, &devfreq->work,
+ msecs_to_jiffies(devfreq->profile->polling_ms));
+ goto out;
+ }
+ /* if current delay is greater than new delay, restart polling */
+ if (cur_delay > new_delay) {
+ mutex_unlock(&devfreq->lock);
+ cancel_delayed_work_sync(&devfreq->work);
+ mutex_lock(&devfreq->lock);
+ if (!devfreq->stop_polling)
+ queue_delayed_work(devfreq_wq, &devfreq->work,
+ msecs_to_jiffies(devfreq->profile->polling_ms));
+ }
out:
- if (central_polling)
- mutex_unlock(&devfreq_list_lock);
+ mutex_unlock(&devfreq->lock);
}
+EXPORT_SYMBOL(devfreq_interval_update);
/**
- * devfreq_monitor() - Periodically poll devfreq objects.
- * @work: the work struct used to run devfreq_monitor periodically.
+ * devfreq_notifier_call() - Notify that the device frequency requirements
+ * has been changed out of devfreq framework.
+ * @nb: the notifier_block (supposed to be devfreq->nb)
+ * @type: not used
+ * @devp: not used
*
+ * Called by a notifier that uses devfreq->nb.
*/
-static void devfreq_monitor(struct work_struct *work)
+static int devfreq_notifier_call(struct notifier_block *nb, unsigned long type,
+ void *devp)
{
- static unsigned long last_polled_at;
- struct devfreq *devfreq, *tmp;
- int error;
- unsigned long jiffies_passed;
- unsigned long next_jiffies = ULONG_MAX, now = jiffies;
- struct device *dev;
-
- /* Initially last_polled_at = 0, polling every device at bootup */
- jiffies_passed = now - last_polled_at;
- last_polled_at = now;
- if (jiffies_passed == 0)
- jiffies_passed = 1;
+ struct devfreq *devfreq = container_of(nb, struct devfreq, nb);
+ int ret;
- mutex_lock(&devfreq_list_lock);
- list_for_each_entry_safe(devfreq, tmp, &devfreq_list, node) {
- mutex_lock(&devfreq->lock);
- dev = devfreq->dev.parent;
+ mutex_lock(&devfreq->lock);
+ ret = update_devfreq(devfreq);
+ mutex_unlock(&devfreq->lock);
- /* Do not remove tmp for a while */
- wait_remove_device = tmp;
+ return ret;
+}
- if (devfreq->governor->no_central_polling ||
- devfreq->next_polling == 0) {
- mutex_unlock(&devfreq->lock);
- continue;
- }
+/**
+ * _remove_devfreq() - Remove devfreq from the list and release its resources.
+ * @devfreq: the devfreq struct
+ * @skip: skip calling device_unregister().
+ */
+static void _remove_devfreq(struct devfreq *devfreq)
+{
+ mutex_lock(&devfreq_list_lock);
+ if (IS_ERR(find_device_devfreq(devfreq->dev.parent))) {
mutex_unlock(&devfreq_list_lock);
+ dev_warn(&devfreq->dev, "releasing devfreq which doesn't exist\n");
+ return;
+ }
+ list_del(&devfreq->node);
+ mutex_unlock(&devfreq_list_lock);
- /*
- * Reduce more next_polling if devfreq_wq took an extra
- * delay. (i.e., CPU has been idled.)
- */
- if (devfreq->next_polling <= jiffies_passed) {
- error = update_devfreq(devfreq);
-
- /* Remove a devfreq with an error. */
- if (error && error != -EAGAIN) {
-
- dev_err(dev, "Due to update_devfreq error(%d), devfreq(%s) is removed from the device\n",
- error, devfreq->governor->name);
-
- /*
- * Unlock devfreq before locking the list
- * in order to avoid deadlock with
- * find_device_devfreq or others
- */
- mutex_unlock(&devfreq->lock);
- mutex_lock(&devfreq_list_lock);
- /* Check if devfreq is already removed */
- if (IS_ERR(find_device_devfreq(dev)))
- continue;
- mutex_lock(&devfreq->lock);
- /* This unlocks devfreq->lock and free it */
- _remove_devfreq(devfreq, false);
- continue;
- }
- devfreq->next_polling = devfreq->polling_jiffies;
- } else {
- devfreq->next_polling -= jiffies_passed;
- }
+ if (devfreq->governor)
+ devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_STOP, NULL);
- if (devfreq->next_polling)
- next_jiffies = (next_jiffies > devfreq->next_polling) ?
- devfreq->next_polling : next_jiffies;
+ if (devfreq->profile->exit)
+ devfreq->profile->exit(devfreq->dev.parent);
- mutex_unlock(&devfreq->lock);
- mutex_lock(&devfreq_list_lock);
- }
- wait_remove_device = NULL;
- mutex_unlock(&devfreq_list_lock);
+ mutex_destroy(&devfreq->lock);
+ kfree(devfreq);
+}
- if (next_jiffies > 0 && next_jiffies < ULONG_MAX) {
- polling = true;
- queue_delayed_work(devfreq_wq, &devfreq_work, next_jiffies);
- } else {
- polling = false;
- }
+/**
+ * devfreq_dev_release() - Callback for struct device to release the device.
+ * @dev: the devfreq device
+ *
+ * This calls _remove_devfreq() if _remove_devfreq() is not called.
+ */
+static void devfreq_dev_release(struct device *dev)
+{
+ struct devfreq *devfreq = to_devfreq(dev);
+
+ _remove_devfreq(devfreq);
}
/**
* devfreq_add_device() - Add devfreq feature to the device
* @dev: the device to add devfreq feature.
* @profile: device-specific profile to run devfreq.
- * @governor: the policy to choose frequency.
+ * @governor_name: name of the policy to choose frequency.
* @data: private data for the governor. The devfreq framework does not
* touch this value.
*/
struct devfreq *devfreq_add_device(struct device *dev,
struct devfreq_dev_profile *profile,
- const struct devfreq_governor *governor,
+ const char *governor_name,
void *data)
{
struct devfreq *devfreq;
+ struct devfreq_governor *governor;
int err = 0;
- if (!dev || !profile || !governor) {
+ if (!dev || !profile || !governor_name) {
dev_err(dev, "%s: Invalid parameters.\n", __func__);
return ERR_PTR(-EINVAL);
}
-
- if (!governor->no_central_polling) {
- mutex_lock(&devfreq_list_lock);
- devfreq = find_device_devfreq(dev);
- mutex_unlock(&devfreq_list_lock);
- if (!IS_ERR(devfreq)) {
- dev_err(dev, "%s: Unable to create devfreq for the device. It already has one.\n", __func__);
- err = -EINVAL;
- goto err_out;
- }
+ mutex_lock(&devfreq_list_lock);
+ devfreq = find_device_devfreq(dev);
+ mutex_unlock(&devfreq_list_lock);
+ if (!IS_ERR(devfreq)) {
+ dev_err(dev, "%s: Unable to create devfreq for the device. It already has one.\n", __func__);
+ err = -EINVAL;
+ goto err_out;
}
devfreq = kzalloc(sizeof(struct devfreq), GFP_KERNEL);
@@ -383,102 +474,405 @@ struct devfreq *devfreq_add_device(struct device *dev,
devfreq->dev.class = devfreq_class;
devfreq->dev.release = devfreq_dev_release;
devfreq->profile = profile;
- devfreq->governor = governor;
+ strncpy(devfreq->governor_name, governor_name, DEVFREQ_NAME_LEN);
devfreq->previous_freq = profile->initial_freq;
devfreq->data = data;
- devfreq->next_polling = devfreq->polling_jiffies
- = msecs_to_jiffies(devfreq->profile->polling_ms);
devfreq->nb.notifier_call = devfreq_notifier_call;
- dev_set_name(&devfreq->dev, dev_name(dev));
+ devfreq->trans_table = devm_kzalloc(dev, sizeof(unsigned int) *
+ devfreq->profile->max_state *
+ devfreq->profile->max_state,
+ GFP_KERNEL);
+ devfreq->time_in_state = devm_kzalloc(dev, sizeof(unsigned int) *
+ devfreq->profile->max_state,
+ GFP_KERNEL);
+ devfreq->last_stat_updated = jiffies;
+
+ dev_set_name(&devfreq->dev, "%s", dev_name(dev));
err = device_register(&devfreq->dev);
if (err) {
put_device(&devfreq->dev);
+ mutex_unlock(&devfreq->lock);
goto err_dev;
}
- if (governor->init)
- err = governor->init(devfreq);
- if (err)
- goto err_init;
-
mutex_unlock(&devfreq->lock);
- if (governor->no_central_polling)
- goto out;
-
mutex_lock(&devfreq_list_lock);
-
list_add(&devfreq->node, &devfreq_list);
- if (devfreq_wq && devfreq->next_polling && !polling) {
- polling = true;
- queue_delayed_work(devfreq_wq, &devfreq_work,
- devfreq->next_polling);
- }
+ governor = find_devfreq_governor(devfreq->governor_name);
+ if (!IS_ERR(governor))
+ devfreq->governor = governor;
+ if (devfreq->governor)
+ err = devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_START, NULL);
mutex_unlock(&devfreq_list_lock);
-out:
+ if (err) {
+ dev_err(dev, "%s: Unable to start governor for the device\n",
+ __func__);
+ goto err_init;
+ }
+
return devfreq;
err_init:
+ list_del(&devfreq->node);
device_unregister(&devfreq->dev);
err_dev:
- mutex_unlock(&devfreq->lock);
kfree(devfreq);
err_out:
return ERR_PTR(err);
}
+EXPORT_SYMBOL(devfreq_add_device);
/**
* devfreq_remove_device() - Remove devfreq feature from a device.
- * @devfreq the devfreq instance to be removed
+ * @devfreq: the devfreq instance to be removed
+ *
+ * The opposite of devfreq_add_device().
*/
int devfreq_remove_device(struct devfreq *devfreq)
{
- bool central_polling;
+ if (!devfreq)
+ return -EINVAL;
+
+ device_unregister(&devfreq->dev);
+ put_device(&devfreq->dev);
+
+ return 0;
+}
+EXPORT_SYMBOL(devfreq_remove_device);
+
+static int devm_devfreq_dev_match(struct device *dev, void *res, void *data)
+{
+ struct devfreq **r = res;
+
+ if (WARN_ON(!r || !*r))
+ return 0;
+
+ return *r == data;
+}
+
+static void devm_devfreq_dev_release(struct device *dev, void *res)
+{
+ devfreq_remove_device(*(struct devfreq **)res);
+}
+
+/**
+ * devm_devfreq_add_device() - Resource-managed devfreq_add_device()
+ * @dev: the device to add devfreq feature.
+ * @profile: device-specific profile to run devfreq.
+ * @governor_name: name of the policy to choose frequency.
+ * @data: private data for the governor. The devfreq framework does not
+ * touch this value.
+ *
+ * This function manages automatically the memory of devfreq device using device
+ * resource management and simplify the free operation for memory of devfreq
+ * device.
+ */
+struct devfreq *devm_devfreq_add_device(struct device *dev,
+ struct devfreq_dev_profile *profile,
+ const char *governor_name,
+ void *data)
+{
+ struct devfreq **ptr, *devfreq;
+
+ ptr = devres_alloc(devm_devfreq_dev_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ devfreq = devfreq_add_device(dev, profile, governor_name, data);
+ if (IS_ERR(devfreq)) {
+ devres_free(ptr);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ *ptr = devfreq;
+ devres_add(dev, ptr);
+
+ return devfreq;
+}
+EXPORT_SYMBOL(devm_devfreq_add_device);
+
+/**
+ * devm_devfreq_remove_device() - Resource-managed devfreq_remove_device()
+ * @dev: the device to add devfreq feature.
+ * @devfreq: the devfreq instance to be removed
+ */
+void devm_devfreq_remove_device(struct device *dev, struct devfreq *devfreq)
+{
+ WARN_ON(devres_release(dev, devm_devfreq_dev_release,
+ devm_devfreq_dev_match, devfreq));
+}
+EXPORT_SYMBOL(devm_devfreq_remove_device);
+/**
+ * devfreq_suspend_device() - Suspend devfreq of a device.
+ * @devfreq: the devfreq instance to be suspended
+ *
+ * This function is intended to be called by the pm callbacks
+ * (e.g., runtime_suspend, suspend) of the device driver that
+ * holds the devfreq.
+ */
+int devfreq_suspend_device(struct devfreq *devfreq)
+{
if (!devfreq)
return -EINVAL;
- central_polling = !devfreq->governor->no_central_polling;
+ if (!devfreq->governor)
+ return 0;
+
+ return devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_SUSPEND, NULL);
+}
+EXPORT_SYMBOL(devfreq_suspend_device);
+
+/**
+ * devfreq_resume_device() - Resume devfreq of a device.
+ * @devfreq: the devfreq instance to be resumed
+ *
+ * This function is intended to be called by the pm callbacks
+ * (e.g., runtime_resume, resume) of the device driver that
+ * holds the devfreq.
+ */
+int devfreq_resume_device(struct devfreq *devfreq)
+{
+ if (!devfreq)
+ return -EINVAL;
+
+ if (!devfreq->governor)
+ return 0;
+
+ return devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_RESUME, NULL);
+}
+EXPORT_SYMBOL(devfreq_resume_device);
+
+/**
+ * devfreq_add_governor() - Add devfreq governor
+ * @governor: the devfreq governor to be added
+ */
+int devfreq_add_governor(struct devfreq_governor *governor)
+{
+ struct devfreq_governor *g;
+ struct devfreq *devfreq;
+ int err = 0;
+
+ if (!governor) {
+ pr_err("%s: Invalid parameters.\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_lock(&devfreq_list_lock);
+ g = find_devfreq_governor(governor->name);
+ if (!IS_ERR(g)) {
+ pr_err("%s: governor %s already registered\n", __func__,
+ g->name);
+ err = -EINVAL;
+ goto err_out;
+ }
- if (central_polling) {
- mutex_lock(&devfreq_list_lock);
- while (wait_remove_device == devfreq) {
- mutex_unlock(&devfreq_list_lock);
- schedule();
- mutex_lock(&devfreq_list_lock);
+ list_add(&governor->node, &devfreq_governor_list);
+
+ list_for_each_entry(devfreq, &devfreq_list, node) {
+ int ret = 0;
+ struct device *dev = devfreq->dev.parent;
+
+ if (!strncmp(devfreq->governor_name, governor->name,
+ DEVFREQ_NAME_LEN)) {
+ /* The following should never occur */
+ if (devfreq->governor) {
+ dev_warn(dev,
+ "%s: Governor %s already present\n",
+ __func__, devfreq->governor->name);
+ ret = devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_STOP, NULL);
+ if (ret) {
+ dev_warn(dev,
+ "%s: Governor %s stop = %d\n",
+ __func__,
+ devfreq->governor->name, ret);
+ }
+ /* Fall through */
+ }
+ devfreq->governor = governor;
+ ret = devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_START, NULL);
+ if (ret) {
+ dev_warn(dev, "%s: Governor %s start=%d\n",
+ __func__, devfreq->governor->name,
+ ret);
+ }
}
}
- mutex_lock(&devfreq->lock);
- _remove_devfreq(devfreq, false); /* it unlocks devfreq->lock */
+err_out:
+ mutex_unlock(&devfreq_list_lock);
- if (central_polling)
- mutex_unlock(&devfreq_list_lock);
+ return err;
+}
+EXPORT_SYMBOL(devfreq_add_governor);
- return 0;
+/**
+ * devfreq_remove_device() - Remove devfreq feature from a device.
+ * @governor: the devfreq governor to be removed
+ */
+int devfreq_remove_governor(struct devfreq_governor *governor)
+{
+ struct devfreq_governor *g;
+ struct devfreq *devfreq;
+ int err = 0;
+
+ if (!governor) {
+ pr_err("%s: Invalid parameters.\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_lock(&devfreq_list_lock);
+ g = find_devfreq_governor(governor->name);
+ if (IS_ERR(g)) {
+ pr_err("%s: governor %s not registered\n", __func__,
+ governor->name);
+ err = PTR_ERR(g);
+ goto err_out;
+ }
+ list_for_each_entry(devfreq, &devfreq_list, node) {
+ int ret;
+ struct device *dev = devfreq->dev.parent;
+
+ if (!strncmp(devfreq->governor_name, governor->name,
+ DEVFREQ_NAME_LEN)) {
+ /* we should have a devfreq governor! */
+ if (!devfreq->governor) {
+ dev_warn(dev, "%s: Governor %s NOT present\n",
+ __func__, governor->name);
+ continue;
+ /* Fall through */
+ }
+ ret = devfreq->governor->event_handler(devfreq,
+ DEVFREQ_GOV_STOP, NULL);
+ if (ret) {
+ dev_warn(dev, "%s: Governor %s stop=%d\n",
+ __func__, devfreq->governor->name,
+ ret);
+ }
+ devfreq->governor = NULL;
+ }
+ }
+
+ list_del(&governor->node);
+err_out:
+ mutex_unlock(&devfreq_list_lock);
+
+ return err;
}
+EXPORT_SYMBOL(devfreq_remove_governor);
-static ssize_t show_governor(struct device *dev,
+static ssize_t governor_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
+ if (!to_devfreq(dev)->governor)
+ return -EINVAL;
+
return sprintf(buf, "%s\n", to_devfreq(dev)->governor->name);
}
-static ssize_t show_freq(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t governor_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct devfreq *df = to_devfreq(dev);
+ int ret;
+ char str_governor[DEVFREQ_NAME_LEN + 1];
+ struct devfreq_governor *governor;
+
+ ret = sscanf(buf, "%" __stringify(DEVFREQ_NAME_LEN) "s", str_governor);
+ if (ret != 1)
+ return -EINVAL;
+
+ mutex_lock(&devfreq_list_lock);
+ governor = find_devfreq_governor(str_governor);
+ if (IS_ERR(governor)) {
+ ret = PTR_ERR(governor);
+ goto out;
+ }
+ if (df->governor == governor)
+ goto out;
+
+ if (df->governor) {
+ ret = df->governor->event_handler(df, DEVFREQ_GOV_STOP, NULL);
+ if (ret) {
+ dev_warn(dev, "%s: Governor %s not stopped(%d)\n",
+ __func__, df->governor->name, ret);
+ goto out;
+ }
+ }
+ df->governor = governor;
+ strncpy(df->governor_name, governor->name, DEVFREQ_NAME_LEN);
+ ret = df->governor->event_handler(df, DEVFREQ_GOV_START, NULL);
+ if (ret)
+ dev_warn(dev, "%s: Governor %s not started(%d)\n",
+ __func__, df->governor->name, ret);
+out:
+ mutex_unlock(&devfreq_list_lock);
+
+ if (!ret)
+ ret = count;
+ return ret;
+}
+static DEVICE_ATTR_RW(governor);
+
+static ssize_t available_governors_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct devfreq_governor *tmp_governor;
+ ssize_t count = 0;
+
+ mutex_lock(&devfreq_list_lock);
+ list_for_each_entry(tmp_governor, &devfreq_governor_list, node)
+ count += scnprintf(&buf[count], (PAGE_SIZE - count - 2),
+ "%s ", tmp_governor->name);
+ mutex_unlock(&devfreq_list_lock);
+
+ /* Truncate the trailing space */
+ if (count)
+ count--;
+
+ count += sprintf(&buf[count], "\n");
+
+ return count;
+}
+static DEVICE_ATTR_RO(available_governors);
+
+static ssize_t cur_freq_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ unsigned long freq;
+ struct devfreq *devfreq = to_devfreq(dev);
+
+ if (devfreq->profile->get_cur_freq &&
+ !devfreq->profile->get_cur_freq(devfreq->dev.parent, &freq))
+ return sprintf(buf, "%lu\n", freq);
+
+ return sprintf(buf, "%lu\n", devfreq->previous_freq);
+}
+static DEVICE_ATTR_RO(cur_freq);
+
+static ssize_t target_freq_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%lu\n", to_devfreq(dev)->previous_freq);
}
+static DEVICE_ATTR_RO(target_freq);
-static ssize_t show_polling_interval(struct device *dev,
+static ssize_t polling_interval_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%d\n", to_devfreq(dev)->profile->polling_ms);
}
-static ssize_t store_polling_interval(struct device *dev,
+static ssize_t polling_interval_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -486,40 +880,21 @@ static ssize_t store_polling_interval(struct device *dev,
unsigned int value;
int ret;
+ if (!df->governor)
+ return -EINVAL;
+
ret = sscanf(buf, "%u", &value);
if (ret != 1)
- goto out;
-
- mutex_lock(&df->lock);
- df->profile->polling_ms = value;
- df->next_polling = df->polling_jiffies
- = msecs_to_jiffies(value);
- mutex_unlock(&df->lock);
+ return -EINVAL;
+ df->governor->event_handler(df, DEVFREQ_GOV_INTERVAL, &value);
ret = count;
- if (df->governor->no_central_polling)
- goto out;
-
- mutex_lock(&devfreq_list_lock);
- if (df->next_polling > 0 && !polling) {
- polling = true;
- queue_delayed_work(devfreq_wq, &devfreq_work,
- df->next_polling);
- }
- mutex_unlock(&devfreq_list_lock);
-out:
return ret;
}
+static DEVICE_ATTR_RW(polling_interval);
-static ssize_t show_central_polling(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- return sprintf(buf, "%d\n",
- !to_devfreq(dev)->governor->no_central_polling);
-}
-
-static ssize_t store_min_freq(struct device *dev, struct device_attribute *attr,
+static ssize_t min_freq_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct devfreq *df = to_devfreq(dev);
@@ -529,7 +904,7 @@ static ssize_t store_min_freq(struct device *dev, struct device_attribute *attr,
ret = sscanf(buf, "%lu", &value);
if (ret != 1)
- goto out;
+ return -EINVAL;
mutex_lock(&df->lock);
max = df->max_freq;
@@ -543,17 +918,16 @@ static ssize_t store_min_freq(struct device *dev, struct device_attribute *attr,
ret = count;
unlock:
mutex_unlock(&df->lock);
-out:
return ret;
}
-static ssize_t show_min_freq(struct device *dev, struct device_attribute *attr,
+static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%lu\n", to_devfreq(dev)->min_freq);
}
-static ssize_t store_max_freq(struct device *dev, struct device_attribute *attr,
+static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct devfreq *df = to_devfreq(dev);
@@ -563,7 +937,7 @@ static ssize_t store_max_freq(struct device *dev, struct device_attribute *attr,
ret = sscanf(buf, "%lu", &value);
if (ret != 1)
- goto out;
+ return -EINVAL;
mutex_lock(&df->lock);
min = df->min_freq;
@@ -577,43 +951,104 @@ static ssize_t store_max_freq(struct device *dev, struct device_attribute *attr,
ret = count;
unlock:
mutex_unlock(&df->lock);
-out:
return ret;
}
+static DEVICE_ATTR_RW(min_freq);
-static ssize_t show_max_freq(struct device *dev, struct device_attribute *attr,
+static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%lu\n", to_devfreq(dev)->max_freq);
}
+static DEVICE_ATTR_RW(max_freq);
-static struct device_attribute devfreq_attrs[] = {
- __ATTR(governor, S_IRUGO, show_governor, NULL),
- __ATTR(cur_freq, S_IRUGO, show_freq, NULL),
- __ATTR(central_polling, S_IRUGO, show_central_polling, NULL),
- __ATTR(polling_interval, S_IRUGO | S_IWUSR, show_polling_interval,
- store_polling_interval),
- __ATTR(min_freq, S_IRUGO | S_IWUSR, show_min_freq, store_min_freq),
- __ATTR(max_freq, S_IRUGO | S_IWUSR, show_max_freq, store_max_freq),
- { },
-};
+static ssize_t available_frequencies_show(struct device *d,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct devfreq *df = to_devfreq(d);
+ struct device *dev = df->dev.parent;
+ struct dev_pm_opp *opp;
+ ssize_t count = 0;
+ unsigned long freq = 0;
+
+ rcu_read_lock();
+ do {
+ opp = dev_pm_opp_find_freq_ceil(dev, &freq);
+ if (IS_ERR(opp))
+ break;
+
+ count += scnprintf(&buf[count], (PAGE_SIZE - count - 2),
+ "%lu ", freq);
+ freq++;
+ } while (1);
+ rcu_read_unlock();
+
+ /* Truncate the trailing space */
+ if (count)
+ count--;
+
+ count += sprintf(&buf[count], "\n");
+
+ return count;
+}
+static DEVICE_ATTR_RO(available_frequencies);
-/**
- * devfreq_start_polling() - Initialize data structure for devfreq framework and
- * start polling registered devfreq devices.
- */
-static int __init devfreq_start_polling(void)
+static ssize_t trans_stat_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
- mutex_lock(&devfreq_list_lock);
- polling = false;
- devfreq_wq = create_freezable_workqueue("devfreq_wq");
- INIT_DEFERRABLE_WORK(&devfreq_work, devfreq_monitor);
- mutex_unlock(&devfreq_list_lock);
+ struct devfreq *devfreq = to_devfreq(dev);
+ ssize_t len;
+ int i, j;
+ unsigned int max_state = devfreq->profile->max_state;
+
+ if (!devfreq->stop_polling &&
+ devfreq_update_status(devfreq, devfreq->previous_freq))
+ return 0;
+
+ len = sprintf(buf, " From : To\n");
+ len += sprintf(buf + len, " :");
+ for (i = 0; i < max_state; i++)
+ len += sprintf(buf + len, "%8u",
+ devfreq->profile->freq_table[i]);
+
+ len += sprintf(buf + len, " time(ms)\n");
+
+ for (i = 0; i < max_state; i++) {
+ if (devfreq->profile->freq_table[i]
+ == devfreq->previous_freq) {
+ len += sprintf(buf + len, "*");
+ } else {
+ len += sprintf(buf + len, " ");
+ }
+ len += sprintf(buf + len, "%8u:",
+ devfreq->profile->freq_table[i]);
+ for (j = 0; j < max_state; j++)
+ len += sprintf(buf + len, "%8u",
+ devfreq->trans_table[(i * max_state) + j]);
+ len += sprintf(buf + len, "%10u\n",
+ jiffies_to_msecs(devfreq->time_in_state[i]));
+ }
- devfreq_monitor(&devfreq_work.work);
- return 0;
+ len += sprintf(buf + len, "Total transition : %u\n",
+ devfreq->total_trans);
+ return len;
}
-late_initcall(devfreq_start_polling);
+static DEVICE_ATTR_RO(trans_stat);
+
+static struct attribute *devfreq_attrs[] = {
+ &dev_attr_governor.attr,
+ &dev_attr_available_governors.attr,
+ &dev_attr_cur_freq.attr,
+ &dev_attr_available_frequencies.attr,
+ &dev_attr_target_freq.attr,
+ &dev_attr_polling_interval.attr,
+ &dev_attr_min_freq.attr,
+ &dev_attr_max_freq.attr,
+ &dev_attr_trans_stat.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(devfreq);
static int __init devfreq_init(void)
{
@@ -622,7 +1057,15 @@ static int __init devfreq_init(void)
pr_err("%s: couldn't create class\n", __FILE__);
return PTR_ERR(devfreq_class);
}
- devfreq_class->dev_attrs = devfreq_attrs;
+
+ devfreq_wq = create_freezable_workqueue("devfreq_wq");
+ if (!devfreq_wq) {
+ class_destroy(devfreq_class);
+ pr_err("%s: couldn't create workqueue\n", __FILE__);
+ return -ENOMEM;
+ }
+ devfreq_class->dev_groups = devfreq_groups;
+
return 0;
}
subsys_initcall(devfreq_init);
@@ -630,6 +1073,7 @@ subsys_initcall(devfreq_init);
static void __exit devfreq_exit(void)
{
class_destroy(devfreq_class);
+ destroy_workqueue(devfreq_wq);
}
module_exit(devfreq_exit);
@@ -641,30 +1085,36 @@ module_exit(devfreq_exit);
/**
* devfreq_recommended_opp() - Helper function to get proper OPP for the
* freq value given to target callback.
- * @dev The devfreq user device. (parent of devfreq)
- * @freq The frequency given to target function
- * @flags Flags handed from devfreq framework.
+ * @dev: The devfreq user device. (parent of devfreq)
+ * @freq: The frequency given to target function
+ * @flags: Flags handed from devfreq framework.
*
+ * Locking: This function must be called under rcu_read_lock(). opp is a rcu
+ * protected pointer. The reason for the same is that the opp pointer which is
+ * returned will remain valid for use with opp_get_{voltage, freq} only while
+ * under the locked area. The pointer returned must be used prior to unlocking
+ * with rcu_read_unlock() to maintain the integrity of the pointer.
*/
-struct opp *devfreq_recommended_opp(struct device *dev, unsigned long *freq,
- u32 flags)
+struct dev_pm_opp *devfreq_recommended_opp(struct device *dev,
+ unsigned long *freq,
+ u32 flags)
{
- struct opp *opp;
+ struct dev_pm_opp *opp;
if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND) {
/* The freq is an upper bound. opp should be lower */
- opp = opp_find_freq_floor(dev, freq);
+ opp = dev_pm_opp_find_freq_floor(dev, freq);
/* If not available, use the closest opp */
- if (opp == ERR_PTR(-ENODEV))
- opp = opp_find_freq_ceil(dev, freq);
+ if (opp == ERR_PTR(-ERANGE))
+ opp = dev_pm_opp_find_freq_ceil(dev, freq);
} else {
/* The freq is an lower bound. opp should be higher */
- opp = opp_find_freq_ceil(dev, freq);
+ opp = dev_pm_opp_find_freq_ceil(dev, freq);
/* If not available, use the closest opp */
- if (opp == ERR_PTR(-ENODEV))
- opp = opp_find_freq_floor(dev, freq);
+ if (opp == ERR_PTR(-ERANGE))
+ opp = dev_pm_opp_find_freq_floor(dev, freq);
}
return opp;
@@ -674,36 +1124,98 @@ struct opp *devfreq_recommended_opp(struct device *dev, unsigned long *freq,
* devfreq_register_opp_notifier() - Helper function to get devfreq notified
* for any changes in the OPP availability
* changes
- * @dev The devfreq user device. (parent of devfreq)
- * @devfreq The devfreq object.
+ * @dev: The devfreq user device. (parent of devfreq)
+ * @devfreq: The devfreq object.
*/
int devfreq_register_opp_notifier(struct device *dev, struct devfreq *devfreq)
{
- struct srcu_notifier_head *nh = opp_get_notifier(dev);
+ struct srcu_notifier_head *nh;
+ int ret = 0;
+ rcu_read_lock();
+ nh = dev_pm_opp_get_notifier(dev);
if (IS_ERR(nh))
- return PTR_ERR(nh);
- return srcu_notifier_chain_register(nh, &devfreq->nb);
+ ret = PTR_ERR(nh);
+ rcu_read_unlock();
+ if (!ret)
+ ret = srcu_notifier_chain_register(nh, &devfreq->nb);
+
+ return ret;
}
/**
* devfreq_unregister_opp_notifier() - Helper function to stop getting devfreq
* notified for any changes in the OPP
* availability changes anymore.
- * @dev The devfreq user device. (parent of devfreq)
- * @devfreq The devfreq object.
+ * @dev: The devfreq user device. (parent of devfreq)
+ * @devfreq: The devfreq object.
*
* At exit() callback of devfreq_dev_profile, this must be included if
* devfreq_recommended_opp is used.
*/
int devfreq_unregister_opp_notifier(struct device *dev, struct devfreq *devfreq)
{
- struct srcu_notifier_head *nh = opp_get_notifier(dev);
+ struct srcu_notifier_head *nh;
+ int ret = 0;
+ rcu_read_lock();
+ nh = dev_pm_opp_get_notifier(dev);
if (IS_ERR(nh))
- return PTR_ERR(nh);
- return srcu_notifier_chain_unregister(nh, &devfreq->nb);
+ ret = PTR_ERR(nh);
+ rcu_read_unlock();
+ if (!ret)
+ ret = srcu_notifier_chain_unregister(nh, &devfreq->nb);
+
+ return ret;
+}
+
+static void devm_devfreq_opp_release(struct device *dev, void *res)
+{
+ devfreq_unregister_opp_notifier(dev, *(struct devfreq **)res);
+}
+
+/**
+ * devm_ devfreq_register_opp_notifier()
+ * - Resource-managed devfreq_register_opp_notifier()
+ * @dev: The devfreq user device. (parent of devfreq)
+ * @devfreq: The devfreq object.
+ */
+int devm_devfreq_register_opp_notifier(struct device *dev,
+ struct devfreq *devfreq)
+{
+ struct devfreq **ptr;
+ int ret;
+
+ ptr = devres_alloc(devm_devfreq_opp_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ ret = devfreq_register_opp_notifier(dev, devfreq);
+ if (ret) {
+ devres_free(ptr);
+ return ret;
+ }
+
+ *ptr = devfreq;
+ devres_add(dev, ptr);
+
+ return 0;
+}
+EXPORT_SYMBOL(devm_devfreq_register_opp_notifier);
+
+/**
+ * devm_devfreq_unregister_opp_notifier()
+ * - Resource-managed devfreq_unregister_opp_notifier()
+ * @dev: The devfreq user device. (parent of devfreq)
+ * @devfreq: The devfreq object.
+ */
+void devm_devfreq_unregister_opp_notifier(struct device *dev,
+ struct devfreq *devfreq)
+{
+ WARN_ON(devres_release(dev, devm_devfreq_opp_release,
+ devm_devfreq_dev_match, devfreq));
}
+EXPORT_SYMBOL(devm_devfreq_unregister_opp_notifier);
MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
MODULE_DESCRIPTION("devfreq class support");
diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile
new file mode 100644
index 00000000000..49bc9175f92
--- /dev/null
+++ b/drivers/devfreq/exynos/Makefile
@@ -0,0 +1,3 @@
+# Exynos DEVFREQ Drivers
+obj-$(CONFIG_ARM_EXYNOS4_BUS_DEVFREQ) += exynos_ppmu.o exynos4_bus.o
+obj-$(CONFIG_ARM_EXYNOS5_BUS_DEVFREQ) += exynos_ppmu.o exynos5_bus.o
diff --git a/drivers/devfreq/exynos4_bus.c b/drivers/devfreq/exynos/exynos4_bus.c
index 88ddc77a9bb..d9b08d3b683 100644
--- a/drivers/devfreq/exynos4_bus.c
+++ b/drivers/devfreq/exynos/exynos4_bus.c
@@ -19,20 +19,16 @@
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/suspend.h>
-#include <linux/opp.h>
+#include <linux/pm_opp.h>
#include <linux/devfreq.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/module.h>
-/* Exynos4 ASV has been in the mailing list, but not upstreamed, yet. */
-#ifdef CONFIG_EXYNOS_ASV
-extern unsigned int exynos_result_of_asv;
-#endif
-
-#include <mach/regs-clock.h>
+#include <mach/map.h>
-#include <plat/map-s5p.h>
+#include "exynos_ppmu.h"
+#include "exynos4_bus.h"
#define MAX_SAFEVOLT 1200000 /* 1.2V */
@@ -44,22 +40,6 @@ enum exynos4_busf_type {
/* Assume that the bus is saturated if the utilization is 40% */
#define BUS_SATURATION_RATIO 40
-enum ppmu_counter {
- PPMU_PMNCNT0 = 0,
- PPMU_PMCCNT1,
- PPMU_PMNCNT2,
- PPMU_PMNCNT3,
- PPMU_PMNCNT_MAX,
-};
-struct exynos4_ppmu {
- void __iomem *hw_base;
- unsigned int ccnt;
- unsigned int event;
- unsigned int count[PPMU_PMNCNT_MAX];
- bool ccnt_overflow;
- bool count_overflow[PPMU_PMNCNT_MAX];
-};
-
enum busclk_level_idx {
LV_0 = 0,
LV_1,
@@ -68,11 +48,28 @@ enum busclk_level_idx {
LV_4,
_LV_END
};
+
+enum exynos_ppmu_idx {
+ PPMU_DMC0,
+ PPMU_DMC1,
+ PPMU_END,
+};
+
#define EX4210_LV_MAX LV_2
#define EX4x12_LV_MAX LV_4
#define EX4210_LV_NUM (LV_2 + 1)
#define EX4x12_LV_NUM (LV_4 + 1)
+/**
+ * struct busfreq_opp_info - opp information for bus
+ * @rate: Frequency in hertz
+ * @volt: Voltage in microvolts corresponding to this OPP
+ */
+struct busfreq_opp_info {
+ unsigned long rate;
+ unsigned long volt;
+};
+
struct busfreq_data {
enum exynos4_busf_type type;
struct device *dev;
@@ -80,8 +77,8 @@ struct busfreq_data {
bool disabled;
struct regulator *vdd_int;
struct regulator *vdd_mif; /* Exynos4412/4212 only */
- struct opp *curr_opp;
- struct exynos4_ppmu dmc[2];
+ struct busfreq_opp_info curr_oppinfo;
+ struct busfreq_ppmu_data ppmu_data;
struct notifier_block pm_notifier;
struct mutex lock;
@@ -91,12 +88,6 @@ struct busfreq_data {
unsigned int top_divtable[_LV_END];
};
-struct bus_opp_table {
- unsigned int idx;
- unsigned long clk;
- unsigned long volt;
-};
-
/* 4210 controls clock of mif and voltage of int */
static struct bus_opp_table exynos4210_busclk_table[] = {
{LV_0, 400000, 1150000},
@@ -106,7 +97,7 @@ static struct bus_opp_table exynos4210_busclk_table[] = {
};
/*
- * MIF is the main control knob clock for exynox4x12 MIF/INT
+ * MIF is the main control knob clock for Exynos4x12 MIF/INT
* clock and voltage of both mif/int are controlled.
*/
static struct bus_opp_table exynos4x12_mifclk_table[] = {
@@ -296,13 +287,14 @@ static unsigned int exynos4x12_clkdiv_sclkip[][3] = {
};
-static int exynos4210_set_busclk(struct busfreq_data *data, struct opp *opp)
+static int exynos4210_set_busclk(struct busfreq_data *data,
+ struct busfreq_opp_info *oppi)
{
unsigned int index;
unsigned int tmp;
for (index = LV_0; index < EX4210_LV_NUM; index++)
- if (opp_get_freq(opp) == exynos4210_busclk_table[index].clk)
+ if (oppi->rate == exynos4210_busclk_table[index].clk)
break;
if (index == EX4210_LV_NUM)
@@ -361,13 +353,14 @@ static int exynos4210_set_busclk(struct busfreq_data *data, struct opp *opp)
return 0;
}
-static int exynos4x12_set_busclk(struct busfreq_data *data, struct opp *opp)
+static int exynos4x12_set_busclk(struct busfreq_data *data,
+ struct busfreq_opp_info *oppi)
{
unsigned int index;
unsigned int tmp;
for (index = LV_0; index < EX4x12_LV_NUM; index++)
- if (opp_get_freq(opp) == exynos4x12_mifclk_table[index].clk)
+ if (oppi->rate == exynos4x12_mifclk_table[index].clk)
break;
if (index == EX4x12_LV_NUM)
@@ -512,57 +505,6 @@ static int exynos4x12_set_busclk(struct busfreq_data *data, struct opp *opp)
return 0;
}
-
-static void busfreq_mon_reset(struct busfreq_data *data)
-{
- unsigned int i;
-
- for (i = 0; i < 2; i++) {
- void __iomem *ppmu_base = data->dmc[i].hw_base;
-
- /* Reset PPMU */
- __raw_writel(0x8000000f, ppmu_base + 0xf010);
- __raw_writel(0x8000000f, ppmu_base + 0xf050);
- __raw_writel(0x6, ppmu_base + 0xf000);
- __raw_writel(0x0, ppmu_base + 0xf100);
-
- /* Set PPMU Event */
- data->dmc[i].event = 0x6;
- __raw_writel(((data->dmc[i].event << 12) | 0x1),
- ppmu_base + 0xfc);
-
- /* Start PPMU */
- __raw_writel(0x1, ppmu_base + 0xf000);
- }
-}
-
-static void exynos4_read_ppmu(struct busfreq_data *data)
-{
- int i, j;
-
- for (i = 0; i < 2; i++) {
- void __iomem *ppmu_base = data->dmc[i].hw_base;
- u32 overflow;
-
- /* Stop PPMU */
- __raw_writel(0x0, ppmu_base + 0xf000);
-
- /* Update local data from PPMU */
- overflow = __raw_readl(ppmu_base + 0xf050);
-
- data->dmc[i].ccnt = __raw_readl(ppmu_base + 0xf100);
- data->dmc[i].ccnt_overflow = overflow & (1 << 31);
-
- for (j = 0; j < PPMU_PMNCNT_MAX; j++) {
- data->dmc[i].count[j] = __raw_readl(
- ppmu_base + (0xf110 + (0x10 * j)));
- data->dmc[i].count_overflow[j] = overflow & (1 << j);
- }
- }
-
- busfreq_mon_reset(data);
-}
-
static int exynos4x12_get_intspec(unsigned long mifclk)
{
int i = 0;
@@ -576,11 +518,12 @@ static int exynos4x12_get_intspec(unsigned long mifclk)
return -EINVAL;
}
-static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp,
- struct opp *oldopp)
+static int exynos4_bus_setvolt(struct busfreq_data *data,
+ struct busfreq_opp_info *oppi,
+ struct busfreq_opp_info *oldoppi)
{
int err = 0, tmp;
- unsigned long volt = opp_get_voltage(opp);
+ unsigned long volt = oppi->volt;
switch (data->type) {
case TYPE_BUSF_EXYNOS4210:
@@ -595,11 +538,11 @@ static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp,
if (err)
break;
- tmp = exynos4x12_get_intspec(opp_get_freq(opp));
+ tmp = exynos4x12_get_intspec(oppi->rate);
if (tmp < 0) {
err = tmp;
regulator_set_voltage(data->vdd_mif,
- opp_get_voltage(oldopp),
+ oldoppi->volt,
MAX_SAFEVOLT);
break;
}
@@ -609,7 +552,7 @@ static int exynos4_bus_setvolt(struct busfreq_data *data, struct opp *opp,
/* Try to recover */
if (err)
regulator_set_voltage(data->vdd_mif,
- opp_get_voltage(oldopp),
+ oldoppi->volt,
MAX_SAFEVOLT);
break;
default:
@@ -626,17 +569,26 @@ static int exynos4_bus_target(struct device *dev, unsigned long *_freq,
struct platform_device *pdev = container_of(dev, struct platform_device,
dev);
struct busfreq_data *data = platform_get_drvdata(pdev);
- struct opp *opp = devfreq_recommended_opp(dev, _freq, flags);
- unsigned long freq = opp_get_freq(opp);
- unsigned long old_freq = opp_get_freq(data->curr_opp);
+ struct dev_pm_opp *opp;
+ unsigned long freq;
+ unsigned long old_freq = data->curr_oppinfo.rate;
+ struct busfreq_opp_info new_oppinfo;
- if (IS_ERR(opp))
+ rcu_read_lock();
+ opp = devfreq_recommended_opp(dev, _freq, flags);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
return PTR_ERR(opp);
+ }
+ new_oppinfo.rate = dev_pm_opp_get_freq(opp);
+ new_oppinfo.volt = dev_pm_opp_get_voltage(opp);
+ rcu_read_unlock();
+ freq = new_oppinfo.rate;
if (old_freq == freq)
return 0;
- dev_dbg(dev, "targetting %lukHz %luuV\n", freq, opp_get_voltage(opp));
+ dev_dbg(dev, "targeting %lukHz %luuV\n", freq, new_oppinfo.volt);
mutex_lock(&data->lock);
@@ -644,17 +596,18 @@ static int exynos4_bus_target(struct device *dev, unsigned long *_freq,
goto out;
if (old_freq < freq)
- err = exynos4_bus_setvolt(data, opp, data->curr_opp);
+ err = exynos4_bus_setvolt(data, &new_oppinfo,
+ &data->curr_oppinfo);
if (err)
goto out;
if (old_freq != freq) {
switch (data->type) {
case TYPE_BUSF_EXYNOS4210:
- err = exynos4210_set_busclk(data, opp);
+ err = exynos4210_set_busclk(data, &new_oppinfo);
break;
case TYPE_BUSF_EXYNOS4x12:
- err = exynos4x12_set_busclk(data, opp);
+ err = exynos4x12_set_busclk(data, &new_oppinfo);
break;
default:
err = -EINVAL;
@@ -664,94 +617,46 @@ static int exynos4_bus_target(struct device *dev, unsigned long *_freq,
goto out;
if (old_freq > freq)
- err = exynos4_bus_setvolt(data, opp, data->curr_opp);
+ err = exynos4_bus_setvolt(data, &new_oppinfo,
+ &data->curr_oppinfo);
if (err)
goto out;
- data->curr_opp = opp;
+ data->curr_oppinfo = new_oppinfo;
out:
mutex_unlock(&data->lock);
return err;
}
-static int exynos4_get_busier_dmc(struct busfreq_data *data)
-{
- u64 p0 = data->dmc[0].count[0];
- u64 p1 = data->dmc[1].count[0];
-
- p0 *= data->dmc[1].ccnt;
- p1 *= data->dmc[0].ccnt;
-
- if (data->dmc[1].ccnt == 0)
- return 0;
-
- if (p0 > p1)
- return 0;
- return 1;
-}
-
static int exynos4_bus_get_dev_status(struct device *dev,
struct devfreq_dev_status *stat)
{
struct busfreq_data *data = dev_get_drvdata(dev);
- int busier_dmc;
- int cycles_x2 = 2; /* 2 x cycles */
- void __iomem *addr;
- u32 timing;
- u32 memctrl;
-
- exynos4_read_ppmu(data);
- busier_dmc = exynos4_get_busier_dmc(data);
- stat->current_frequency = opp_get_freq(data->curr_opp);
-
- if (busier_dmc)
- addr = S5P_VA_DMC1;
- else
- addr = S5P_VA_DMC0;
-
- memctrl = __raw_readl(addr + 0x04); /* one of DDR2/3/LPDDR2 */
- timing = __raw_readl(addr + 0x38); /* CL or WL/RL values */
-
- switch ((memctrl >> 8) & 0xf) {
- case 0x4: /* DDR2 */
- cycles_x2 = ((timing >> 16) & 0xf) * 2;
- break;
- case 0x5: /* LPDDR2 */
- case 0x6: /* DDR3 */
- cycles_x2 = ((timing >> 8) & 0xf) + ((timing >> 0) & 0xf);
- break;
- default:
- pr_err("%s: Unknown Memory Type(%d).\n", __func__,
- (memctrl >> 8) & 0xf);
- return -EINVAL;
- }
+ struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
+ int busier;
+
+ exynos_read_ppmu(ppmu_data);
+ busier = exynos_get_busier_ppmu(ppmu_data);
+ stat->current_frequency = data->curr_oppinfo.rate;
/* Number of cycles spent on memory access */
- stat->busy_time = data->dmc[busier_dmc].count[0] / 2 * (cycles_x2 + 2);
+ stat->busy_time = ppmu_data->ppmu[busier].count[PPMU_PMNCNT3];
stat->busy_time *= 100 / BUS_SATURATION_RATIO;
- stat->total_time = data->dmc[busier_dmc].ccnt;
+ stat->total_time = ppmu_data->ppmu[busier].ccnt;
/* If the counters have overflown, retry */
- if (data->dmc[busier_dmc].ccnt_overflow ||
- data->dmc[busier_dmc].count_overflow[0])
+ if (ppmu_data->ppmu[busier].ccnt_overflow ||
+ ppmu_data->ppmu[busier].count_overflow[0])
return -EAGAIN;
return 0;
}
-static void exynos4_bus_exit(struct device *dev)
-{
- struct busfreq_data *data = dev_get_drvdata(dev);
-
- devfreq_unregister_opp_notifier(dev, data->devfreq);
-}
-
static struct devfreq_dev_profile exynos4_devfreq_profile = {
.initial_freq = 400000,
.polling_ms = 50,
.target = exynos4_bus_target,
.get_dev_status = exynos4_bus_get_dev_status,
- .exit = exynos4_bus_exit,
};
static int exynos4210_init_tables(struct busfreq_data *data)
@@ -813,11 +718,11 @@ static int exynos4210_init_tables(struct busfreq_data *data)
data->top_divtable[i] = tmp;
}
-#ifdef CONFIG_EXYNOS_ASV
- tmp = exynos4_result_of_asv;
-#else
+ /*
+ * TODO: init tmp based on busfreq_data
+ * (device-tree or platform-data)
+ */
tmp = 0; /* Max voltages for the reliability of the unknown */
-#endif
pr_debug("ASV Group of Exynos4 is %d\n", tmp);
/* Use merged grouping for voltage */
@@ -849,7 +754,7 @@ static int exynos4210_init_tables(struct busfreq_data *data)
exynos4210_busclk_table[i].volt = exynos4210_asv_volt[mgrp][i];
for (i = LV_0; i < EX4210_LV_NUM; i++) {
- err = opp_add(data->dev, exynos4210_busclk_table[i].clk,
+ err = dev_pm_opp_add(data->dev, exynos4210_busclk_table[i].clk,
exynos4210_busclk_table[i].volt);
if (err) {
dev_err(data->dev, "Cannot add opp entries.\n");
@@ -898,11 +803,7 @@ static int exynos4x12_init_tables(struct busfreq_data *data)
data->dmc_divtable[i] = tmp;
}
-#ifdef CONFIG_EXYNOS_ASV
- tmp = exynos4_result_of_asv;
-#else
tmp = 0; /* Max voltages for the reliability of the unknown */
-#endif
if (tmp > 8)
tmp = 0;
@@ -916,7 +817,7 @@ static int exynos4x12_init_tables(struct busfreq_data *data)
}
for (i = 0; i < EX4x12_LV_NUM; i++) {
- ret = opp_add(data->dev, exynos4x12_mifclk_table[i].clk,
+ ret = dev_pm_opp_add(data->dev, exynos4x12_mifclk_table[i].clk,
exynos4x12_mifclk_table[i].volt);
if (ret) {
dev_err(data->dev, "Fail to add opp entries.\n");
@@ -932,7 +833,8 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
{
struct busfreq_data *data = container_of(this, struct busfreq_data,
pm_notifier);
- struct opp *opp;
+ struct dev_pm_opp *opp;
+ struct busfreq_opp_info new_oppinfo;
unsigned long maxfreq = ULONG_MAX;
int err = 0;
@@ -943,18 +845,30 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
data->disabled = true;
- opp = opp_find_freq_floor(data->dev, &maxfreq);
+ rcu_read_lock();
+ opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ dev_err(data->dev, "%s: unable to find a min freq\n",
+ __func__);
+ mutex_unlock(&data->lock);
+ return PTR_ERR(opp);
+ }
+ new_oppinfo.rate = dev_pm_opp_get_freq(opp);
+ new_oppinfo.volt = dev_pm_opp_get_voltage(opp);
+ rcu_read_unlock();
- err = exynos4_bus_setvolt(data, opp, data->curr_opp);
+ err = exynos4_bus_setvolt(data, &new_oppinfo,
+ &data->curr_oppinfo);
if (err)
goto unlock;
switch (data->type) {
case TYPE_BUSF_EXYNOS4210:
- err = exynos4210_set_busclk(data, opp);
+ err = exynos4210_set_busclk(data, &new_oppinfo);
break;
case TYPE_BUSF_EXYNOS4x12:
- err = exynos4x12_set_busclk(data, opp);
+ err = exynos4x12_set_busclk(data, &new_oppinfo);
break;
default:
err = -EINVAL;
@@ -962,7 +876,7 @@ static int exynos4_busfreq_pm_notifier_event(struct notifier_block *this,
if (err)
goto unlock;
- data->curr_opp = opp;
+ data->curr_oppinfo = new_oppinfo;
unlock:
mutex_unlock(&data->lock);
if (err)
@@ -980,22 +894,33 @@ unlock:
return NOTIFY_DONE;
}
-static __devinit int exynos4_busfreq_probe(struct platform_device *pdev)
+static int exynos4_busfreq_probe(struct platform_device *pdev)
{
struct busfreq_data *data;
- struct opp *opp;
+ struct busfreq_ppmu_data *ppmu_data;
+ struct dev_pm_opp *opp;
struct device *dev = &pdev->dev;
int err = 0;
- data = kzalloc(sizeof(struct busfreq_data), GFP_KERNEL);
+ data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data), GFP_KERNEL);
if (data == NULL) {
dev_err(dev, "Cannot allocate memory.\n");
return -ENOMEM;
}
+ ppmu_data = &data->ppmu_data;
+ ppmu_data->ppmu_end = PPMU_END;
+ ppmu_data->ppmu = devm_kzalloc(dev,
+ sizeof(struct exynos_ppmu) * PPMU_END,
+ GFP_KERNEL);
+ if (!ppmu_data->ppmu) {
+ dev_err(dev, "Failed to allocate memory for exynos_ppmu\n");
+ return -ENOMEM;
+ }
+
data->type = pdev->id_entry->driver_data;
- data->dmc[0].hw_base = S5P_VA_DMC0;
- data->dmc[1].hw_base = S5P_VA_DMC1;
+ ppmu_data->ppmu[PPMU_DMC0].hw_base = S5P_VA_DMC0;
+ ppmu_data->ppmu[PPMU_DMC1].hw_base = S5P_VA_DMC1;
data->pm_notifier.notifier_call = exynos4_busfreq_pm_notifier_event;
data->dev = dev;
mutex_init(&data->lock);
@@ -1011,91 +936,90 @@ static __devinit int exynos4_busfreq_probe(struct platform_device *pdev)
dev_err(dev, "Cannot determine the device id %d\n", data->type);
err = -EINVAL;
}
- if (err)
- goto err_regulator;
+ if (err) {
+ dev_err(dev, "Cannot initialize busfreq table %d\n",
+ data->type);
+ return err;
+ }
- data->vdd_int = regulator_get(dev, "vdd_int");
+ data->vdd_int = devm_regulator_get(dev, "vdd_int");
if (IS_ERR(data->vdd_int)) {
dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
- err = PTR_ERR(data->vdd_int);
- goto err_regulator;
+ return PTR_ERR(data->vdd_int);
}
if (data->type == TYPE_BUSF_EXYNOS4x12) {
- data->vdd_mif = regulator_get(dev, "vdd_mif");
+ data->vdd_mif = devm_regulator_get(dev, "vdd_mif");
if (IS_ERR(data->vdd_mif)) {
dev_err(dev, "Cannot get the regulator \"vdd_mif\"\n");
- err = PTR_ERR(data->vdd_mif);
- regulator_put(data->vdd_int);
- goto err_regulator;
-
+ return PTR_ERR(data->vdd_mif);
}
}
- opp = opp_find_freq_floor(dev, &exynos4_devfreq_profile.initial_freq);
+ rcu_read_lock();
+ opp = dev_pm_opp_find_freq_floor(dev,
+ &exynos4_devfreq_profile.initial_freq);
if (IS_ERR(opp)) {
+ rcu_read_unlock();
dev_err(dev, "Invalid initial frequency %lu kHz.\n",
- exynos4_devfreq_profile.initial_freq);
- err = PTR_ERR(opp);
- goto err_opp_add;
+ exynos4_devfreq_profile.initial_freq);
+ return PTR_ERR(opp);
}
- data->curr_opp = opp;
+ data->curr_oppinfo.rate = dev_pm_opp_get_freq(opp);
+ data->curr_oppinfo.volt = dev_pm_opp_get_voltage(opp);
+ rcu_read_unlock();
platform_set_drvdata(pdev, data);
- busfreq_mon_reset(data);
+ data->devfreq = devm_devfreq_add_device(dev, &exynos4_devfreq_profile,
+ "simple_ondemand", NULL);
+ if (IS_ERR(data->devfreq))
+ return PTR_ERR(data->devfreq);
- data->devfreq = devfreq_add_device(dev, &exynos4_devfreq_profile,
- &devfreq_simple_ondemand, NULL);
- if (IS_ERR(data->devfreq)) {
- err = PTR_ERR(data->devfreq);
- goto err_opp_add;
- }
+ /*
+ * Start PPMU (Performance Profiling Monitoring Unit) to check
+ * utilization of each IP in the Exynos4 SoC.
+ */
+ busfreq_mon_reset(ppmu_data);
- devfreq_register_opp_notifier(dev, data->devfreq);
+ /* Register opp_notifier for Exynos4 busfreq */
+ err = devm_devfreq_register_opp_notifier(dev, data->devfreq);
+ if (err < 0) {
+ dev_err(dev, "Failed to register opp notifier\n");
+ return err;
+ }
+ /* Register pm_notifier for Exynos4 busfreq */
err = register_pm_notifier(&data->pm_notifier);
if (err) {
dev_err(dev, "Failed to setup pm notifier\n");
- goto err_devfreq_add;
+ return err;
}
return 0;
-err_devfreq_add:
- devfreq_remove_device(data->devfreq);
-err_opp_add:
- if (data->vdd_mif)
- regulator_put(data->vdd_mif);
- regulator_put(data->vdd_int);
-err_regulator:
- kfree(data);
- return err;
}
-static __devexit int exynos4_busfreq_remove(struct platform_device *pdev)
+static int exynos4_busfreq_remove(struct platform_device *pdev)
{
struct busfreq_data *data = platform_get_drvdata(pdev);
+ /* Unregister all of notifier chain */
unregister_pm_notifier(&data->pm_notifier);
- devfreq_remove_device(data->devfreq);
- regulator_put(data->vdd_int);
- if (data->vdd_mif)
- regulator_put(data->vdd_mif);
- kfree(data);
return 0;
}
+#ifdef CONFIG_PM_SLEEP
static int exynos4_busfreq_resume(struct device *dev)
{
struct busfreq_data *data = dev_get_drvdata(dev);
+ struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
- busfreq_mon_reset(data);
+ busfreq_mon_reset(ppmu_data);
return 0;
}
+#endif
-static const struct dev_pm_ops exynos4_busfreq_pm = {
- .resume = exynos4_busfreq_resume,
-};
+static SIMPLE_DEV_PM_OPS(exynos4_busfreq_pm_ops, NULL, exynos4_busfreq_resume);
static const struct platform_device_id exynos4_busfreq_id[] = {
{ "exynos4210-busfreq", TYPE_BUSF_EXYNOS4210 },
@@ -1106,12 +1030,12 @@ static const struct platform_device_id exynos4_busfreq_id[] = {
static struct platform_driver exynos4_busfreq_driver = {
.probe = exynos4_busfreq_probe,
- .remove = __devexit_p(exynos4_busfreq_remove),
+ .remove = exynos4_busfreq_remove,
.id_table = exynos4_busfreq_id,
.driver = {
.name = "exynos4-busfreq",
.owner = THIS_MODULE,
- .pm = &exynos4_busfreq_pm,
+ .pm = &exynos4_busfreq_pm_ops,
},
};
diff --git a/drivers/devfreq/exynos/exynos4_bus.h b/drivers/devfreq/exynos/exynos4_bus.h
new file mode 100644
index 00000000000..94c73c18d28
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos4_bus.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2013 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS4 BUS header
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#ifndef __DEVFREQ_EXYNOS4_BUS_H
+#define __DEVFREQ_EXYNOS4_BUS_H __FILE__
+
+#include <mach/map.h>
+
+#define EXYNOS4_CLKDIV_LEFTBUS (S5P_VA_CMU + 0x04500)
+#define EXYNOS4_CLKDIV_STAT_LEFTBUS (S5P_VA_CMU + 0x04600)
+
+#define EXYNOS4_CLKDIV_RIGHTBUS (S5P_VA_CMU + 0x08500)
+#define EXYNOS4_CLKDIV_STAT_RIGHTBUS (S5P_VA_CMU + 0x08600)
+
+#define EXYNOS4_CLKDIV_TOP (S5P_VA_CMU + 0x0C510)
+#define EXYNOS4_CLKDIV_CAM (S5P_VA_CMU + 0x0C520)
+#define EXYNOS4_CLKDIV_MFC (S5P_VA_CMU + 0x0C528)
+
+#define EXYNOS4_CLKDIV_STAT_TOP (S5P_VA_CMU + 0x0C610)
+#define EXYNOS4_CLKDIV_STAT_MFC (S5P_VA_CMU + 0x0C628)
+
+#define EXYNOS4210_CLKGATE_IP_IMAGE (S5P_VA_CMU + 0x0C930)
+#define EXYNOS4212_CLKGATE_IP_IMAGE (S5P_VA_CMU + 0x04930)
+
+#define EXYNOS4_CLKDIV_DMC0 (S5P_VA_CMU + 0x10500)
+#define EXYNOS4_CLKDIV_DMC1 (S5P_VA_CMU + 0x10504)
+#define EXYNOS4_CLKDIV_STAT_DMC0 (S5P_VA_CMU + 0x10600)
+#define EXYNOS4_CLKDIV_STAT_DMC1 (S5P_VA_CMU + 0x10604)
+
+#define EXYNOS4_DMC_PAUSE_CTRL (S5P_VA_CMU + 0x11094)
+#define EXYNOS4_DMC_PAUSE_ENABLE (1 << 0)
+
+#define EXYNOS4_CLKDIV_DMC0_ACP_SHIFT (0)
+#define EXYNOS4_CLKDIV_DMC0_ACP_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_ACP_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT (4)
+#define EXYNOS4_CLKDIV_DMC0_ACPPCLK_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_ACPPCLK_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT (8)
+#define EXYNOS4_CLKDIV_DMC0_DPHY_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DPHY_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_DMC_SHIFT (12)
+#define EXYNOS4_CLKDIV_DMC0_DMC_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMC_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT (16)
+#define EXYNOS4_CLKDIV_DMC0_DMCD_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMCD_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT (20)
+#define EXYNOS4_CLKDIV_DMC0_DMCP_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_DMCP_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT (24)
+#define EXYNOS4_CLKDIV_DMC0_COPY2_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_COPY2_SHIFT)
+#define EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT (28)
+#define EXYNOS4_CLKDIV_DMC0_CORETI_MASK (0x7 << EXYNOS4_CLKDIV_DMC0_CORETI_SHIFT)
+
+#define EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT (0)
+#define EXYNOS4_CLKDIV_DMC1_G2D_ACP_MASK (0xf << EXYNOS4_CLKDIV_DMC1_G2D_ACP_SHIFT)
+#define EXYNOS4_CLKDIV_DMC1_C2C_SHIFT (4)
+#define EXYNOS4_CLKDIV_DMC1_C2C_MASK (0x7 << EXYNOS4_CLKDIV_DMC1_C2C_SHIFT)
+#define EXYNOS4_CLKDIV_DMC1_PWI_SHIFT (8)
+#define EXYNOS4_CLKDIV_DMC1_PWI_MASK (0xf << EXYNOS4_CLKDIV_DMC1_PWI_SHIFT)
+#define EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT (12)
+#define EXYNOS4_CLKDIV_DMC1_C2CACLK_MASK (0x7 << EXYNOS4_CLKDIV_DMC1_C2CACLK_SHIFT)
+#define EXYNOS4_CLKDIV_DMC1_DVSEM_SHIFT (16)
+#define EXYNOS4_CLKDIV_DMC1_DVSEM_MASK (0x7f << EXYNOS4_CLKDIV_DMC1_DVSEM_SHIFT)
+#define EXYNOS4_CLKDIV_DMC1_DPM_SHIFT (24)
+#define EXYNOS4_CLKDIV_DMC1_DPM_MASK (0x7f << EXYNOS4_CLKDIV_DMC1_DPM_SHIFT)
+
+#define EXYNOS4_CLKDIV_MFC_SHIFT (0)
+#define EXYNOS4_CLKDIV_MFC_MASK (0x7 << EXYNOS4_CLKDIV_MFC_SHIFT)
+
+#define EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT (0)
+#define EXYNOS4_CLKDIV_TOP_ACLK200_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK200_SHIFT)
+#define EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT (4)
+#define EXYNOS4_CLKDIV_TOP_ACLK100_MASK (0xF << EXYNOS4_CLKDIV_TOP_ACLK100_SHIFT)
+#define EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT (8)
+#define EXYNOS4_CLKDIV_TOP_ACLK160_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK160_SHIFT)
+#define EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT (12)
+#define EXYNOS4_CLKDIV_TOP_ACLK133_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK133_SHIFT)
+#define EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT (16)
+#define EXYNOS4_CLKDIV_TOP_ONENAND_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ONENAND_SHIFT)
+#define EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT (20)
+#define EXYNOS4_CLKDIV_TOP_ACLK266_GPS_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK266_GPS_SHIFT)
+#define EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_SHIFT (24)
+#define EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_MASK (0x7 << EXYNOS4_CLKDIV_TOP_ACLK400_MCUISP_SHIFT)
+
+#define EXYNOS4_CLKDIV_BUS_GDLR_SHIFT (0)
+#define EXYNOS4_CLKDIV_BUS_GDLR_MASK (0x7 << EXYNOS4_CLKDIV_BUS_GDLR_SHIFT)
+#define EXYNOS4_CLKDIV_BUS_GPLR_SHIFT (4)
+#define EXYNOS4_CLKDIV_BUS_GPLR_MASK (0x7 << EXYNOS4_CLKDIV_BUS_GPLR_SHIFT)
+
+#define EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT (0)
+#define EXYNOS4_CLKDIV_CAM_FIMC0_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC0_SHIFT)
+#define EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT (4)
+#define EXYNOS4_CLKDIV_CAM_FIMC1_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC1_SHIFT)
+#define EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT (8)
+#define EXYNOS4_CLKDIV_CAM_FIMC2_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC2_SHIFT)
+#define EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT (12)
+#define EXYNOS4_CLKDIV_CAM_FIMC3_MASK (0xf << EXYNOS4_CLKDIV_CAM_FIMC3_SHIFT)
+
+#define EXYNOS4_CLKDIV_CAM1 (S5P_VA_CMU + 0x0C568)
+
+#define EXYNOS4_CLKDIV_STAT_CAM1 (S5P_VA_CMU + 0x0C668)
+
+#define EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT (0)
+#define EXYNOS4_CLKDIV_CAM1_JPEG_MASK (0xf << EXYNOS4_CLKDIV_CAM1_JPEG_SHIFT)
+
+#endif /* __DEVFREQ_EXYNOS4_BUS_H */
diff --git a/drivers/devfreq/exynos/exynos5_bus.c b/drivers/devfreq/exynos/exynos5_bus.c
new file mode 100644
index 00000000000..6cd0392e279
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos5_bus.c
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS5 INT clock frequency scaling support using DEVFREQ framework
+ * Based on work done by Jonghwan Choi <jhbird.choi@samsung.com>
+ * Support for only EXYNOS5250 is present.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/devfreq.h>
+#include <linux/io.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/pm_qos.h>
+#include <linux/regulator/consumer.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#include "exynos_ppmu.h"
+
+#define MAX_SAFEVOLT 1100000 /* 1.10V */
+/* Assume that the bus is saturated if the utilization is 25% */
+#define INT_BUS_SATURATION_RATIO 25
+
+enum int_level_idx {
+ LV_0,
+ LV_1,
+ LV_2,
+ LV_3,
+ LV_4,
+ _LV_END
+};
+
+enum exynos_ppmu_list {
+ PPMU_RIGHT,
+ PPMU_END,
+};
+
+struct busfreq_data_int {
+ struct device *dev;
+ struct devfreq *devfreq;
+ struct regulator *vdd_int;
+ struct busfreq_ppmu_data ppmu_data;
+ unsigned long curr_freq;
+ bool disabled;
+
+ struct notifier_block pm_notifier;
+ struct mutex lock;
+ struct pm_qos_request int_req;
+ struct clk *int_clk;
+};
+
+struct int_bus_opp_table {
+ unsigned int idx;
+ unsigned long clk;
+ unsigned long volt;
+};
+
+static struct int_bus_opp_table exynos5_int_opp_table[] = {
+ {LV_0, 266000, 1025000},
+ {LV_1, 200000, 1025000},
+ {LV_2, 160000, 1025000},
+ {LV_3, 133000, 1025000},
+ {LV_4, 100000, 1025000},
+ {0, 0, 0},
+};
+
+static int exynos5_int_setvolt(struct busfreq_data_int *data,
+ unsigned long volt)
+{
+ return regulator_set_voltage(data->vdd_int, volt, MAX_SAFEVOLT);
+}
+
+static int exynos5_busfreq_int_target(struct device *dev, unsigned long *_freq,
+ u32 flags)
+{
+ int err = 0;
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+ struct dev_pm_opp *opp;
+ unsigned long old_freq, freq;
+ unsigned long volt;
+
+ rcu_read_lock();
+ opp = devfreq_recommended_opp(dev, _freq, flags);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ dev_err(dev, "%s: Invalid OPP.\n", __func__);
+ return PTR_ERR(opp);
+ }
+
+ freq = dev_pm_opp_get_freq(opp);
+ volt = dev_pm_opp_get_voltage(opp);
+ rcu_read_unlock();
+
+ old_freq = data->curr_freq;
+
+ if (old_freq == freq)
+ return 0;
+
+ dev_dbg(dev, "targeting %lukHz %luuV\n", freq, volt);
+
+ mutex_lock(&data->lock);
+
+ if (data->disabled)
+ goto out;
+
+ if (freq > exynos5_int_opp_table[0].clk)
+ pm_qos_update_request(&data->int_req, freq * 16 / 1000);
+ else
+ pm_qos_update_request(&data->int_req, -1);
+
+ if (old_freq < freq)
+ err = exynos5_int_setvolt(data, volt);
+ if (err)
+ goto out;
+
+ err = clk_set_rate(data->int_clk, freq * 1000);
+
+ if (err)
+ goto out;
+
+ if (old_freq > freq)
+ err = exynos5_int_setvolt(data, volt);
+ if (err)
+ goto out;
+
+ data->curr_freq = freq;
+out:
+ mutex_unlock(&data->lock);
+ return err;
+}
+
+static int exynos5_int_get_dev_status(struct device *dev,
+ struct devfreq_dev_status *stat)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+ struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
+ int busier_dmc;
+
+ exynos_read_ppmu(ppmu_data);
+ busier_dmc = exynos_get_busier_ppmu(ppmu_data);
+
+ stat->current_frequency = data->curr_freq;
+
+ /* Number of cycles spent on memory access */
+ stat->busy_time = ppmu_data->ppmu[busier_dmc].count[PPMU_PMNCNT3];
+ stat->busy_time *= 100 / INT_BUS_SATURATION_RATIO;
+ stat->total_time = ppmu_data->ppmu[busier_dmc].ccnt;
+
+ return 0;
+}
+
+static struct devfreq_dev_profile exynos5_devfreq_int_profile = {
+ .initial_freq = 160000,
+ .polling_ms = 100,
+ .target = exynos5_busfreq_int_target,
+ .get_dev_status = exynos5_int_get_dev_status,
+};
+
+static int exynos5250_init_int_tables(struct busfreq_data_int *data)
+{
+ int i, err = 0;
+
+ for (i = LV_0; i < _LV_END; i++) {
+ err = dev_pm_opp_add(data->dev, exynos5_int_opp_table[i].clk,
+ exynos5_int_opp_table[i].volt);
+ if (err) {
+ dev_err(data->dev, "Cannot add opp entries.\n");
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+static int exynos5_busfreq_int_pm_notifier_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct busfreq_data_int *data = container_of(this,
+ struct busfreq_data_int, pm_notifier);
+ struct dev_pm_opp *opp;
+ unsigned long maxfreq = ULONG_MAX;
+ unsigned long freq;
+ unsigned long volt;
+ int err = 0;
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ /* Set Fastest and Deactivate DVFS */
+ mutex_lock(&data->lock);
+
+ data->disabled = true;
+
+ rcu_read_lock();
+ opp = dev_pm_opp_find_freq_floor(data->dev, &maxfreq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ err = PTR_ERR(opp);
+ goto unlock;
+ }
+ freq = dev_pm_opp_get_freq(opp);
+ volt = dev_pm_opp_get_voltage(opp);
+ rcu_read_unlock();
+
+ err = exynos5_int_setvolt(data, volt);
+ if (err)
+ goto unlock;
+
+ err = clk_set_rate(data->int_clk, freq * 1000);
+
+ if (err)
+ goto unlock;
+
+ data->curr_freq = freq;
+unlock:
+ mutex_unlock(&data->lock);
+ if (err)
+ return NOTIFY_BAD;
+ return NOTIFY_OK;
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ /* Reactivate */
+ mutex_lock(&data->lock);
+ data->disabled = false;
+ mutex_unlock(&data->lock);
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int exynos5_busfreq_int_probe(struct platform_device *pdev)
+{
+ struct busfreq_data_int *data;
+ struct busfreq_ppmu_data *ppmu_data;
+ struct dev_pm_opp *opp;
+ struct device *dev = &pdev->dev;
+ struct device_node *np;
+ unsigned long initial_freq;
+ unsigned long initial_volt;
+ int err = 0;
+ int i;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(struct busfreq_data_int),
+ GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ ppmu_data = &data->ppmu_data;
+ ppmu_data->ppmu_end = PPMU_END;
+ ppmu_data->ppmu = devm_kzalloc(dev,
+ sizeof(struct exynos_ppmu) * PPMU_END,
+ GFP_KERNEL);
+ if (!ppmu_data->ppmu) {
+ dev_err(dev, "Failed to allocate memory for exynos_ppmu\n");
+ return -ENOMEM;
+ }
+
+ np = of_find_compatible_node(NULL, NULL, "samsung,exynos5250-ppmu");
+ if (np == NULL) {
+ pr_err("Unable to find PPMU node\n");
+ return -ENOENT;
+ }
+
+ for (i = 0; i < ppmu_data->ppmu_end; i++) {
+ /* map PPMU memory region */
+ ppmu_data->ppmu[i].hw_base = of_iomap(np, i);
+ if (ppmu_data->ppmu[i].hw_base == NULL) {
+ dev_err(&pdev->dev, "failed to map memory region\n");
+ return -ENOMEM;
+ }
+ }
+ data->pm_notifier.notifier_call = exynos5_busfreq_int_pm_notifier_event;
+ data->dev = dev;
+ mutex_init(&data->lock);
+
+ err = exynos5250_init_int_tables(data);
+ if (err)
+ return err;
+
+ data->vdd_int = devm_regulator_get(dev, "vdd_int");
+ if (IS_ERR(data->vdd_int)) {
+ dev_err(dev, "Cannot get the regulator \"vdd_int\"\n");
+ return PTR_ERR(data->vdd_int);
+ }
+
+ data->int_clk = devm_clk_get(dev, "int_clk");
+ if (IS_ERR(data->int_clk)) {
+ dev_err(dev, "Cannot get clock \"int_clk\"\n");
+ return PTR_ERR(data->int_clk);
+ }
+
+ rcu_read_lock();
+ opp = dev_pm_opp_find_freq_floor(dev,
+ &exynos5_devfreq_int_profile.initial_freq);
+ if (IS_ERR(opp)) {
+ rcu_read_unlock();
+ dev_err(dev, "Invalid initial frequency %lu kHz.\n",
+ exynos5_devfreq_int_profile.initial_freq);
+ return PTR_ERR(opp);
+ }
+ initial_freq = dev_pm_opp_get_freq(opp);
+ initial_volt = dev_pm_opp_get_voltage(opp);
+ rcu_read_unlock();
+ data->curr_freq = initial_freq;
+
+ err = clk_set_rate(data->int_clk, initial_freq * 1000);
+ if (err) {
+ dev_err(dev, "Failed to set initial frequency\n");
+ return err;
+ }
+
+ err = exynos5_int_setvolt(data, initial_volt);
+ if (err)
+ return err;
+
+ platform_set_drvdata(pdev, data);
+
+ busfreq_mon_reset(ppmu_data);
+
+ data->devfreq = devm_devfreq_add_device(dev, &exynos5_devfreq_int_profile,
+ "simple_ondemand", NULL);
+ if (IS_ERR(data->devfreq))
+ return PTR_ERR(data->devfreq);
+
+ err = devm_devfreq_register_opp_notifier(dev, data->devfreq);
+ if (err < 0) {
+ dev_err(dev, "Failed to register opp notifier\n");
+ return err;
+ }
+
+ err = register_pm_notifier(&data->pm_notifier);
+ if (err) {
+ dev_err(dev, "Failed to setup pm notifier\n");
+ return err;
+ }
+
+ /* TODO: Add a new QOS class for int/mif bus */
+ pm_qos_add_request(&data->int_req, PM_QOS_NETWORK_THROUGHPUT, -1);
+
+ return 0;
+}
+
+static int exynos5_busfreq_int_remove(struct platform_device *pdev)
+{
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+
+ pm_qos_remove_request(&data->int_req);
+ unregister_pm_notifier(&data->pm_notifier);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int exynos5_busfreq_int_resume(struct device *dev)
+{
+ struct platform_device *pdev = container_of(dev, struct platform_device,
+ dev);
+ struct busfreq_data_int *data = platform_get_drvdata(pdev);
+ struct busfreq_ppmu_data *ppmu_data = &data->ppmu_data;
+
+ busfreq_mon_reset(ppmu_data);
+ return 0;
+}
+static const struct dev_pm_ops exynos5_busfreq_int_pm = {
+ .resume = exynos5_busfreq_int_resume,
+};
+#endif
+static SIMPLE_DEV_PM_OPS(exynos5_busfreq_int_pm_ops, NULL,
+ exynos5_busfreq_int_resume);
+
+/* platform device pointer for exynos5 devfreq device. */
+static struct platform_device *exynos5_devfreq_pdev;
+
+static struct platform_driver exynos5_busfreq_int_driver = {
+ .probe = exynos5_busfreq_int_probe,
+ .remove = exynos5_busfreq_int_remove,
+ .driver = {
+ .name = "exynos5-bus-int",
+ .owner = THIS_MODULE,
+ .pm = &exynos5_busfreq_int_pm_ops,
+ },
+};
+
+static int __init exynos5_busfreq_int_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&exynos5_busfreq_int_driver);
+ if (ret < 0)
+ goto out;
+
+ exynos5_devfreq_pdev =
+ platform_device_register_simple("exynos5-bus-int", -1, NULL, 0);
+ if (IS_ERR(exynos5_devfreq_pdev)) {
+ ret = PTR_ERR(exynos5_devfreq_pdev);
+ goto out1;
+ }
+
+ return 0;
+out1:
+ platform_driver_unregister(&exynos5_busfreq_int_driver);
+out:
+ return ret;
+}
+late_initcall(exynos5_busfreq_int_init);
+
+static void __exit exynos5_busfreq_int_exit(void)
+{
+ platform_device_unregister(exynos5_devfreq_pdev);
+ platform_driver_unregister(&exynos5_busfreq_int_driver);
+}
+module_exit(exynos5_busfreq_int_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("EXYNOS5 busfreq driver with devfreq framework");
diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c
new file mode 100644
index 00000000000..75fcc5140ff
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos_ppmu.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS - PPMU support
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/io.h>
+
+#include "exynos_ppmu.h"
+
+void exynos_ppmu_reset(void __iomem *ppmu_base)
+{
+ __raw_writel(PPMU_CYCLE_RESET | PPMU_COUNTER_RESET, ppmu_base);
+ __raw_writel(PPMU_ENABLE_CYCLE |
+ PPMU_ENABLE_COUNT0 |
+ PPMU_ENABLE_COUNT1 |
+ PPMU_ENABLE_COUNT2 |
+ PPMU_ENABLE_COUNT3,
+ ppmu_base + PPMU_CNTENS);
+}
+
+void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
+ unsigned int evt)
+{
+ __raw_writel(evt, ppmu_base + PPMU_BEVTSEL(ch));
+}
+
+void exynos_ppmu_start(void __iomem *ppmu_base)
+{
+ __raw_writel(PPMU_ENABLE, ppmu_base);
+}
+
+void exynos_ppmu_stop(void __iomem *ppmu_base)
+{
+ __raw_writel(PPMU_DISABLE, ppmu_base);
+}
+
+unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch)
+{
+ unsigned int total;
+
+ if (ch == PPMU_PMNCNT3)
+ total = ((__raw_readl(ppmu_base + PMCNT_OFFSET(ch)) << 8) |
+ __raw_readl(ppmu_base + PMCNT_OFFSET(ch + 1)));
+ else
+ total = __raw_readl(ppmu_base + PMCNT_OFFSET(ch));
+
+ return total;
+}
+
+void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data)
+{
+ unsigned int i;
+
+ for (i = 0; i < ppmu_data->ppmu_end; i++) {
+ void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base;
+
+ /* Reset the performance and cycle counters */
+ exynos_ppmu_reset(ppmu_base);
+
+ /* Setup count registers to monitor read/write transactions */
+ ppmu_data->ppmu[i].event[PPMU_PMNCNT3] = RDWR_DATA_COUNT;
+ exynos_ppmu_setevent(ppmu_base, PPMU_PMNCNT3,
+ ppmu_data->ppmu[i].event[PPMU_PMNCNT3]);
+
+ exynos_ppmu_start(ppmu_base);
+ }
+}
+
+void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data)
+{
+ int i, j;
+
+ for (i = 0; i < ppmu_data->ppmu_end; i++) {
+ void __iomem *ppmu_base = ppmu_data->ppmu[i].hw_base;
+
+ exynos_ppmu_stop(ppmu_base);
+
+ /* Update local data from PPMU */
+ ppmu_data->ppmu[i].ccnt = __raw_readl(ppmu_base + PPMU_CCNT);
+
+ for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
+ if (ppmu_data->ppmu[i].event[j] == 0)
+ ppmu_data->ppmu[i].count[j] = 0;
+ else
+ ppmu_data->ppmu[i].count[j] =
+ exynos_ppmu_read(ppmu_base, j);
+ }
+ }
+
+ busfreq_mon_reset(ppmu_data);
+}
+
+int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data)
+{
+ unsigned int count = 0;
+ int i, j, busy = 0;
+
+ for (i = 0; i < ppmu_data->ppmu_end; i++) {
+ for (j = PPMU_PMNCNT0; j < PPMU_PMNCNT_MAX; j++) {
+ if (ppmu_data->ppmu[i].count[j] > count) {
+ count = ppmu_data->ppmu[i].count[j];
+ busy = i;
+ }
+ }
+ }
+
+ return busy;
+}
diff --git a/drivers/devfreq/exynos/exynos_ppmu.h b/drivers/devfreq/exynos/exynos_ppmu.h
new file mode 100644
index 00000000000..71f17ba3563
--- /dev/null
+++ b/drivers/devfreq/exynos/exynos_ppmu.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS PPMU header
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#ifndef __DEVFREQ_EXYNOS_PPMU_H
+#define __DEVFREQ_EXYNOS_PPMU_H __FILE__
+
+#include <linux/ktime.h>
+
+/* For PPMU Control */
+#define PPMU_ENABLE BIT(0)
+#define PPMU_DISABLE 0x0
+#define PPMU_CYCLE_RESET BIT(1)
+#define PPMU_COUNTER_RESET BIT(2)
+
+#define PPMU_ENABLE_COUNT0 BIT(0)
+#define PPMU_ENABLE_COUNT1 BIT(1)
+#define PPMU_ENABLE_COUNT2 BIT(2)
+#define PPMU_ENABLE_COUNT3 BIT(3)
+#define PPMU_ENABLE_CYCLE BIT(31)
+
+#define PPMU_CNTENS 0x10
+#define PPMU_FLAG 0x50
+#define PPMU_CCNT_OVERFLOW BIT(31)
+#define PPMU_CCNT 0x100
+
+#define PPMU_PMCNT0 0x110
+#define PPMU_PMCNT_OFFSET 0x10
+#define PMCNT_OFFSET(x) (PPMU_PMCNT0 + (PPMU_PMCNT_OFFSET * x))
+
+#define PPMU_BEVT0SEL 0x1000
+#define PPMU_BEVTSEL_OFFSET 0x100
+#define PPMU_BEVTSEL(x) (PPMU_BEVT0SEL + (ch * PPMU_BEVTSEL_OFFSET))
+
+/* For Event Selection */
+#define RD_DATA_COUNT 0x5
+#define WR_DATA_COUNT 0x6
+#define RDWR_DATA_COUNT 0x7
+
+enum ppmu_counter {
+ PPMU_PMNCNT0,
+ PPMU_PMCCNT1,
+ PPMU_PMNCNT2,
+ PPMU_PMNCNT3,
+ PPMU_PMNCNT_MAX,
+};
+
+struct bus_opp_table {
+ unsigned int idx;
+ unsigned long clk;
+ unsigned long volt;
+};
+
+struct exynos_ppmu {
+ void __iomem *hw_base;
+ unsigned int ccnt;
+ unsigned int event[PPMU_PMNCNT_MAX];
+ unsigned int count[PPMU_PMNCNT_MAX];
+ unsigned long long ns;
+ ktime_t reset_time;
+ bool ccnt_overflow;
+ bool count_overflow[PPMU_PMNCNT_MAX];
+};
+
+struct busfreq_ppmu_data {
+ struct exynos_ppmu *ppmu;
+ int ppmu_end;
+};
+
+void exynos_ppmu_reset(void __iomem *ppmu_base);
+void exynos_ppmu_setevent(void __iomem *ppmu_base, unsigned int ch,
+ unsigned int evt);
+void exynos_ppmu_start(void __iomem *ppmu_base);
+void exynos_ppmu_stop(void __iomem *ppmu_base);
+unsigned int exynos_ppmu_read(void __iomem *ppmu_base, unsigned int ch);
+void busfreq_mon_reset(struct busfreq_ppmu_data *ppmu_data);
+void exynos_read_ppmu(struct busfreq_ppmu_data *ppmu_data);
+int exynos_get_busier_ppmu(struct busfreq_ppmu_data *ppmu_data);
+#endif /* __DEVFREQ_EXYNOS_PPMU_H */
diff --git a/drivers/devfreq/governor.h b/drivers/devfreq/governor.h
index ea7f13c58de..fad7d632197 100644
--- a/drivers/devfreq/governor.h
+++ b/drivers/devfreq/governor.h
@@ -18,7 +18,24 @@
#define to_devfreq(DEV) container_of((DEV), struct devfreq, dev)
+/* Devfreq events */
+#define DEVFREQ_GOV_START 0x1
+#define DEVFREQ_GOV_STOP 0x2
+#define DEVFREQ_GOV_INTERVAL 0x3
+#define DEVFREQ_GOV_SUSPEND 0x4
+#define DEVFREQ_GOV_RESUME 0x5
+
/* Caution: devfreq->lock must be locked before calling update_devfreq */
extern int update_devfreq(struct devfreq *devfreq);
+extern void devfreq_monitor_start(struct devfreq *devfreq);
+extern void devfreq_monitor_stop(struct devfreq *devfreq);
+extern void devfreq_monitor_suspend(struct devfreq *devfreq);
+extern void devfreq_monitor_resume(struct devfreq *devfreq);
+extern void devfreq_interval_update(struct devfreq *devfreq,
+ unsigned int *delay);
+
+extern int devfreq_add_governor(struct devfreq_governor *governor);
+extern int devfreq_remove_governor(struct devfreq_governor *governor);
+
#endif /* _GOVERNOR_H */
diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c
index af75ddd4f15..c72f942f30a 100644
--- a/drivers/devfreq/governor_performance.c
+++ b/drivers/devfreq/governor_performance.c
@@ -10,6 +10,7 @@
*/
#include <linux/devfreq.h>
+#include <linux/module.h>
#include "governor.h"
static int devfreq_performance_func(struct devfreq *df,
@@ -26,14 +27,41 @@ static int devfreq_performance_func(struct devfreq *df,
return 0;
}
-static int performance_init(struct devfreq *devfreq)
+static int devfreq_performance_handler(struct devfreq *devfreq,
+ unsigned int event, void *data)
{
- return update_devfreq(devfreq);
+ int ret = 0;
+
+ if (event == DEVFREQ_GOV_START) {
+ mutex_lock(&devfreq->lock);
+ ret = update_devfreq(devfreq);
+ mutex_unlock(&devfreq->lock);
+ }
+
+ return ret;
}
-const struct devfreq_governor devfreq_performance = {
+static struct devfreq_governor devfreq_performance = {
.name = "performance",
- .init = performance_init,
.get_target_freq = devfreq_performance_func,
- .no_central_polling = true,
+ .event_handler = devfreq_performance_handler,
};
+
+static int __init devfreq_performance_init(void)
+{
+ return devfreq_add_governor(&devfreq_performance);
+}
+subsys_initcall(devfreq_performance_init);
+
+static void __exit devfreq_performance_exit(void)
+{
+ int ret;
+
+ ret = devfreq_remove_governor(&devfreq_performance);
+ if (ret)
+ pr_err("%s: failed remove governor %d\n", __func__, ret);
+
+ return;
+}
+module_exit(devfreq_performance_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c
index fec0cdbd247..0c6bed567e6 100644
--- a/drivers/devfreq/governor_powersave.c
+++ b/drivers/devfreq/governor_powersave.c
@@ -10,6 +10,7 @@
*/
#include <linux/devfreq.h>
+#include <linux/module.h>
#include "governor.h"
static int devfreq_powersave_func(struct devfreq *df,
@@ -23,14 +24,41 @@ static int devfreq_powersave_func(struct devfreq *df,
return 0;
}
-static int powersave_init(struct devfreq *devfreq)
+static int devfreq_powersave_handler(struct devfreq *devfreq,
+ unsigned int event, void *data)
{
- return update_devfreq(devfreq);
+ int ret = 0;
+
+ if (event == DEVFREQ_GOV_START) {
+ mutex_lock(&devfreq->lock);
+ ret = update_devfreq(devfreq);
+ mutex_unlock(&devfreq->lock);
+ }
+
+ return ret;
}
-const struct devfreq_governor devfreq_powersave = {
+static struct devfreq_governor devfreq_powersave = {
.name = "powersave",
- .init = powersave_init,
.get_target_freq = devfreq_powersave_func,
- .no_central_polling = true,
+ .event_handler = devfreq_powersave_handler,
};
+
+static int __init devfreq_powersave_init(void)
+{
+ return devfreq_add_governor(&devfreq_powersave);
+}
+subsys_initcall(devfreq_powersave_init);
+
+static void __exit devfreq_powersave_exit(void)
+{
+ int ret;
+
+ ret = devfreq_remove_governor(&devfreq_powersave);
+ if (ret)
+ pr_err("%s: failed remove governor %d\n", __func__, ret);
+
+ return;
+}
+module_exit(devfreq_powersave_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/devfreq/governor_simpleondemand.c b/drivers/devfreq/governor_simpleondemand.c
index a2e3eae7901..0720ba84ca9 100644
--- a/drivers/devfreq/governor_simpleondemand.c
+++ b/drivers/devfreq/governor_simpleondemand.c
@@ -10,8 +10,10 @@
*/
#include <linux/errno.h>
+#include <linux/module.h>
#include <linux/devfreq.h>
#include <linux/math64.h>
+#include "governor.h"
/* Default constants for DevFreq-Simple-Ondemand (DFSO) */
#define DFSO_UPTHRESHOLD (90)
@@ -88,7 +90,58 @@ static int devfreq_simple_ondemand_func(struct devfreq *df,
return 0;
}
-const struct devfreq_governor devfreq_simple_ondemand = {
+static int devfreq_simple_ondemand_handler(struct devfreq *devfreq,
+ unsigned int event, void *data)
+{
+ switch (event) {
+ case DEVFREQ_GOV_START:
+ devfreq_monitor_start(devfreq);
+ break;
+
+ case DEVFREQ_GOV_STOP:
+ devfreq_monitor_stop(devfreq);
+ break;
+
+ case DEVFREQ_GOV_INTERVAL:
+ devfreq_interval_update(devfreq, (unsigned int *)data);
+ break;
+
+ case DEVFREQ_GOV_SUSPEND:
+ devfreq_monitor_suspend(devfreq);
+ break;
+
+ case DEVFREQ_GOV_RESUME:
+ devfreq_monitor_resume(devfreq);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct devfreq_governor devfreq_simple_ondemand = {
.name = "simple_ondemand",
.get_target_freq = devfreq_simple_ondemand_func,
+ .event_handler = devfreq_simple_ondemand_handler,
};
+
+static int __init devfreq_simple_ondemand_init(void)
+{
+ return devfreq_add_governor(&devfreq_simple_ondemand);
+}
+subsys_initcall(devfreq_simple_ondemand_init);
+
+static void __exit devfreq_simple_ondemand_exit(void)
+{
+ int ret;
+
+ ret = devfreq_remove_governor(&devfreq_simple_ondemand);
+ if (ret)
+ pr_err("%s: failed remove governor %d\n", __func__, ret);
+
+ return;
+}
+module_exit(devfreq_simple_ondemand_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/devfreq/governor_userspace.c b/drivers/devfreq/governor_userspace.c
index 0681246fc89..35de6e83c1f 100644
--- a/drivers/devfreq/governor_userspace.c
+++ b/drivers/devfreq/governor_userspace.c
@@ -14,6 +14,7 @@
#include <linux/devfreq.h>
#include <linux/pm.h>
#include <linux/mutex.h>
+#include <linux/module.h>
#include "governor.h"
struct userspace_data {
@@ -116,10 +117,46 @@ static void userspace_exit(struct devfreq *devfreq)
devfreq->data = NULL;
}
-const struct devfreq_governor devfreq_userspace = {
+static int devfreq_userspace_handler(struct devfreq *devfreq,
+ unsigned int event, void *data)
+{
+ int ret = 0;
+
+ switch (event) {
+ case DEVFREQ_GOV_START:
+ ret = userspace_init(devfreq);
+ break;
+ case DEVFREQ_GOV_STOP:
+ userspace_exit(devfreq);
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static struct devfreq_governor devfreq_userspace = {
.name = "userspace",
.get_target_freq = devfreq_userspace_func,
- .init = userspace_init,
- .exit = userspace_exit,
- .no_central_polling = true,
+ .event_handler = devfreq_userspace_handler,
};
+
+static int __init devfreq_userspace_init(void)
+{
+ return devfreq_add_governor(&devfreq_userspace);
+}
+subsys_initcall(devfreq_userspace_init);
+
+static void __exit devfreq_userspace_exit(void)
+{
+ int ret;
+
+ ret = devfreq_remove_governor(&devfreq_userspace);
+ if (ret)
+ pr_err("%s: failed remove governor %d\n", __func__, ret);
+
+ return;
+}
+module_exit(devfreq_userspace_exit);
+MODULE_LICENSE("GPL");