diff options
Diffstat (limited to 'drivers/edp')
-rw-r--r-- | drivers/edp/Kconfig | 9 | ||||
-rw-r--r-- | drivers/edp/Makefile | 27 | ||||
-rw-r--r-- | drivers/edp/sysedp.c | 207 | ||||
-rw-r--r-- | drivers/edp/sysedp_batmon_calc.c | 269 | ||||
-rw-r--r-- | drivers/edp/sysedp_debug.c | 84 | ||||
-rw-r--r-- | drivers/edp/sysedp_dynamic_capping.c | 530 | ||||
-rw-r--r-- | drivers/edp/sysedp_internal.h | 49 | ||||
-rw-r--r-- | drivers/edp/sysedp_sysfs.c | 349 |
8 files changed, 1514 insertions, 10 deletions
diff --git a/drivers/edp/Kconfig b/drivers/edp/Kconfig index 9e837076b223..15368879eaa0 100644 --- a/drivers/edp/Kconfig +++ b/drivers/edp/Kconfig @@ -5,5 +5,14 @@ config EDP_FRAMEWORK default n help EDP-framework implements peak current management +endmenu + +menu "SYSEDP Framework" +config SYSEDP_FRAMEWORK + bool "System EDP framework" + default n + help + SYSEDP-framework implements system peak current management + depends on !EDP_FRAMEWORK endmenu diff --git a/drivers/edp/Makefile b/drivers/edp/Makefile index a3799cb07293..c4aa90c234c1 100644 --- a/drivers/edp/Makefile +++ b/drivers/edp/Makefile @@ -1,12 +1,19 @@ GCOV_PROFILE := y -obj-y += edp.o -obj-y += edp_bestfit.o -obj-$(CONFIG_DEBUG_FS) += edp_debug.o -obj-y += edp_fair.o -obj-y += edp_overage.o -obj-y += edp_priority.o -obj-y += edp_temporal.o -obj-y += edp_sysfs.o -obj-y += psy_depletion.o -obj-y += tegra_core.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp_bestfit.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp_fair.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp_overage.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp_priority.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp_temporal.o +obj-$(CONFIG_EDP_FRAMEWORK) += edp_sysfs.o +obj-$(CONFIG_EDP_FRAMEWORK) += psy_depletion.o +obj-$(CONFIG_EDP_FRAMEWORK) += tegra_core.o +obj-$(CONFIG_SYSEDP_FRAMEWORK) += sysedp.o +obj-$(CONFIG_SYSEDP_FRAMEWORK) += sysedp_sysfs.o +obj-$(CONFIG_SYSEDP_FRAMEWORK) += sysedp_dynamic_capping.o +obj-$(CONFIG_SYSEDP_FRAMEWORK) += sysedp_batmon_calc.o +ifdef CONFIG_DEBUG_FS +obj-$(CONFIG_EDP_FRAMEWORK) += edp_debug.o +obj-$(CONFIG_SYSEDP_FRAMEWORK) += sysedp_debug.o +endif
\ No newline at end of file diff --git a/drivers/edp/sysedp.c b/drivers/edp/sysedp.c new file mode 100644 index 000000000000..55ce8ea97ca2 --- /dev/null +++ b/drivers/edp/sysedp.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/notifier.h> +#include <linux/sysedp.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/slab.h> +#define CREATE_TRACE_POINTS +#include <trace/events/sysedp.h> + +#include "sysedp_internal.h" + +DEFINE_MUTEX(sysedp_lock); +LIST_HEAD(registered_consumers); +static struct sysedp_platform_data *pdata; +unsigned int avail_budget = 1000000; +int margin; + +void sysedp_set_avail_budget(unsigned int power) +{ + mutex_lock(&sysedp_lock); + if (avail_budget != power) { + trace_sysedp_set_avail_budget(avail_budget, power); + avail_budget = power; + _sysedp_refresh(); + } + mutex_unlock(&sysedp_lock); +} + +void _sysedp_refresh(void) +{ + struct sysedp_consumer *p; + int limit; + int consumer_sum = 0; + + list_for_each_entry(p, ®istered_consumers, link) { + consumer_sum += _cur_level(p); + } + limit = (int)avail_budget - (int)consumer_sum - margin; + limit = limit >= 0 ? limit : 0; + sysedp_set_dynamic_cap((unsigned int)limit); +} + +struct sysedp_consumer *sysedp_get_consumer(const char *name) +{ + struct sysedp_consumer *p; + struct sysedp_consumer *match = NULL; + + mutex_lock(&sysedp_lock); + list_for_each_entry(p, ®istered_consumers, link) { + if (!strncmp(p->name, name, SYSEDP_NAME_LEN)) { + match = p; + break; + } + } + mutex_unlock(&sysedp_lock); + + return match; +} + +int sysedp_register_consumer(struct sysedp_consumer *consumer) +{ + int r; + + if (!consumer) + return -EINVAL; + + r = sysedp_consumer_add_kobject(consumer); + if (r) + return r; + + mutex_lock(&sysedp_lock); + list_add_tail(&consumer->link, ®istered_consumers); + _sysedp_refresh(); + mutex_unlock(&sysedp_lock); + return 0; +} +EXPORT_SYMBOL(sysedp_register_consumer); + +void sysedp_unregister_consumer(struct sysedp_consumer *consumer) +{ + if (!consumer) + return; + + mutex_lock(&sysedp_lock); + list_del(&consumer->link); + _sysedp_refresh(); + mutex_unlock(&sysedp_lock); + sysedp_consumer_remove_kobject(consumer); +} +EXPORT_SYMBOL(sysedp_unregister_consumer); + +void sysedp_free_consumer(struct sysedp_consumer *consumer) +{ + if (consumer) { + sysedp_unregister_consumer(consumer); + kfree(consumer); + } +} +EXPORT_SYMBOL(sysedp_free_consumer); + +static struct sysedp_consumer_data *sysedp_find_consumer_data(const char *name) +{ + unsigned int i; + struct sysedp_consumer_data *match = NULL; + + if (!pdata || !pdata->consumer_data) + return NULL; + + for (i = 0; i < pdata->consumer_data_size; i++) { + match = &pdata->consumer_data[i]; + if (!strcmp(match->name, name)) + break; + match = NULL; + } + return match; +} + +struct sysedp_consumer *sysedp_create_consumer(const char *specname, + const char *consumername) +{ + struct sysedp_consumer *consumer; + struct sysedp_consumer_data *match; + + match = sysedp_find_consumer_data(specname); + if (!match) { + pr_info("sysedp_create_consumer: unable to create %s, no consumer_data for %s found", + consumername, specname); + return NULL; + } + + consumer = kzalloc(sizeof(*consumer), GFP_KERNEL); + if (!consumer) + return NULL; + + strncpy(consumer->name, consumername, SYSEDP_NAME_LEN-1); + consumer->name[SYSEDP_NAME_LEN-1] = 0; + consumer->states = match->states; + consumer->num_states = match->num_states; + + if (sysedp_register_consumer(consumer)) { + kfree(consumer); + return NULL; + } + + return consumer; +} +EXPORT_SYMBOL(sysedp_create_consumer); + +void sysedp_set_state(struct sysedp_consumer *consumer, unsigned int new_state) +{ + if (!consumer) + return; + + mutex_lock(&sysedp_lock); + if (consumer->state != new_state) { + trace_sysedp_change_state(consumer->name, consumer->state, + new_state); + consumer->state = clamp_t(unsigned int, new_state, 0, + consumer->num_states-1); + _sysedp_refresh(); + } + mutex_unlock(&sysedp_lock); +} +EXPORT_SYMBOL(sysedp_set_state); + +static int sysedp_probe(struct platform_device *pdev) +{ + pdata = pdev->dev.platform_data; + if (!pdata) + return -EINVAL; + + margin = pdata->margin; + sysedp_init_sysfs(); + sysedp_init_debugfs(); + return 0; +} + +static struct platform_driver sysedp_driver = { + .probe = sysedp_probe, + .driver = { + .owner = THIS_MODULE, + .name = "sysedp" + } +}; + +static __init int sysedp_init(void) +{ + return platform_driver_register(&sysedp_driver); +} +pure_initcall(sysedp_init); diff --git a/drivers/edp/sysedp_batmon_calc.c b/drivers/edp/sysedp_batmon_calc.c new file mode 100644 index 000000000000..9855a28acdf2 --- /dev/null +++ b/drivers/edp/sysedp_batmon_calc.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>. + */ + +#include <linux/sysedp.h> +#include <linux/edpdev.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/workqueue.h> +#include <linux/suspend.h> +#include <linux/debugfs.h> +#include "sysedp_internal.h" + +#define UPDATE_INTERVAL 60000 + +static struct sysedp_batmon_calc_platform_data *pdata; +static struct delayed_work work; +static struct power_supply *psy; +int (*get_ocv)(unsigned int capacity); + +static int psy_get_property(enum power_supply_property psp, int *val) +{ + union power_supply_propval pv; + + if (psy->get_property(psy, psp, &pv)) + return -EFAULT; + if (val) + *val = pv.intval; + return 0; +} + +static int psy_ocv_from_chip(unsigned int capacity) +{ + int val; + if (psy_get_property(POWER_SUPPLY_PROP_VOLTAGE_OCV, &val)) + return pdata->vsys_min; + return val; +} + +static int psy_capacity(void) +{ + int val; + if (psy_get_property(POWER_SUPPLY_PROP_CAPACITY, &val)) + return 0; + return val; +} + +static int psy_temp(void) +{ + int val; + + if (psy_get_property(POWER_SUPPLY_PROP_TEMP, &val)) + return 25; + return val; +} + +/* Given two points (x1, y1) and (x2, y2), find the y coord of x */ +static int interpolate(int x, int x1, int y1, int x2, int y2) +{ + if (x1 == x2) + return y1; + return (y2 * (x - x1) - y1 * (x - x2)) / (x2 - x1); +} + +static int psy_ocv_from_lut(unsigned int capacity) +{ + struct sysedp_batmon_ocv_lut *p; + struct sysedp_batmon_ocv_lut *q; + + p = pdata->ocv_lut; + + while (p->capacity > capacity) + p++; + + if (p == pdata->ocv_lut) + return p->ocv; + + q = p - 1; + + return interpolate(capacity, p->capacity, p->ocv, q->capacity, + q->ocv); +} + +/* Calc ESR for current capacity (SOC) */ +static s64 calc_esr(unsigned int capacity) +{ + struct sysedp_batmon_rbat_lut *p; + struct sysedp_batmon_rbat_lut *q; + int esr; + + esr = pdata->r_const; + p = pdata->rbat_lut; + if (!p) + return esr; + + while (p->capacity > capacity) + p++; + + if (p == pdata->rbat_lut) + return esr + p->rbat; + + q = p - 1; + + esr += interpolate(capacity, p->capacity, p->rbat, + q->capacity, q->rbat); + return esr; +} + +/* calculate maximum allowed current (in mA) limited by equivalent + * series resistance (esr) */ +static s64 calc_ibat_esr(s64 ocv, s64 esr) +{ + if (ocv <= pdata->vsys_min) + return 0; + else + return div64_s64(1000 * (ocv - pdata->vsys_min), esr); +} + +/* Calc IBAT for a given temperature */ +static int calc_ibat(unsigned int temp) +{ + struct sysedp_batmon_ibat_lut *p; + struct sysedp_batmon_ibat_lut *q; + int ibat; + + p = pdata->ibat_lut; + while (p->ibat && p->temp > temp) + p++; + + if (p == pdata->ibat_lut || !p->ibat) + return p->ibat; + + q = p - 1; + ibat = interpolate(temp, p->temp, p->ibat, q->temp, q->ibat); + + return ibat; +} + +static s64 calc_pbat(s64 ocv, s64 ibat, s64 esr) +{ + s64 vsys; + vsys = ocv - div64_s64(ibat * esr, 1000); + return div64_s64(vsys * ibat, 1000000); +} + +static unsigned int calc_avail_budget(void) +{ + unsigned int capacity; + s64 ocv; + s64 esr; + s64 ibat_esr; + s64 ibat; + s64 ibat_max; + s64 pbat; + + capacity = psy_capacity(); + ocv = get_ocv(capacity); + esr = calc_esr(capacity); + + ibat_esr = calc_ibat_esr(ocv, esr); + ibat = calc_ibat(psy_temp()); + ibat_max = min(ibat_esr, ibat); + + pbat = calc_pbat(ocv, ibat_max, esr); + + pr_debug("capacity : %u\n", capacity); + pr_debug("ocv : %lld\n", ocv); + pr_debug("esr : %lld\n", esr); + pr_debug("ibat_esr : %lld\n", ibat_esr); + pr_debug("ibat : %lld\n", ibat); + pr_debug("ibat_max : %lld\n", ibat_max); + pr_debug("pbat : %lld\n", pbat); + + return pbat; +} + +static void batmon_update(struct work_struct *work) +{ + unsigned int budget; + unsigned int update_interval; + budget = calc_avail_budget(); + sysedp_set_avail_budget(budget); + + update_interval = pdata->update_interval ?: UPDATE_INTERVAL; + + schedule_delayed_work(to_delayed_work(work), + msecs_to_jiffies(update_interval)); +} + +static void batmon_shutdown(struct platform_device *pdev) +{ + cancel_delayed_work_sync(&work); +} + +static int batmon_suspend(struct platform_device *pdev, pm_message_t state) +{ + batmon_shutdown(pdev); + return 0; +} + +static int batmon_resume(struct platform_device *pdev) +{ + schedule_delayed_work(&work, 0); + return 0; +} + +static int init_ocv_reader(void) +{ + if (pdata->ocv_lut) + get_ocv = psy_ocv_from_lut; + else if (!psy_get_property(POWER_SUPPLY_PROP_VOLTAGE_OCV, NULL)) + get_ocv = psy_ocv_from_chip; + else + return -ENODEV; + + return 0; +} + +static int batmon_probe(struct platform_device *pdev) +{ + pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + psy = power_supply_get_by_name(pdata->power_supply); + + if (!psy) + return -EFAULT; + + if (init_ocv_reader()) + return -EFAULT; + + INIT_DEFERRABLE_WORK(&work, batmon_update); + schedule_delayed_work(&work, 0); + + return 0; +} + +static struct platform_driver batmon_driver = { + .probe = batmon_probe, + .shutdown = batmon_shutdown, + .suspend = batmon_suspend, + .resume = batmon_resume, + .driver = { + .name = "sysedp_batmon_calc", + .owner = THIS_MODULE + } +}; + +static __init int batmon_init(void) +{ + return platform_driver_register(&batmon_driver); +} +late_initcall(batmon_init); diff --git a/drivers/edp/sysedp_debug.c b/drivers/edp/sysedp_debug.c new file mode 100644 index 000000000000..3ffe6d028747 --- /dev/null +++ b/drivers/edp/sysedp_debug.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sysedp.h> +#include <linux/debugfs.h> +#include "sysedp_internal.h" + +struct dentry *edp_debugfs_dir; +struct dentry *sysedp_debugfs_dir; + +static int sysedp_status_show(struct seq_file *file, void *data) +{ + int consumer_sum = 0; + struct sysedp_consumer *c; + + mutex_lock(&sysedp_lock); + list_for_each_entry(c, ®istered_consumers, link) { + consumer_sum += _cur_level(c); + } + + seq_printf(file, " avail_budget : %u\n", avail_budget); + seq_printf(file, "- consumer_sum : %u\n", consumer_sum); + seq_printf(file, "- margin : %d\n", margin); + seq_printf(file, "= remaining : %d\n", ((int)avail_budget - + consumer_sum - margin)); + + seq_puts(file, "------------------------------------------\n"); + seq_printf(file, "%-16s %7s\n", "consumer", "current"); + seq_puts(file, "------------------------------------------\n"); + + list_for_each_entry(c, ®istered_consumers, link) + seq_printf(file, "%-16s %7u\n", c->name, _cur_level(c)); + + mutex_unlock(&sysedp_lock); + return 0; +} + +static int sysedp_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, sysedp_status_show, inode->i_private); +} + +static const struct file_operations sysedp_status_fops = { + .open = sysedp_status_open, + .read = seq_read, +}; + +void sysedp_init_debugfs(void) +{ + struct dentry *d; + + d = debugfs_create_dir("edp", NULL); + if (IS_ERR_OR_NULL(d)) { + WARN_ON(1); + return; + } + edp_debugfs_dir = d; + + d = debugfs_create_dir("sysedp", edp_debugfs_dir); + if (IS_ERR_OR_NULL(d)) { + WARN_ON(1); + return; + } + sysedp_debugfs_dir = d; + + d = debugfs_create_file("status", S_IRUGO, sysedp_debugfs_dir, NULL, + &sysedp_status_fops); + WARN_ON(IS_ERR_OR_NULL(d)); +} diff --git a/drivers/edp/sysedp_dynamic_capping.c b/drivers/edp/sysedp_dynamic_capping.c new file mode 100644 index 000000000000..3e1ddaf913db --- /dev/null +++ b/drivers/edp/sysedp_dynamic_capping.c @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/cpu.h> +#include <linux/debugfs.h> +#include <linux/edp.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_qos.h> +#include <linux/workqueue.h> +#include <linux/platform_data/tegra_edp.h> +#include <linux/debugfs.h> + +#include "sysedp_internal.h" + +struct freqcap { + unsigned int cpu; + unsigned int gpu; + unsigned int emc; +}; + +static unsigned int gpu_high_threshold = 500; +static unsigned int gpu_window = 80; +static unsigned int gpu_high_hist; +static unsigned int gpu_high_count = 2; +static unsigned int online_cpu_count; +static bool gpu_busy; +static unsigned int avail_power; +static struct tegra_sysedp_corecap *cur_corecap; +static struct clk *emc_cap_clk; +static struct clk *gpu_cap_clk; +static struct pm_qos_request cpufreq_qos; +static unsigned int cpu_power_balance; +static unsigned int force_gpu_pri; +static struct delayed_work capping_work; +static struct tegra_sysedp_platform_data *capping_device_platdata; +static struct freqcap core_policy; +static struct freqcap forced_caps; +static struct freqcap cur_caps; +static DEFINE_MUTEX(core_lock); + +static int init_done; + +/* To save some cycles from a linear search */ +static unsigned int cpu_lut_match(unsigned int power, + struct tegra_system_edp_entry *lut, unsigned int lutlen) +{ + unsigned int fv; + unsigned int lv; + unsigned int step; + unsigned int i; + + if (lutlen == 1) + return 0; + + fv = lut[0].power_limit_100mW * 100; + lv = lut[lutlen - 1].power_limit_100mW * 100; + step = (lv - fv) / (lutlen - 1); + + i = (power - fv + step - 1) / step; + i = min_t(unsigned int, i, lutlen - 1); + if (lut[i].power_limit_100mW * 100 >= power) + return i; + + /* Didn't work, search back from the end */ + return lutlen - 1; +} + +static unsigned int get_cpufreq_lim(unsigned int power) +{ + struct tegra_system_edp_entry *p; + int i; + + i = cpu_lut_match(power, capping_device_platdata->cpufreq_lim, + capping_device_platdata->cpufreq_lim_size); + p = capping_device_platdata->cpufreq_lim + i; + + for (; i > 0; i--, p--) { + if (p->power_limit_100mW * 100 <= power) + break; + } + + WARN_ON(p->power_limit_100mW > power); + return p->freq_limits[online_cpu_count - 1]; +} + +static void pr_caps(struct freqcap *old, struct freqcap *new, + unsigned int cpu_power) +{ + if (!IS_ENABLED(CONFIG_DEBUG_KERNEL)) + return; + + if (new->cpu == old->cpu && + new->gpu == old->gpu && + new->emc == old->emc) + return; + + pr_debug("sysedp: ncpus %u, gpupri %d, core %5u mW, " + "cpu %5u mW %u kHz, gpu %u kHz, emc %u kHz\n", + online_cpu_count, gpu_busy, cur_corecap->power, + cpu_power, new->cpu, new->gpu, new->emc); +} + +static void apply_caps(struct tegra_sysedp_devcap *devcap) +{ + struct freqcap new; + int r; + + core_policy.cpu = get_cpufreq_lim(devcap->cpu_power + + cpu_power_balance); + core_policy.gpu = devcap->gpufreq; + core_policy.emc = devcap->emcfreq; + + new.cpu = forced_caps.cpu ?: core_policy.cpu; + new.gpu = forced_caps.gpu ?: core_policy.gpu; + new.emc = forced_caps.emc ?: core_policy.emc; + + if (new.cpu != cur_caps.cpu) + pm_qos_update_request(&cpufreq_qos, new.cpu); + + if (new.emc != cur_caps.emc) { + r = clk_set_rate(emc_cap_clk, new.emc * 1000); + WARN_ON(r); + } + + if (new.gpu != cur_caps.gpu) { + r = clk_set_rate(gpu_cap_clk, new.gpu * 1000); + WARN_ON(r); + } + + pr_caps(&cur_caps, &new, devcap->cpu_power); + cur_caps = new; +} + +static inline bool gpu_priority(void) +{ + return gpu_busy || force_gpu_pri; +} + +static inline struct tegra_sysedp_devcap *get_devcap(void) +{ + return gpu_priority() ? &cur_corecap->gpupri : &cur_corecap->cpupri; +} + +static void __do_cap_control(void) +{ + struct tegra_sysedp_devcap *cap; + + if (!cur_corecap) + return; + + cap = get_devcap(); + apply_caps(cap); +} + +static void do_cap_control(void) +{ + mutex_lock(&core_lock); + __do_cap_control(); + mutex_unlock(&core_lock); +} + +static void update_cur_corecap(void) +{ + struct tegra_sysedp_corecap *cap; + unsigned int power; + int i; + + if (!capping_device_platdata) + return; + + power = avail_power * capping_device_platdata->core_gain / 100; + + i = capping_device_platdata->corecap_size - 1; + cap = capping_device_platdata->corecap + i; + + for (; i >= 0; i--, cap--) { + if (cap->power <= power) { + cur_corecap = cap; + cpu_power_balance = power - cap->power; + return; + } + } + + cur_corecap = capping_device_platdata->corecap; + cpu_power_balance = 0; +} + +/* set the available power budget for cpu/gpu/emc (in mW) */ +void sysedp_set_dynamic_cap(unsigned int power) +{ + if (!init_done) + return; + + mutex_lock(&core_lock); + avail_power = power; + update_cur_corecap(); + __do_cap_control(); + mutex_unlock(&core_lock); +} + +static void capping_worker(struct work_struct *work) +{ + if (!gpu_busy) + do_cap_control(); +} + +/* + * Return true if load was above threshold for at least + * gpu_high_count number of notifications + */ +static bool calc_gpu_busy(unsigned int load) +{ + unsigned int mask; + + mask = (1 << gpu_high_count) - 1; + + gpu_high_hist <<= 1; + if (load >= gpu_high_threshold) + gpu_high_hist |= 1; + + return (gpu_high_hist & mask) == mask; +} + +void tegra_edp_notify_gpu_load(unsigned int load) +{ + bool old; + + old = gpu_busy; + gpu_busy = calc_gpu_busy(load); + + if (gpu_busy == old || force_gpu_pri || !capping_device_platdata) + return; + + cancel_delayed_work(&capping_work); + + if (gpu_busy) + do_cap_control(); + else + schedule_delayed_work(&capping_work, + msecs_to_jiffies(gpu_window)); +} + +static int tegra_edp_cpu_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + switch (action) { + case CPU_UP_PREPARE: + online_cpu_count = num_online_cpus() + 1; + break; + case CPU_DEAD: + online_cpu_count = num_online_cpus(); + break; + default: + return NOTIFY_OK; + } + + do_cap_control(); + return NOTIFY_OK; +} + +static struct notifier_block tegra_edp_cpu_nb = { + .notifier_call = tegra_edp_cpu_notify +}; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *capping_debugfs_dir; + +static int core_set(void *data, u64 val) +{ + unsigned int *pdata = data; + unsigned int old; + + old = *pdata; + *pdata = val; + + if (old != *pdata) { + /* Changes to core_gain require corecap update */ + if (pdata == &capping_device_platdata->core_gain) + update_cur_corecap(); + do_cap_control(); + } + + return 0; +} + +static int core_get(void *data, u64 *val) +{ + unsigned int *pdata = data; + *val = *pdata; + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(core_fops, core_get, core_set, "%lld\n"); + +static void create_attr(const char *name, unsigned int *data) +{ + struct dentry *d; + + d = debugfs_create_file(name, S_IRUGO | S_IWUSR, capping_debugfs_dir, + data, &core_fops); + WARN_ON(IS_ERR_OR_NULL(d)); +} + +static inline void edp_show_2core_cpucaps(struct seq_file *file) +{ + int i; + struct tegra_system_edp_entry *p = capping_device_platdata->cpufreq_lim; + + seq_printf(file, "%5s %10s %10s\n", + "Power", "1-core", "2-cores"); + + for (i = 0; i < capping_device_platdata->cpufreq_lim_size; i++, p++) { + seq_printf(file, "%5d %10u %10u\n", + p->power_limit_100mW * 100, + p->freq_limits[0], + p->freq_limits[1]); + } +} + +static inline void edp_show_4core_cpucaps(struct seq_file *file) +{ + int i; + struct tegra_system_edp_entry *p = capping_device_platdata->cpufreq_lim; + + seq_printf(file, "%5s %10s %10s %10s %10s\n", + "Power", "1-core", "2-cores", "3-cores", "4-cores"); + + for (i = 0; i < capping_device_platdata->cpufreq_lim_size; i++, p++) { + seq_printf(file, "%5d %10u %10u %10u %10u\n", + p->power_limit_100mW * 100, + p->freq_limits[0], + p->freq_limits[1], + p->freq_limits[2], + p->freq_limits[3]); + } +} + +static int cpucaps_show(struct seq_file *file, void *data) +{ + unsigned int max_nr_cpus = num_possible_cpus(); + + if (!capping_device_platdata || !capping_device_platdata->cpufreq_lim) + return -ENODEV; + + if (max_nr_cpus == 2) + edp_show_2core_cpucaps(file); + else if (max_nr_cpus == 4) + edp_show_4core_cpucaps(file); + + return 0; +} + +static int corecaps_show(struct seq_file *file, void *data) +{ + int i; + struct tegra_sysedp_corecap *p; + struct tegra_sysedp_devcap *c; + struct tegra_sysedp_devcap *g; + + if (!capping_device_platdata || !capping_device_platdata->corecap) + return -ENODEV; + + p = capping_device_platdata->corecap; + + seq_printf(file, "%s %s { %s %9s %9s } %s { %s %9s %9s }\n", + "E-state", + "CPU-pri", "CPU-mW", "GPU-kHz", "EMC-kHz", + "GPU-pri", "CPU-mW", "GPU-kHz", "EMC-kHz"); + + for (i = 0; i < capping_device_platdata->corecap_size; i++, p++) { + c = &p->cpupri; + g = &p->gpupri; + seq_printf(file, "%7u %16u %9u %9u %18u %9u %9u\n", + p->power, + c->cpu_power, c->gpufreq, c->emcfreq, + g->cpu_power, g->gpufreq, g->emcfreq); + } + + return 0; +} + +static int status_show(struct seq_file *file, void *data) +{ + mutex_lock(&core_lock); + + seq_printf(file, "cpus online : %u\n", online_cpu_count); + seq_printf(file, "gpu priority: %u\n", gpu_priority()); + seq_printf(file, "gain : %u\n", capping_device_platdata->core_gain); + seq_printf(file, "core cap : %u\n", cur_corecap->power); + seq_printf(file, "cpu balance : %u\n", cpu_power_balance); + seq_printf(file, "cpu power : %u\n", get_devcap()->cpu_power + + cpu_power_balance); + seq_printf(file, "cpu cap : %u kHz\n", cur_caps.cpu); + seq_printf(file, "gpu cap : %u kHz\n", cur_caps.gpu); + seq_printf(file, "emc cap : %u kHz\n", cur_caps.emc); + + mutex_unlock(&core_lock); + return 0; +} + +static int longattr_open(struct inode *inode, struct file *file) +{ + return single_open(file, inode->i_private, NULL); +} + +static const struct file_operations longattr_fops = { + .open = longattr_open, + .read = seq_read, +}; + +static void create_longattr(const char *name, + int (*show)(struct seq_file *, void *)) +{ + struct dentry *d; + + d = debugfs_create_file(name, S_IRUGO, capping_debugfs_dir, show, + &longattr_fops); + WARN_ON(IS_ERR_OR_NULL(d)); +} + +static void init_debug(void) +{ + struct dentry *d; + + if (!sysedp_debugfs_dir) + return; + + d = debugfs_create_dir("capping", sysedp_debugfs_dir); + if (IS_ERR_OR_NULL(d)) { + WARN_ON(1); + return; + } + + capping_debugfs_dir = d; + + + create_attr("favor_gpu", &force_gpu_pri); + create_attr("gpu_threshold", &gpu_high_threshold); + create_attr("force_cpu", &forced_caps.cpu); + create_attr("force_gpu", &forced_caps.gpu); + create_attr("force_emc", &forced_caps.emc); + create_attr("gpu_window", &gpu_window); + create_attr("gain", &capping_device_platdata->core_gain); + create_attr("gpu_high_count", &gpu_high_count); + + create_longattr("corecaps", corecaps_show); + create_longattr("cpucaps", cpucaps_show); + create_longattr("status", status_show); +} +#else +static inline void init_debug(void) {} +#endif + +static int init_clks(void) +{ + emc_cap_clk = clk_get_sys("battery_edp", "emc"); + if (IS_ERR(emc_cap_clk)) + return -ENODEV; + + gpu_cap_clk = clk_get_sys("battery_edp", "gpu"); + if (IS_ERR(gpu_cap_clk)) { + clk_put(emc_cap_clk); + return -ENODEV; + } + + return 0; +} + +static int sysedp_dynamic_capping_probe(struct platform_device *pdev) +{ + int r; + + if (!pdev->dev.platform_data) + return -EINVAL; + + online_cpu_count = num_online_cpus(); + INIT_DELAYED_WORK(&capping_work, capping_worker); + pm_qos_add_request(&cpufreq_qos, PM_QOS_CPU_FREQ_MAX, + PM_QOS_CPU_FREQ_MAX_DEFAULT_VALUE); + + r = register_cpu_notifier(&tegra_edp_cpu_nb); + if (r) + return r; + + r = init_clks(); + if (r) + return r; + + mutex_lock(&core_lock); + capping_device_platdata = pdev->dev.platform_data; + avail_power = capping_device_platdata->init_req_watts; + update_cur_corecap(); + __do_cap_control(); + mutex_unlock(&core_lock); + + init_debug(); + + init_done = 1; + return 0; +} + +static struct platform_driver sysedp_dynamic_capping_driver = { + .probe = sysedp_dynamic_capping_probe, + .driver = { + .owner = THIS_MODULE, + .name = "sysedp_dynamic_capping" + } +}; + +static __init int sysedp_dynamic_capping_init(void) +{ + return platform_driver_register(&sysedp_dynamic_capping_driver); +} +late_initcall(sysedp_dynamic_capping_init); diff --git a/drivers/edp/sysedp_internal.h b/drivers/edp/sysedp_internal.h new file mode 100644 index 000000000000..620c721109c7 --- /dev/null +++ b/drivers/edp/sysedp_internal.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012-2013, 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 <http://www.gnu.org/licenses/>. + */ + +#ifndef _SYSEDP_INTERNAL_H +#define _SYSEDP_INTERNAL_H + +#include <linux/mutex.h> +#include <linux/sysedp.h> + +extern struct mutex sysedp_lock; +extern struct dentry *edp_debugfs_dir; +extern struct dentry *sysedp_debugfs_dir; +extern int margin; +extern unsigned int avail_budget; +extern unsigned int consumer_sum; +extern struct list_head registered_consumers; +extern struct mutex sysedp_lock; + +static inline unsigned int _cur_level(struct sysedp_consumer *c) +{ + return c->states[c->state]; +} + + +void sysedp_set_avail_budget(unsigned int); +void sysedp_set_dynamic_cap(unsigned int); +struct sysedp_consumer *sysedp_get_consumer(const char *); + +int sysedp_init_sysfs(void); +void sysedp_init_debugfs(void); + +void _sysedp_refresh(void); +int sysedp_consumer_add_kobject(struct sysedp_consumer *); +void sysedp_consumer_remove_kobject(struct sysedp_consumer *); + +#endif diff --git a/drivers/edp/sysedp_sysfs.c b/drivers/edp/sysedp_sysfs.c new file mode 100644 index 000000000000..98669507820a --- /dev/null +++ b/drivers/edp/sysedp_sysfs.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2013, 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 <http://www.gnu.org/licenses/>. + */ + +#include <linux/kernel.h> +#include <linux/sysfs.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/sysedp.h> +#include <linux/err.h> +#include <trace/events/sysedp.h> +#include "sysedp_internal.h" + +static struct kobject sysedp_kobj; + +struct sysedp_consumer_attribute { + struct attribute attr; + ssize_t (*show)(struct sysedp_consumer *c, char *buf); + ssize_t (*store)(struct sysedp_consumer *c, + const char *buf, size_t count); +}; + + +static ssize_t states_show(struct sysedp_consumer *c, char *s) +{ + unsigned int i; + int cnt = 0; + const int sz = sizeof(*c->states) * 3 + 2; + + for (i = 0; i < c->num_states && (cnt + sz) < PAGE_SIZE; i++) + cnt += sprintf(s + cnt, "%s%u", i ? " " : "", c->states[i]); + + cnt += sprintf(s + cnt, "\n"); + return cnt; +} + +static ssize_t current_show(struct sysedp_consumer *c, char *s) +{ + return sprintf(s, "%u\n", c->states[c->state]); +} + +static ssize_t state_show(struct sysedp_consumer *c, char *s) +{ + return sprintf(s, "%u\n", c->state); +} + +static ssize_t state_store(struct sysedp_consumer *c, const char *s, + size_t count) +{ + unsigned int new_state; + + if (sscanf(s, "%u", &new_state) != 1) + return -EINVAL; + + sysedp_set_state(c, new_state); + + return count; +} + +static struct sysedp_consumer_attribute attr_current = { + .attr = { .name = "current", .mode = 0444 }, + .show = current_show +}; +static struct sysedp_consumer_attribute attr_state = __ATTR(state, 0660, + state_show, + state_store); +static struct sysedp_consumer_attribute attr_states = __ATTR_RO(states); + +static struct attribute *consumer_attrs[] = { + &attr_current.attr, + &attr_state.attr, + &attr_states.attr, + NULL +}; + +static struct sysedp_consumer *to_consumer(struct kobject *kobj) +{ + return container_of(kobj, struct sysedp_consumer, kobj); +} + +static ssize_t consumer_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + ssize_t r = -EINVAL; + struct sysedp_consumer *c; + struct sysedp_consumer_attribute *cattr; + + c = to_consumer(kobj); + cattr = container_of(attr, struct sysedp_consumer_attribute, attr); + if (c && cattr) { + if (cattr->show) + r = cattr->show(c, buf); + } + + return r; +} + +static ssize_t consumer_attr_store(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t count) +{ + ssize_t r = -EINVAL; + struct sysedp_consumer *c; + struct sysedp_consumer_attribute *cattr; + + c = to_consumer(kobj); + cattr = container_of(attr, struct sysedp_consumer_attribute, attr); + if (c && cattr) { + if (cattr->store) + r = cattr->store(c, buf, count); + } + + return r; +} + +static const struct sysfs_ops consumer_sysfs_ops = { + .show = consumer_attr_show, + .store = consumer_attr_store +}; + +static struct kobj_type ktype_consumer = { + .sysfs_ops = &consumer_sysfs_ops, + .default_attrs = consumer_attrs +}; + +int sysedp_consumer_add_kobject(struct sysedp_consumer *consumer) +{ + struct kobject *parent = &sysedp_kobj; + + if (kobject_init_and_add(&consumer->kobj, &ktype_consumer, parent, + consumer->name)) { + pr_err("%s: failed to init & add sysfs consumer entry\n", + consumer->name); + return -EINVAL; + } + + kobject_uevent(&consumer->kobj, KOBJ_ADD); + return 0; +} + +void sysedp_consumer_remove_kobject(struct sysedp_consumer *consumer) +{ + kobject_put(&consumer->kobj); +} + +struct sysedp_attribute { + struct attribute attr; + ssize_t (*show)(char *buf); + ssize_t (*store)(const char *buf, size_t count); +}; + +static unsigned int *get_tokenized_data(const char *buf, + unsigned int *num_tokens) +{ + const char *cp; + int i; + unsigned int ntokens = 1; + unsigned int *tokenized_data; + int err = -EINVAL; + + cp = buf; + while ((cp = strpbrk(cp + 1, ","))) + ntokens++; + + tokenized_data = kmalloc(ntokens * sizeof(unsigned int), + GFP_KERNEL); + if (!tokenized_data) { + err = -ENOMEM; + goto err; + } + + cp = buf; + i = 0; + while (i < ntokens) { + if (sscanf(cp, "%u", &tokenized_data[i++]) != 1) + goto err_kfree; + + cp = strpbrk(cp, ","); + if (!cp) + break; + cp++; + } + + if (i != ntokens) + goto err_kfree; + + *num_tokens = ntokens; + return tokenized_data; + +err_kfree: + kfree(tokenized_data); +err: + return ERR_PTR(err); +} + + +static ssize_t consumer_register_store(const char *s, size_t count) +{ + size_t name_len; + unsigned int *states; + unsigned int num_states; + struct sysedp_consumer *consumer; + + name_len = strcspn(s, " \n"); + if (name_len > SYSEDP_NAME_LEN-1) + return -EINVAL; + + states = get_tokenized_data(s + name_len, &num_states); + if (IS_ERR_OR_NULL(states)) + return -EINVAL; + + consumer = kzalloc(sizeof(*consumer), GFP_KERNEL); + if (!consumer) { + kfree(states); + return -ENOMEM; + } + + memcpy(consumer->name, s, name_len); + consumer->name[name_len] = 0; + consumer->states = states; + consumer->num_states = num_states; + consumer->removable = 1; + + if (sysedp_register_consumer(consumer)) { + kfree(states); + kfree(consumer); + return -EINVAL; + } + + return count; +} + +static ssize_t consumer_unregister_store(const char *s, size_t count) +{ + char name[SYSEDP_NAME_LEN]; + size_t n; + struct sysedp_consumer *consumer; + + n = count > SYSEDP_NAME_LEN ? SYSEDP_NAME_LEN : count; + strncpy(name, s, n); + name[n-1] = 0; + consumer = sysedp_get_consumer(strim(name)); + + if (!consumer) + return -EINVAL; + + if (!consumer->removable) + return -EINVAL; + + kfree(consumer->states); + sysedp_free_consumer(consumer); + + return count; +} + +static ssize_t margin_show(char *s) +{ + return sprintf(s, "%d\n", margin); +} + +static ssize_t margin_store(const char *s, size_t count) +{ + int val; + if (sscanf(s, "%d", &val) != 1) + return -EINVAL; + + mutex_lock(&sysedp_lock); + margin = val; + _sysedp_refresh(); + mutex_unlock(&sysedp_lock); + + return count; +} + +static ssize_t avail_budget_show(char *s) +{ + return sprintf(s, "%u\n", avail_budget); +} + +static struct sysedp_attribute attr_consumer_register = + __ATTR(consumer_register, 0220, NULL, consumer_register_store); +static struct sysedp_attribute attr_consumer_unregister = + __ATTR(consumer_unregister, 0220, NULL, consumer_unregister_store); +static struct sysedp_attribute attr_margin = + __ATTR(margin, 0660, margin_show, margin_store); +static struct sysedp_attribute attr_avail_budget = __ATTR_RO(avail_budget); + +static struct attribute *sysedp_attrs[] = { + &attr_consumer_register.attr, + &attr_consumer_unregister.attr, + &attr_margin.attr, + &attr_avail_budget.attr, + NULL +}; + +static ssize_t sysedp_attr_show(struct kobject *kobj, + struct attribute *_attr, char *buf) +{ + ssize_t r = -EINVAL; + struct sysedp_attribute *attr; + attr = container_of(_attr, struct sysedp_attribute, attr); + if (attr && attr->show) + r = attr->show(buf); + return r; +} + +static ssize_t sysedp_attr_store(struct kobject *kobj, struct attribute *_attr, + const char *buf, size_t count) +{ + ssize_t r = -EINVAL; + struct sysedp_attribute *attr; + attr = container_of(_attr, struct sysedp_attribute, attr); + if (attr && attr->store) + r = attr->store(buf, count); + return r; +} + +static const struct sysfs_ops sysedp_sysfs_ops = { + .show = sysedp_attr_show, + .store = sysedp_attr_store +}; + +static struct kobj_type ktype_sysedp = { + .sysfs_ops = &sysedp_sysfs_ops, + .default_attrs = sysedp_attrs +}; + +int sysedp_init_sysfs(void) +{ + struct kobject *parent = NULL; + +#ifdef CONFIG_PM + parent = power_kobj; +#endif + + return kobject_init_and_add(&sysedp_kobj, &ktype_sysedp, + parent, "sysedp"); +} |