/* * Copyright (c) 2013-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 #define CREATE_TRACE_POINTS #include #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; int min_budget; 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; /* Amount of power available when OC=1*/ int oc_relax; /* Additional power available when OC=0 */ int pmax_sum = 0; /* sum of peak values (OC=1) */ int pthres_sum = 0; /* sum of peak values (OC=0) */ list_for_each_entry(p, ®istered_consumers, link) { pmax_sum += _cur_oclevel(p); pthres_sum += _cur_level(p); } limit = (int)avail_budget - pmax_sum - margin; limit = limit >= min_budget ? limit : min_budget; oc_relax = pmax_sum - pthres_sum; oc_relax = oc_relax >= 0 ? oc_relax : 0; sysedp_set_dynamic_cap((unsigned int)limit, (unsigned int)oc_relax); } static struct sysedp_consumer *_sysedp_get_consumer(const char *name) { struct sysedp_consumer *p; struct sysedp_consumer *match = NULL; list_for_each_entry(p, ®istered_consumers, link) { if (!strncmp(p->name, name, SYSEDP_NAME_LEN)) { match = p; break; } } return match; } struct sysedp_consumer *sysedp_get_consumer(const char *name) { struct sysedp_consumer *match = NULL; mutex_lock(&sysedp_lock); match = _sysedp_get_consumer(name); mutex_unlock(&sysedp_lock); return match; } int sysedp_register_consumer(struct sysedp_consumer *consumer) { int r; if (!consumer) return -EINVAL; if (sysedp_get_consumer(consumer->name)) return -EEXIST; 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); static void _sysedp_set_state(struct sysedp_consumer *consumer, unsigned int new_state) { 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(); } } void sysedp_set_state(struct sysedp_consumer *consumer, unsigned int new_state) { if (!consumer) return; mutex_lock(&sysedp_lock); _sysedp_set_state(consumer, new_state); mutex_unlock(&sysedp_lock); } EXPORT_SYMBOL(sysedp_set_state); void sysedp_set_state_by_name(const char *name, unsigned int new_state) { struct sysedp_consumer *consumer = NULL; if (!name) return; mutex_lock(&sysedp_lock); consumer = _sysedp_get_consumer(name); if (consumer) _sysedp_set_state(consumer, new_state); mutex_unlock(&sysedp_lock); } EXPORT_SYMBOL(sysedp_set_state_by_name); unsigned int sysedp_get_state(struct sysedp_consumer *consumer) { unsigned int state; if (!consumer) return 0; mutex_lock(&sysedp_lock); state = consumer->state; mutex_unlock(&sysedp_lock); return state; } EXPORT_SYMBOL(sysedp_get_state); static int sysedp_probe(struct platform_device *pdev) { pdata = pdev->dev.platform_data; if (!pdata) return -EINVAL; margin = pdata->margin; min_budget = (pdata->min_budget >= 0) ? pdata->min_budget : 0; 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);