]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/commitdiff
EDP: introduce revised system-EDP framework
authorTimo Alho <talho@nvidia.com>
Fri, 25 Oct 2013 20:49:49 +0000 (23:49 +0300)
committerJuha Tukkinen <jtukkinen@nvidia.com>
Mon, 28 Oct 2013 14:46:53 +0000 (07:46 -0700)
This patch introduces a revised system-EDP software
framework. Framework consist of following components:
 * sysedp - central component handling of the book keeping of consumer
   power consumptions
 * sysedp_batmon - periodically monitors the state of battery and
   updates the available budget (in mW) to sysedp framework
 * sysedp_dynamic_capping - limits the CPU, GPU, and EMC frequencies
   to ensure that system will operate in the given budget.
 * sysedp_consumer - A device in the platform that has noticeable peak
   power consumption is called sysedp consumer. Consumers register
   themselves to the sysedp framework and inform sysedp when there is
   a change in their power state

Change-Id: I343d8f09082216744da41abe5e749b15cb20417a
Signed-off-by: Timo Alho <talho@nvidia.com>
Reviewed-on: http://git-master/r/304006
GVS: Gerrit_Virtual_Submit
Reviewed-by: Sivaram Nair <sivaramn@nvidia.com>
Reviewed-by: Juha Tukkinen <jtukkinen@nvidia.com>
12 files changed:
drivers/Makefile
drivers/edp/Kconfig
drivers/edp/Makefile
drivers/edp/sysedp.c [new file with mode: 0644]
drivers/edp/sysedp_batmon_calc.c [new file with mode: 0644]
drivers/edp/sysedp_debug.c [new file with mode: 0644]
drivers/edp/sysedp_dynamic_capping.c [new file with mode: 0644]
drivers/edp/sysedp_internal.h [new file with mode: 0644]
drivers/edp/sysedp_sysfs.c [new file with mode: 0644]
include/linux/platform_data/tegra_edp.h
include/linux/sysedp.h [new file with mode: 0644]
include/trace/events/sysedp.h [new file with mode: 0644]

index 962047eb7f159e96f09146decfc58862311a0aba..b76476275b472b65ea1ea91d42720fd17f45917b 100644 (file)
@@ -5,7 +5,8 @@
 # Rewritten to use lists instead of if-statements.
 #
 
-obj-$(CONFIG_EDP_FRAMEWORK)    += edp/
+obj-$(CONFIG_EDP_FRAMEWORK)    += edp/
+obj-$(CONFIG_SYSEDP_FRAMEWORK) += edp/
 obj-$(CONFIG_CPUQUIET_FRAMEWORK) += cpuquiet/
 
 obj-y                          += irqchip/
index 9e837076b2232919cbeacc40a75a9c668f6fe041..15368879eaa079e5cf44f62520cf13dc51051729 100644 (file)
@@ -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
index a3799cb07293ad07326b3acc0f3ea32ec4a54ce7..c4aa90c234c170741e2d7d7b17cd74477eee6c37 100644 (file)
@@ -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 (file)
index 0000000..55ce8ea
--- /dev/null
@@ -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);
diff --git a/drivers/edp/sysedp_batmon_calc.c b/drivers/edp/sysedp_batmon_calc.c
new file mode 100644 (file)
index 0000000..9855a28
--- /dev/null
@@ -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 (file)
index 0000000..3ffe6d0
--- /dev/null
@@ -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, &registered_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, &registered_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 (file)
index 0000000..3e1ddaf
--- /dev/null
@@ -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 (file)
index 0000000..620c721
--- /dev/null
@@ -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 (file)
index 0000000..9866950
--- /dev/null
@@ -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");
+}
index 717f071e9b6b332dc16bd1fdd8f34a3c6d4926e9..9ae36ee4694e5b7c6a2ef38fd66329cfb42a228f 100644 (file)
@@ -48,7 +48,7 @@ struct tegra_sysedp_platform_data {
        const char *bbc;
 };
 
-#ifdef CONFIG_EDP_FRAMEWORK
+#if defined(CONFIG_EDP_FRAMEWORK) || defined(CONFIG_SYSEDP_FRAMEWORK)
 void tegra_edp_notify_gpu_load(unsigned int load);
 #else
 static inline void tegra_edp_notify_gpu_load(unsigned int load) {}
