diff --git a/drivers/devfreq/governor_wmark_active.c b/drivers/devfreq/governor_wmark_active.c new file mode 100644 index 00000000..c617271f --- /dev/null +++ b/drivers/devfreq/governor_wmark_active.c @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct wmark_gov_info { + /* probed from the devfreq */ + unsigned long *freqlist; + int freq_count; + + /* algorithm parameters */ + unsigned int p_load_target; + unsigned int p_load_max; + + /* common data */ + struct devfreq *df; + struct platform_device *pdev; + struct dentry *debugdir; +}; + +static unsigned long freqlist_up(struct wmark_gov_info *wmarkinfo, + unsigned long curr_freq) +{ + int i, pos; + + for (i = 0; i < wmarkinfo->freq_count; i++) + if (wmarkinfo->freqlist[i] > curr_freq) + break; + + pos = min(wmarkinfo->freq_count - 1, i); + + return wmarkinfo->freqlist[pos]; +} + +static unsigned long freqlist_down(struct wmark_gov_info *wmarkinfo, + unsigned long curr_freq) +{ + int i, pos; + + for (i = wmarkinfo->freq_count - 1; i >= 0; i--) + if (wmarkinfo->freqlist[i] < curr_freq) + break; + + pos = max(0, i); + return wmarkinfo->freqlist[pos]; +} + +static unsigned long freqlist_round(struct wmark_gov_info *wmarkinfo, + unsigned long freq) +{ + int i, pos; + + for (i = 0; i < wmarkinfo->freq_count; i++) + if (wmarkinfo->freqlist[i] >= freq) + break; + + pos = min(wmarkinfo->freq_count - 1, i); + return wmarkinfo->freqlist[pos]; +} + +static void update_watermarks(struct devfreq *df, + unsigned long current_frequency) +{ + struct wmark_gov_info *wmarkinfo = df->data; + unsigned long long relation = 0, next_freq = 0; + unsigned long long current_frequency_khz = current_frequency / 1000; + + if (current_frequency == wmarkinfo->freqlist[0]) { + /* disable the low watermark if we are at lowest clock */ + df->profile->set_low_wmark(df->dev.parent, 0); + } else { + /* calculate the low threshold; what is the load value + * at which we would go into lower frequency given the + * that we are running at the new frequency? */ + next_freq = freqlist_down(wmarkinfo, current_frequency); + relation = ((next_freq / current_frequency_khz) * + wmarkinfo->p_load_target) / 1000; + df->profile->set_low_wmark(df->dev.parent, relation); + } + + if (current_frequency == + wmarkinfo->freqlist[wmarkinfo->freq_count - 1]) { + /* disable the high watermark if we are at highest clock */ + df->profile->set_high_wmark(df->dev.parent, 1000); + } else { + /* calculate the high threshold; what is the load value + * at which we would go into highest frequency given the + * that we are running at the new frequency? */ + next_freq = freqlist_up(wmarkinfo, current_frequency); + relation = ((next_freq / current_frequency_khz) * + wmarkinfo->p_load_target) / 1000; + relation = min((unsigned long long)wmarkinfo->p_load_max, + relation); + df->profile->set_high_wmark(df->dev.parent, relation); + } + +} + +static int devfreq_watermark_target_freq(struct devfreq *df, + unsigned long *freq) +{ + struct wmark_gov_info *wmarkinfo = df->data; + struct devfreq_dev_status dev_stat; + unsigned long long load, relation, next_freq; + int err; + + err = df->profile->get_dev_status(df->dev.parent, &dev_stat); + if (err < 0) + return err; + + /* keep current frequency if we do not have proper data available */ + if (!dev_stat.total_time) { + *freq = dev_stat.current_frequency; + return 0; + } + + /* calculate first load and relation load/p_load_target */ + load = (dev_stat.busy_time * 1000) / dev_stat.total_time; + + /* if we cross load max... */ + if (load >= wmarkinfo->p_load_max) { + /* we go directly to the highest frequency. depending + * on frequency table we might never go higher than + * the current frequency (i.e. load should be over 100% + * to make relation push to the next frequency). */ + *freq = wmarkinfo->freqlist[wmarkinfo->freq_count - 1]; + } else { + /* otherwise, based on relation between current load and + * load target we calculate the "ideal" frequency + * where we would be just at the target */ + relation = (load * 1000) / wmarkinfo->p_load_target; + next_freq = relation * (dev_stat.current_frequency / 1000); + + /* round this frequency */ + *freq = freqlist_round(wmarkinfo, next_freq); + } + + /* update watermarks to match with the new frequency */ + update_watermarks(df, *freq); + + return 0; +} + +static void devfreq_watermark_debug_start(struct devfreq *df) +{ + struct wmark_gov_info *wmarkinfo = df->data; + struct dentry *f; + char dirname[128]; + + snprintf(dirname, sizeof(dirname), "%s_scaling", + to_platform_device(df->dev.parent)->name); + + if (!wmarkinfo) + return; + + wmarkinfo->debugdir = debugfs_create_dir(dirname, NULL); + if (!wmarkinfo->debugdir) { + pr_warn("cannot create debugfs directory\n"); + return; + } + +#define CREATE_DBG_FILE(fname) \ + do {\ + f = debugfs_create_u32(#fname, S_IRUGO | S_IWUSR, \ + wmarkinfo->debugdir, &wmarkinfo->p_##fname); \ + if (NULL == f) { \ + pr_warn("cannot create debug entry " #fname "\n"); \ + return; \ + } \ + } while (0) + + CREATE_DBG_FILE(load_target); + CREATE_DBG_FILE(load_max); +#undef CREATE_DBG_FILE + +} + +static void devfreq_watermark_debug_stop(struct devfreq *df) +{ + struct wmark_gov_info *wmarkinfo = df->data; + debugfs_remove_recursive(wmarkinfo->debugdir); +} + +static int devfreq_watermark_start(struct devfreq *df) +{ + struct wmark_gov_info *wmarkinfo; + struct platform_device *pdev = to_platform_device(df->dev.parent); + + if (!df->profile->freq_table) { + dev_err(&pdev->dev, "Frequency table missing\n"); + return -EINVAL; + } + + wmarkinfo = kzalloc(sizeof(struct wmark_gov_info), GFP_KERNEL); + if (!wmarkinfo) + return -ENOMEM; + + df->data = (void *)wmarkinfo; + wmarkinfo->freqlist = df->profile->freq_table; + wmarkinfo->freq_count = df->profile->max_state; + wmarkinfo->p_load_target = 700; + wmarkinfo->p_load_max = 900; + wmarkinfo->df = df; + wmarkinfo->pdev = pdev; + + devfreq_watermark_debug_start(df); + + return 0; +} + +static int devfreq_watermark_event_handler(struct devfreq *df, + unsigned int event, void *wmark_type) +{ + int ret = 0; + struct wmark_gov_info *wmarkinfo = df->data; + + switch (event) { + case DEVFREQ_GOV_START: + devfreq_watermark_start(df); + wmarkinfo = df->data; + update_watermarks(df, wmarkinfo->freqlist[0]); + break; + case DEVFREQ_GOV_STOP: + devfreq_watermark_debug_stop(df); + break; + case DEVFREQ_GOV_SUSPEND: + devfreq_monitor_suspend(df); + break; + + case DEVFREQ_GOV_RESUME: + wmarkinfo = df->data; + update_watermarks(df, wmarkinfo->freqlist[0]); + devfreq_monitor_resume(df); + break; + + case DEVFREQ_GOV_WMARK: + mutex_lock(&df->lock); + update_devfreq(df); + mutex_unlock(&df->lock); + break; + + default: + break; + } + + return ret; +} + +static struct devfreq_governor devfreq_watermark_active = { + .name = "wmark_active", + .get_target_freq = devfreq_watermark_target_freq, + .event_handler = devfreq_watermark_event_handler, +}; + + +static int __init devfreq_watermark_init(void) +{ + return devfreq_add_governor(&devfreq_watermark_active); +} + +static void __exit devfreq_watermark_exit(void) +{ + devfreq_remove_governor(&devfreq_watermark_active); +} + +rootfs_initcall(devfreq_watermark_init); +module_exit(devfreq_watermark_exit);