#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
#include <linux/devfreq.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include "governor.h"
+#ifdef CONFIG_ARM_EXYNOS_DEVFREQ
+#include <soc/samsung/exynos-devfreq.h>
+
+#ifdef CONFIG_EXYNOS_DVFS_MANAGER
+#include <soc/samsung/exynos-dm.h>
+#endif
+
+#endif
+
static struct class *devfreq_class;
/*
*/
int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
{
- int lev, prev_lev, ret = 0;
+ int lev, prev_lev = 0, ret = 0;
unsigned long cur_time;
cur_time = jiffies;
goto out;
}
- if (lev != prev_lev) {
+ if (freq != devfreq->previous_freq) {
+ prev_lev = devfreq_get_freq_level(devfreq,
+ devfreq->previous_freq);
+ if (prev_lev && prev_lev < 0) {
+ pr_err("DEVFREQ: invalid index to update status\n");
+ return -EINVAL;
+ }
devfreq->trans_table[(prev_lev *
devfreq->profile->max_state) + lev]++;
devfreq->total_trans++;
return ERR_PTR(-ENODEV);
}
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+static int devfreq_frequency_scaler(int dm_type, void *devdata,
+ u32 target_freq, unsigned int relation)
+{
+ struct device *dev;
+ struct devfreq *devfreq;
+ unsigned long freq = target_freq;
+ u32 flags = 0;
+ int err = 0;
+
+ dev = find_exynos_devfreq_device(devdata);
+ if (IS_ERR(dev)) {
+ pr_err("%s: No such devfreq device for dm_type(%d)\n", __func__, dm_type);
+ err = -ENODEV;
+ 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: No such devfreq for the device\n", __func__);
+ err = -ENODEV;
+ goto err_out;
+ }
+
+ /*
+ * Adjust the freuqency with user freq and QoS.
+ *
+ * List from the highest proiority
+ * max_freq (probably called by thermal when it's too hot)
+ * min_freq
+ */
+
+ if (devfreq->min_freq && freq < devfreq->min_freq) {
+ freq = devfreq->min_freq;
+ flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use GLB */
+ }
+ if (devfreq->max_freq && freq > devfreq->max_freq) {
+ freq = devfreq->max_freq;
+ flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; /* Use LUB */
+ }
+
+ err = devfreq->profile->target(devfreq->dev.parent, &freq, flags);
+ 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;
+
+err_out:
+ return err;
+}
+#endif
+
+#if !defined(CONFIG_EXYNOS_DVFS_MANAGER) || !defined(CONFIG_ARM_EXYNOS_DEVFREQ)
static int devfreq_notify_transition(struct devfreq *devfreq,
struct devfreq_freqs *freqs, unsigned int state)
{
return 0;
}
+#endif
/* Load monitoring helper functions for governors use */
*/
int update_devfreq(struct devfreq *devfreq)
{
- struct devfreq_freqs freqs;
- unsigned long freq, cur_freq;
+ unsigned long freq;
int err = 0;
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ int dm_type;
+ unsigned long pm_qos_max;
+#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_INTERACTIVE)
+ struct devfreq_simple_interactive_data *gov_data = devfreq->data;
+#endif
+#else
u32 flags = 0;
+ unsigned long cur_freq;
+ struct devfreq_freqs freqs;
+#endif
if (!mutex_is_locked(&devfreq->lock)) {
WARN(true, "devfreq->lock must be locked by the caller.\n");
if (err)
return err;
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ err = find_exynos_devfreq_dm_type(devfreq->dev.parent, &dm_type);
+ if (err)
+ return -EINVAL;
+
+ pm_qos_max = devfreq->max_freq;
+
+#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_INTERACTIVE)
+ if (!strcmp(devfreq->governor->name, "interactive") && gov_data->pm_qos_class_max)
+ pm_qos_max = (unsigned long)pm_qos_request(gov_data->pm_qos_class_max);
+#endif
+ if (devfreq->str_freq)
+ policy_update_call_to_DM(dm_type, devfreq->str_freq,
+ devfreq->str_freq);
+ else
+ policy_update_call_to_DM(dm_type, freq, pm_qos_max);
+ DM_CALL(dm_type, &freq);
+#else
/*
* Adjust the frequency with user freq and QoS.
*
"Couldn't update frequency transition information.\n");
devfreq->previous_freq = freq;
+#endif
return err;
}
EXPORT_SYMBOL(update_devfreq);
mutex_lock(&devfreq->lock);
err = update_devfreq(devfreq);
- if (err)
+ if (err && err != -EAGAIN)
dev_err(&devfreq->dev, "dvfs failed with (%d) error\n", err);
-
+#ifdef CONFIG_SCHED_HMP
+ mod_delayed_work_on(0, devfreq_wq, &devfreq->work,
+ msecs_to_jiffies(devfreq->profile->polling_ms));
+#else
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
+#endif
mutex_unlock(&devfreq->lock);
}
*/
void devfreq_monitor_start(struct devfreq *devfreq)
{
- INIT_DEFERRABLE_WORK(&devfreq->work, devfreq_monitor);
+ INIT_DELAYED_WORK(&devfreq->work, devfreq_monitor);
if (devfreq->profile->polling_ms)
queue_delayed_work(devfreq_wq, &devfreq->work,
msecs_to_jiffies(devfreq->profile->polling_ms));
{
struct devfreq *devfreq;
struct devfreq_governor *governor;
- static atomic_t devfreq_no = ATOMIC_INIT(-1);
int err = 0;
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ int dm_type;
+#endif
if (!dev || !profile || !governor_name) {
dev_err(dev, "%s: Invalid parameters.\n", __func__);
mutex_lock(&devfreq->lock);
}
- dev_set_name(&devfreq->dev, "devfreq%d",
- atomic_inc_return(&devfreq_no));
+ dev_set_name(&devfreq->dev, "%s", dev_name(dev));
err = device_register(&devfreq->dev);
if (err) {
mutex_unlock(&devfreq->lock);
mutex_unlock(&devfreq->lock);
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ err = find_exynos_devfreq_dm_type(dev, &dm_type);
+ if (err)
+ goto err_dm_type;
+
+ err = register_exynos_dm_freq_scaler(dm_type, devfreq_frequency_scaler);
+ if (err)
+ goto err_dm_scaler;
+#endif
+
mutex_lock(&devfreq_list_lock);
list_add(&devfreq->node, &devfreq_list);
mutex_unlock(&devfreq_list_lock);
return devfreq;
-
err_init:
list_del(&devfreq->node);
mutex_unlock(&devfreq_list_lock);
-
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ unregister_exynos_dm_freq_scaler(dm_type);
+err_dm_scaler:
+err_dm_type:
+#endif
device_unregister(&devfreq->dev);
err_dev:
if (devfreq)
*/
int devfreq_remove_device(struct devfreq *devfreq)
{
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ int dm_type;
+ int err = 0;
+#endif
if (!devfreq)
return -EINVAL;
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER) && defined(CONFIG_ARM_EXYNOS_DEVFREQ)
+ err = find_exynos_devfreq_dm_type(devfreq->dev.parent, &dm_type);
+ if (err)
+ return err;
+
+ unregister_exynos_dm_freq_scaler(dm_type);
+#endif
device_unregister(&devfreq->dev);
return 0;
}
static DEVICE_ATTR_RW(polling_interval);
-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);
- unsigned long value;
- int ret;
- unsigned long max;
-
- ret = sscanf(buf, "%lu", &value);
- if (ret != 1)
- return -EINVAL;
-
- mutex_lock(&df->lock);
- max = df->max_freq;
- if (value && max && value > max) {
- ret = -EINVAL;
- goto unlock;
- }
-
- df->min_freq = value;
- update_devfreq(df);
- ret = count;
-unlock:
- mutex_unlock(&df->lock);
- return ret;
-}
-
-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);
- unsigned long value;
- int ret;
- unsigned long min;
-
- ret = sscanf(buf, "%lu", &value);
- if (ret != 1)
- return -EINVAL;
-
- mutex_lock(&df->lock);
- min = df->min_freq;
- if (value && min && value < min) {
- ret = -EINVAL;
- goto unlock;
- }
-
- df->max_freq = value;
- update_devfreq(df);
- ret = count;
-unlock:
- mutex_unlock(&df->lock);
- return ret;
-}
-
#define show_one(name) \
static ssize_t name##_show \
(struct device *dev, struct device_attribute *attr, char *buf) \
show_one(min_freq);
show_one(max_freq);
-static DEVICE_ATTR_RW(min_freq);
-static DEVICE_ATTR_RW(max_freq);
+static DEVICE_ATTR_RO(min_freq);
+static DEVICE_ATTR_RO(max_freq);
static ssize_t available_frequencies_show(struct device *d,
struct device_attribute *attr,
}
static DEVICE_ATTR_RO(trans_stat);
+static ssize_t time_in_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct devfreq *devfreq = to_devfreq(dev);
+ ssize_t len = 0;
+ int i, err;
+ unsigned int max_state = devfreq->profile->max_state;
+
+ err = devfreq_update_status(devfreq, devfreq->previous_freq);
+ if (err)
+ return 0;
+
+ for (i = 0; i < max_state; i++) {
+ len += sprintf(buf + len, "%8lu",
+ devfreq->profile->freq_table[i]);
+ len += sprintf(buf + len, "%10u\n",
+ jiffies_to_msecs(devfreq->time_in_state[i]));
+ }
+ return len;
+}
+static DEVICE_ATTR_RO(time_in_state);
+
static struct attribute *devfreq_attrs[] = {
&dev_attr_governor.attr,
&dev_attr_available_governors.attr,
&dev_attr_min_freq.attr,
&dev_attr_max_freq.attr,
&dev_attr_trans_stat.attr,
+ &dev_attr_time_in_state.attr,
NULL,
};
ATTRIBUTE_GROUPS(devfreq);