summaryrefslogtreecommitdiff
path: root/drivers/edp/sysedp.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/edp/sysedp.c')
-rw-r--r--drivers/edp/sysedp.c207
1 files changed, 207 insertions, 0 deletions
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, &registered_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, &registered_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, &registered_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);