summaryrefslogtreecommitdiff
path: root/drivers/edp
diff options
context:
space:
mode:
authorTimo Alho <talho@nvidia.com>2013-10-25 23:49:49 +0300
committerJuha Tukkinen <jtukkinen@nvidia.com>2013-10-28 07:46:53 -0700
commit7f54fbd16212785b346ad959364c674f26deaece (patch)
tree7f681602d3d59e62193172b34ae175cf18587128 /drivers/edp
parent2f86343c877ef70ffa0a58e3fd90e3f0b8add83c (diff)
EDP: introduce revised system-EDP framework
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>
Diffstat (limited to 'drivers/edp')
-rw-r--r--drivers/edp/Kconfig9
-rw-r--r--drivers/edp/Makefile27
-rw-r--r--drivers/edp/sysedp.c207
-rw-r--r--drivers/edp/sysedp_batmon_calc.c269
-rw-r--r--drivers/edp/sysedp_debug.c84
-rw-r--r--drivers/edp/sysedp_dynamic_capping.c530
-rw-r--r--drivers/edp/sysedp_internal.h49
-rw-r--r--drivers/edp/sysedp_sysfs.c349
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, &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
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, &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
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");
+}