diff --git a/include/linux/sysedp.h b/include/linux/sysedp.h
new file mode 100644 (file)
index 0000000..2a42e27
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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/>.
+ */
+
+#ifndef _LINUX_SYSEDP_H
+#define _LINUX_SYSEDP_H
+
+#include <linux/kernel.h>
+#include <linux/notifier.h>
+#include <linux/sysfs.h>
+#include <linux/kobject.h>
+
+#define SYSEDP_NAME_LEN 32
+
+/*
+ * @name: name of consumer
+ * @states: EDP state array holding the max peak power for each state.
+ * @num_states: length of the above array
+ * @state: current power state of sysedp consumer
+ */
+struct sysedp_consumer {
+       char name[SYSEDP_NAME_LEN];
+       unsigned int *states;
+       unsigned int num_states;
+
+       unsigned int state;
+
+       /* internal */
+       int removable;
+       struct list_head link;
+       struct kobject kobj;
+
+#ifdef CONFIG_DEBUG_FS
+       /* public */
+       struct dentry *dentry;
+#endif
+};
+
+struct sysedp_consumer_data {
+       char *name;
+       unsigned int *states;
+       unsigned int num_states;
+};
+
+struct sysedp_platform_data {
+       struct sysedp_consumer_data *consumer_data;
+       unsigned int consumer_data_size;
+       int margin;
+};
+
+#define SYSEDP_CONSUMER_DATA(_name, _states)     \
+       {                                         \
+               .name = _name,                    \
+               .states = _states,                \
+               .num_states = ARRAY_SIZE(_states) \
+       }
+
+/*
+ * Temperature -> IBAT LUT
+ * Should be descending wrt temp
+ * { ..., .ibat = 0 } must be the last entry
+ */
+struct sysedp_batmon_ibat_lut {
+       int temp;
+       unsigned int ibat;
+};
+
+/*
+ * Capacity -> RBAT LUT
+ * Should be descending wrt capacity
+ * { .capacity = 0, ... } must be the last entry
+ */
+struct sysedp_batmon_rbat_lut {
+       unsigned int capacity;
+       unsigned int rbat;
+};
+
+/*
+ * Capacity -> OCV LUT
+ * Should be descending wrt capacity
+ * { .capacity = 0, ... } must be the last entry
+ * @capacity: battery capacity in percents
+ * @ocv: OCV in uV
+ */
+struct sysedp_batmon_ocv_lut {
+       unsigned int capacity;
+       unsigned int ocv;
+};
+
+/* Battery monitor data */
+struct sysedp_batmon_calc_platform_data {
+       char *power_supply;
+       unsigned int r_const;
+       unsigned int vsys_min;
+       struct sysedp_batmon_ibat_lut *ibat_lut;
+       struct sysedp_batmon_rbat_lut *rbat_lut;
+       struct sysedp_batmon_ocv_lut *ocv_lut;
+       unsigned int update_interval;
+};
+
+#ifdef CONFIG_SYSEDP_FRAMEWORK
+extern struct dentry *edp_debugfs_dir;
+
+void sysedp_set_state(struct sysedp_consumer *, unsigned int);
+struct sysedp_consumer *sysedp_create_consumer(const char *, const char *);
+int sysedp_register_consumer(struct sysedp_consumer *);
+void sysedp_unregister_consumer(struct sysedp_consumer *);
+void sysedp_free_consumer(struct sysedp_consumer *);
+
+#else
+static inline void sysedp_set_state(struct sysedp_consumer *c, unsigned int i)
+{ return; }
+static inline struct sysedp_consumer *sysedp_create_consumer(const char *c,
+                                                            const char *s)
+{ return NULL; }
+static inline int sysedp_register_consumer(struct sysedp_consumer *c)
+{ return -ENODEV; }
+static inline void sysedp_unregister_consumer(struct sysedp_consumer *c)
+{ return; }
+static inline void sysedp_free_consumer(struct sysedp_consumer *c)
+{ return; }
+#endif
+
+#endif
diff --git a/include/trace/events/sysedp.h b/include/trace/events/sysedp.h
new file mode 100644 (file)
index 0000000..511eea5
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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/>.
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM sysedp
+
+#if !defined(_TRACE_SYSEDP_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SYSEDP_H
+
+#include <linux/string.h>
+#include <linux/sysedp.h>
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(sysedp_change_state,
+
+           TP_PROTO(const char *name, unsigned int old, unsigned int new),
+
+           TP_ARGS(name, old, new),
+
+           TP_STRUCT__entry(
+                   __array(char,     name,       SYSEDP_NAME_LEN)
+                   __field(unsigned int,       old)
+                   __field(unsigned int,       new)
+                   ),
+
+           TP_fast_assign(
+                   memcpy(__entry->name, name, SYSEDP_NAME_LEN);
+                   __entry->old = old;
+                   __entry->new = new;
+                   ),
+
+           TP_printk("%s: %u -> %u", __entry->name,
+                     __entry->old, __entry->new)
+       );
+
+TRACE_EVENT(sysedp_set_avail_budget,
+
+           TP_PROTO(unsigned int old, unsigned int new),
+
+           TP_ARGS(old, new),
+
+           TP_STRUCT__entry(
+                   __field(unsigned int,       old)
+                   __field(unsigned int,       new)
+                   ),
+
+           TP_fast_assign(
+                   __entry->old = old;
+                   __entry->new = new;
+                   ),
+
+           TP_printk("%umW -> %umW", __entry->old, __entry->new)
+       );
+
+#endif /* _TRACE_SYSEDP_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>