diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/devfreq/governor_ondemand.c | |
Diffstat (limited to 'drivers/devfreq/governor_ondemand.c')
| -rw-r--r-- | drivers/devfreq/governor_ondemand.c | 729 |
1 files changed, 729 insertions, 0 deletions
diff --git a/drivers/devfreq/governor_ondemand.c b/drivers/devfreq/governor_ondemand.c new file mode 100644 index 00000000..ac358bae --- /dev/null +++ b/drivers/devfreq/governor_ondemand.c @@ -0,0 +1,729 @@ +/* + * Copyright (C) 2013 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/devfreq.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/timer.h> +#include <linux/earlysuspend.h> +#include "governor.h" + +/* Default constants for DevFreq-Ondemand (DFO) */ +#define DFO_UPTHRESHOLD (80) +#define DFO_DOWNDIFFERENCTIAL (30) + +/* +* TODO: add kernel space requests +*/ +#ifdef CONFIG_SPRD_SCX35_DMC_FREQ +extern void devfreq_min_freq_cnt_reset(unsigned int, unsigned int); +extern int devfreq_request_ignore(void); +#endif + +static void dfs_req_timer_timeout(unsigned long arg); +#define REQ_TIMEOUT_DEF (HZ/20); +static DEFINE_SPINLOCK(dfs_req_lock); +static unsigned int dfs_req_timeout; +static DEFINE_TIMER(dfs_req_timer, dfs_req_timer_timeout, 0, 0); +struct dfs_request_state{ + int req_sum; /* in KHz */ + int req_timeout; /* in KHz */ + int req_quirk; /* in KHz */ + u32 ddr_freq_after_req; /* in KHz */ +}; +static struct dfs_request_state user_requests; +static struct devfreq *g_devfreq; /* for requests from kernel */ +static int gov_eb = 1; +struct userspace_data { + int req_bw; + unsigned long set_freq; + unsigned long set_count; + unsigned long upthreshold; + unsigned long downdifferential; + unsigned long (*convert_bw_to_freq)(u32 req_bw); + bool enable; /*sysfs only*/ + bool devfreq_enable; +}; + +/************ kernel interface *****************/ +/* +* get dfs enable flag +* enabled == 0 --> dfs disable +* enabled == 1 --> dfs enable +*/ +bool dfs_get_enable(void) +{ + struct userspace_data *user_data; + bool enabled; + + if(g_devfreq && g_devfreq->data){ + user_data = (struct userspace_data *)(g_devfreq->data); + enabled = user_data->enable; + pr_debug("%s, enabled = %d, user_data->enable = %d\n", __func__, enabled, user_data->enable); + } + + return enabled; +} +EXPORT_SYMBOL(dfs_get_enable); +/* +* set ddr frequnecy +* @freq: KHz +* if ddr frequency is set through this function, DVS is disabled +*/ +int dfs_set_freq(int freq) +{ + struct userspace_data *user_data; + int err; + + if(freq < 0){ + err = -1; + pr_debug("*** %s,freq < 0\n",__func__); + goto done; + } + + user_data = (struct userspace_data *)(g_devfreq->data); + mutex_lock(&g_devfreq->lock); + if(user_data){ + if(freq > 0){ + user_data->set_count++; + user_data->devfreq_enable = false; + if(freq > user_data->set_freq) + user_data->set_freq = freq; + }else{ + if(user_data->set_count > 0){ + user_data->set_count--; + if(user_data->set_count == 0){ + user_data->set_freq = 0; + user_data->devfreq_enable = true; + } + } + } + pr_debug("*** %s, set freq:%d KHz, set_count:%lu ***\n", __func__, freq, user_data->set_count ); + } + else + { + pr_debug("*** %s,user_data == 0\n",__func__); + } + err = update_devfreq(g_devfreq); + mutex_unlock(&g_devfreq->lock); +done: + return err; +} + +/* +* add a new ddr bandwidth request. +* @req_bw: KB +* + addition(add>=0) or - subtraction(add<0) +*/ +void dfs_request_bw(int req_bw) +{ + u32 req_freq = 0; + int add = 1; + struct userspace_data *user_data; + + if (req_bw < 0) { + req_bw = -req_bw; + add = -1; + } + + if(g_devfreq && g_devfreq->data){ + user_data = (struct userspace_data *)(g_devfreq->data); + if(user_data->convert_bw_to_freq){ + req_freq = (user_data->convert_bw_to_freq)(req_bw); + } + } + pr_debug("*** %s, pid:%u, %creq_bw:%u, req_freq:%u ***\n", + __func__, current->pid, add>=0?'+':'-', req_bw, req_freq ); + if(req_freq){ + mutex_lock(&g_devfreq->lock); + if(add >= 0) + user_requests.req_sum += req_freq; + else + user_requests.req_sum -= req_freq; + if(user_requests.req_sum < 0) + user_requests.req_sum = 0; + update_devfreq(g_devfreq); + mutex_unlock(&g_devfreq->lock); + } +} + +/* +* set request timer timeout +* @timeout, ms +*/ +void dfs_req_set_timeout(unsigned int timeout) +{ + spin_lock(&dfs_req_lock); + dfs_req_timeout = msecs_to_jiffies(timeout); + spin_unlock(&dfs_req_lock); +} +EXPORT_SYMBOL(dfs_req_set_timeout); + +/* +* get request timer timeout +* @return, ms +*/ +unsigned int dfs_req_get_timeout(void) +{ + unsigned int timeout; + + spin_lock(&dfs_req_lock); + timeout = dfs_req_timeout; + spin_unlock(&dfs_req_lock); + + timeout = jiffies_to_msecs(timeout); + return timeout; +} +EXPORT_SYMBOL(dfs_req_get_timeout); + +static void dfs_req_timer_timeout(unsigned long arg) +{ + spin_lock(&dfs_req_lock); + user_requests.req_timeout = 0; + spin_unlock(&dfs_req_lock); + return; +} + +/* +* add a new ddr bandwidth request. when time is up, request is cleared automatically +* @req_bw, KB +*/ +void dfs_request_bw_timeout(unsigned int req_bw) +{ + struct userspace_data *user_data; + unsigned int req_freq; + + req_freq = 0; + if(req_bw == 0) + return; + + spin_lock(&dfs_req_lock); + if( user_requests.req_timeout ){ + spin_unlock(&dfs_req_lock); + pr_debug("*** %s, ignore, req_timeout:%d ***\n", __func__, user_requests.req_timeout); + return; + } + spin_unlock(&dfs_req_lock); + + if(g_devfreq && g_devfreq->data){ + user_data = (struct userspace_data *)(g_devfreq->data); + if(user_data->convert_bw_to_freq){ + req_freq = (user_data->convert_bw_to_freq)(req_bw); + printk("*** %s, req_freq:%d ***\n", __func__, req_freq ); + } + } + spin_lock(&dfs_req_lock); + user_requests.req_timeout = req_freq; + spin_unlock(&dfs_req_lock); + + if(req_freq) + mod_timer(&dfs_req_timer, jiffies+dfs_req_timeout); + else + del_timer_sync(&dfs_req_timer); + + if(req_freq){ + mutex_lock(&g_devfreq->lock); + update_devfreq(g_devfreq); + mutex_unlock(&g_devfreq->lock); + } +} +EXPORT_SYMBOL(dfs_request_bw_timeout); + +/* +* raise ddr frequency up temporarily +* @req_bw, KB +*/ +#ifdef CONFIG_SPRD_SCX35_DMC_FREQ +void dfs_freq_raise_quirk(unsigned int req_bw) +{ + if(req_bw == 0) + return; + + spin_lock(&dfs_req_lock); + if(user_requests.req_quirk || devfreq_request_ignore() ){ + spin_unlock(&dfs_req_lock); + return; + } + user_requests.req_quirk = req_bw; + spin_unlock(&dfs_req_lock); + + mutex_lock(&g_devfreq->lock); + devfreq_min_freq_cnt_reset(-1, 1); + update_devfreq(g_devfreq); + devfreq_min_freq_cnt_reset(-1, 0); + user_requests.req_quirk = 0; + mutex_unlock(&g_devfreq->lock); +} +EXPORT_SYMBOL(dfs_freq_raise_quirk); +#endif + +/************ early suspend *****************/ + +static void devfreq_early_suspend(struct early_suspend *h) +{ +#ifdef CONFIG_ARCH_SCX15 + dfs_set_freq(192000); +#else +#ifdef CONFIG_ARCH_SCX35 + dfs_set_freq(200000); +#endif +#endif + gov_eb = 0; +} + +static void devfreq_late_resume(struct early_suspend *h) +{ + dfs_set_freq(0); +} + +static struct early_suspend devfreq_early_suspend_desc = { + .level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 100, + .suspend = devfreq_early_suspend, + .resume = devfreq_late_resume, +}; + +static void devfreq_enable_late_resume(struct early_suspend *h) +{ + gov_eb = 1; +} +static struct early_suspend devfreq_enable_desc = { + .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN, + .resume = devfreq_enable_late_resume, +}; +/************ userspace interface *****************/ + +static ssize_t store_upthreshold(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + + + mutex_lock(&devfreq->lock); + data = devfreq->data; + sscanf(buf, "%lu", &wanted); + if(data) + data->upthreshold = wanted; + mutex_unlock(&devfreq->lock); + return count; +} + +static ssize_t show_upthreshold(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + if(data){ + err = sprintf(buf, "%lu\n", data->upthreshold); + }else + err = sprintf(buf, "%d\n", DFO_UPTHRESHOLD); + mutex_unlock(&devfreq->lock); + return err; +} + +static ssize_t store_downdifferential(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + + + mutex_lock(&devfreq->lock); + data = devfreq->data; + sscanf(buf, "%lu", &wanted); + if(data) + data->downdifferential = wanted; + mutex_unlock(&devfreq->lock); + return count; +} + +static ssize_t show_downdifferential(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + if(data){ + err = sprintf(buf, "%lu\n", data->downdifferential); + }else{ + err = sprintf(buf, "%d\n", DFO_DOWNDIFFERENCTIAL); + } + mutex_unlock(&devfreq->lock); + return err; +} + + +static ssize_t store_request(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int wanted; + int req_freq; + int err = 0; + + req_freq = 0; + mutex_lock(&devfreq->lock); + data = devfreq->data; + sscanf(buf, "%d", &wanted); + if(data){ + data->req_bw += wanted; + pr_debug("*** %s, request:%d, total request:%d ***\n", + __func__, wanted, data->req_bw); + if(data->req_bw < 0) + data->req_bw = 0; + if(data->convert_bw_to_freq) + req_freq = data->convert_bw_to_freq(wanted); + } + user_requests.req_sum += req_freq; + if(user_requests.req_sum < 0) + user_requests.req_sum = 0; + err = update_devfreq(devfreq); + if (err == 0) + err = count; + mutex_unlock(&devfreq->lock); + return err; + +} + +static ssize_t show_request(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + if(data) + err = sprintf(buf, "%d KB\n", data->req_bw); + mutex_unlock(&devfreq->lock); + return err; +} + +static ssize_t store_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + sscanf(buf, "%lu", &wanted); + if(data){ + data->enable = wanted; + } + mutex_unlock(&devfreq->lock); + return count; +} + +static ssize_t show_enable(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + if(data) + err = sprintf(buf, "%d \n", data->enable); + mutex_unlock(&devfreq->lock); + return err; +} + +static ssize_t store_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + unsigned long wanted; + int err = 0; + + sscanf(buf, "%lu", &wanted); + err = dfs_set_freq(wanted); + if (err == 0) + err = count; + return err; +} + +static ssize_t show_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct devfreq *devfreq = to_devfreq(dev); + struct userspace_data *data; + int err = 0; + + mutex_lock(&devfreq->lock); + data = devfreq->data; + if(data) + err = sprintf(buf, "%lu KHz, set count:%lu\n", data->set_freq, data->set_count); + mutex_unlock(&devfreq->lock); + return err; +} + +static DEVICE_ATTR(set_freq, 0644, show_freq, store_freq); +static DEVICE_ATTR(set_enable, 0644, show_enable, store_enable); +static DEVICE_ATTR(set_request, 0644, show_request, store_request); +static DEVICE_ATTR(set_upthreshold, 0644, show_upthreshold, store_upthreshold); +static DEVICE_ATTR(set_downdifferential, 0644, show_downdifferential, store_downdifferential); +static struct attribute *dev_entries[] = { + &dev_attr_set_freq.attr, + &dev_attr_set_enable.attr, + &dev_attr_set_request.attr, + &dev_attr_set_upthreshold.attr, + &dev_attr_set_downdifferential.attr, + NULL, +}; +static struct attribute_group dev_attr_group = { + .name = "ondemand", + .attrs = dev_entries, +}; + +static int devfreq_ondemand_start(struct devfreq *devfreq) +{ + int err = 0; + struct userspace_data *data = kzalloc(sizeof(struct userspace_data), + GFP_KERNEL); + + if (!data) { + err = -ENOMEM; + goto out; + } + data->req_bw = 0; + data->set_freq = 0; + data->upthreshold = DFO_UPTHRESHOLD; + data->downdifferential = DFO_DOWNDIFFERENCTIAL; +#if defined(CONFIG_ARCH_SCX30G) + data->enable = false; +#else + data->enable = true; +#endif + data->devfreq_enable = true; + if(devfreq->data){ + data->convert_bw_to_freq = devfreq->data; + pr_info("*** %s, data->convert_bw_to_freq:%pf ***\n", __func__, data->convert_bw_to_freq); + } + devfreq->data = data; + g_devfreq = devfreq; + err = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group); + register_early_suspend(&devfreq_early_suspend_desc); + /* + * disable DFS before DISPC late resume + */ + register_early_suspend(&devfreq_enable_desc); + + spin_lock(&dfs_req_lock); + dfs_req_timeout = REQ_TIMEOUT_DEF; + spin_unlock(&dfs_req_lock); +out: + return err; +} + +static int devfreq_ondemand_stop(struct devfreq *devfreq) +{ + int err = 0; + if(devfreq->data){ + kfree(devfreq->data); + } + + return err; + +} +/************ userspace interface *****************/ + +static int devfreq_ondemand_func(struct devfreq *df, + unsigned long *freq) +{ + struct devfreq_dev_status stat; + int err = df->profile->get_dev_status(df->dev.parent, &stat); + unsigned long long a, b; + unsigned int dfso_upthreshold = DFO_UPTHRESHOLD; + unsigned int dfso_downdifferential = DFO_DOWNDIFFERENCTIAL; + struct userspace_data *data = df->data; + unsigned long max = (df->max_freq) ? df->max_freq : UINT_MAX; + unsigned long req_freq; + + if (err) + return err; + + if(user_requests.req_quirk && + stat.current_frequency==df->min_freq){ + /* + * QUIRK: only set frequency larger than minimum + */ + *freq = df->min_freq + 1; + pr_debug("*** %s, req_quirk, freq:%lu, return ***\n", __func__, *freq); + return 0; + } + /* + * TODO: add request frequency + */ + req_freq = user_requests.req_sum + user_requests.req_timeout; + + if (data) { + if (data->enable==false || !(data->devfreq_enable) || + data->set_freq || !gov_eb){ + if(user_requests.ddr_freq_after_req == 0) + user_requests.ddr_freq_after_req = max; + *freq = (data->set_freq?data->set_freq:user_requests.ddr_freq_after_req); + pr_debug("*** %s, data->enable:%d, data->set_freq:%u, gov_eb:%d ***\n", + __func__, data->enable, data->set_freq, gov_eb ); + return 0; + } + if (data->upthreshold) + dfso_upthreshold = data->upthreshold; + if (data->downdifferential) + dfso_downdifferential = data->downdifferential; + }else{ + printk("*** %s, data is NULL ***\n", __func__ ); + } + + if (dfso_upthreshold > 100 || + dfso_upthreshold < dfso_downdifferential){ + printk("*** %s, dfso_upthreshold:%u, dfso_downdifferential:%u ***\n", + __func__, dfso_upthreshold, dfso_downdifferential ); + return -EINVAL; + } + + /* Assume MAX if it is going to be divided by zero */ + if (stat.total_time == 0) { + *freq = max; + user_requests.ddr_freq_after_req = *freq; + pr_debug("*** %s, stat.total_time == 0, freq:%lu ***\n", __func__, *freq); + return 0; + } + + /* Prevent overflow */ + if (stat.busy_time >= (1 << 24) || stat.total_time >= (1 << 24)) { + stat.busy_time >>= 7; + stat.total_time >>= 7; + } + + /* Set MAX if it's busy enough */ + if (stat.busy_time * 100 > + stat.total_time * dfso_upthreshold) { + *freq = max; + user_requests.ddr_freq_after_req = *freq; + pr_debug("*** %s, set max freq:%lu ***\n", __func__, *freq); + return 0; + } + + /* Set MAX if we do not know the initial frequency */ + if (stat.current_frequency == 0) { + *freq = max; + user_requests.ddr_freq_after_req = *freq; + pr_debug("*** %s, stat.current_frequency == 0, freq:%lu ***\n", __func__, *freq); + return 0; + } + + /* Keep the current frequency */ + if (stat.busy_time * 100 > + stat.total_time * (dfso_upthreshold - dfso_downdifferential)) { + *freq = stat.current_frequency + req_freq; + user_requests.ddr_freq_after_req = *freq; + pr_debug("*** %s, Keep the current frequency %lu, req_freq:%lu ***\n", + __func__, stat.current_frequency, req_freq); + return 0; + } + + /* Set the desired frequency based on the load */ + a = stat.busy_time; + a *= stat.current_frequency; + b = div_u64(a, stat.total_time); + b *= 100; + b = div_u64(b, (dfso_upthreshold - dfso_downdifferential / 2)); + *freq = (unsigned long) b + req_freq; + user_requests.ddr_freq_after_req = *freq; + pr_debug("*** %s, calculate freq:%lu, req_freq:%lu ***\n", + __func__, (unsigned long)b, req_freq); + + if (df->min_freq && *freq < df->min_freq) + *freq = df->min_freq; + if (df->max_freq && *freq > df->max_freq) + *freq = df->max_freq; + + return 0; +} + +static int devfreq_ondemand_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + switch (event) { + case DEVFREQ_GOV_START: + devfreq_ondemand_start(devfreq); + devfreq_monitor_start(devfreq); + break; + + case DEVFREQ_GOV_STOP: + devfreq_monitor_stop(devfreq); + //devfreq_ondemand_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; +} + +const struct devfreq_governor devfreq_ondemand = { + .name = "ondemand", + .get_target_freq = devfreq_ondemand_func, + .event_handler = devfreq_ondemand_handler, +}; + +static int __init devfreq_ondemand_init(void) +{ + return devfreq_add_governor(&devfreq_ondemand); +} +subsys_initcall(devfreq_ondemand_init); + +static void __exit devfreq_ondemand_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_ondemand); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_ondemand_exit); +MODULE_LICENSE("GPL"); |
