Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / thermal / cpu_cooling.c
diff --git a/kernel/drivers/thermal/cpu_cooling.c b/kernel/drivers/thermal/cpu_cooling.c
new file mode 100644 (file)
index 0000000..f65f0d1
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ *  linux/drivers/thermal/cpu_cooling.c
+ *
+ *  Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
+ *  Copyright (C) 2012  Amit Daniel <amit.kachhap@linaro.org>
+ *
+ *  Copyright (C) 2014  Viresh Kumar <viresh.kumar@linaro.org>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  This program is distributed in the hope that 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, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/cpufreq.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/cpu_cooling.h>
+
+/*
+ * Cooling state <-> CPUFreq frequency
+ *
+ * Cooling states are translated to frequencies throughout this driver and this
+ * is the relation between them.
+ *
+ * Highest cooling state corresponds to lowest possible frequency.
+ *
+ * i.e.
+ *     level 0 --> 1st Max Freq
+ *     level 1 --> 2nd Max Freq
+ *     ...
+ */
+
+/**
+ * struct cpufreq_cooling_device - data for cooling device with cpufreq
+ * @id: unique integer value corresponding to each cpufreq_cooling_device
+ *     registered.
+ * @cool_dev: thermal_cooling_device pointer to keep track of the
+ *     registered cooling device.
+ * @cpufreq_state: integer value representing the current state of cpufreq
+ *     cooling devices.
+ * @cpufreq_val: integer value representing the absolute value of the clipped
+ *     frequency.
+ * @max_level: maximum cooling level. One less than total number of valid
+ *     cpufreq frequencies.
+ * @allowed_cpus: all the cpus involved for this cpufreq_cooling_device.
+ * @node: list_head to link all cpufreq_cooling_device together.
+ *
+ * This structure is required for keeping information of each registered
+ * cpufreq_cooling_device.
+ */
+struct cpufreq_cooling_device {
+       int id;
+       struct thermal_cooling_device *cool_dev;
+       unsigned int cpufreq_state;
+       unsigned int cpufreq_val;
+       unsigned int max_level;
+       unsigned int *freq_table;       /* In descending order */
+       struct cpumask allowed_cpus;
+       struct list_head node;
+};
+static DEFINE_IDR(cpufreq_idr);
+static DEFINE_MUTEX(cooling_cpufreq_lock);
+
+static LIST_HEAD(cpufreq_dev_list);
+
+/**
+ * get_idr - function to get a unique id.
+ * @idr: struct idr * handle used to create a id.
+ * @id: int * value generated by this function.
+ *
+ * This function will populate @id with an unique
+ * id, using the idr API.
+ *
+ * Return: 0 on success, an error code on failure.
+ */
+static int get_idr(struct idr *idr, int *id)
+{
+       int ret;
+
+       mutex_lock(&cooling_cpufreq_lock);
+       ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
+       mutex_unlock(&cooling_cpufreq_lock);
+       if (unlikely(ret < 0))
+               return ret;
+       *id = ret;
+
+       return 0;
+}
+
+/**
+ * release_idr - function to free the unique id.
+ * @idr: struct idr * handle used for creating the id.
+ * @id: int value representing the unique id.
+ */
+static void release_idr(struct idr *idr, int id)
+{
+       mutex_lock(&cooling_cpufreq_lock);
+       idr_remove(idr, id);
+       mutex_unlock(&cooling_cpufreq_lock);
+}
+
+/* Below code defines functions to be used for cpufreq as cooling device */
+
+/**
+ * get_level: Find the level for a particular frequency
+ * @cpufreq_dev: cpufreq_dev for which the property is required
+ * @freq: Frequency
+ *
+ * Return: level on success, THERMAL_CSTATE_INVALID on error.
+ */
+static unsigned long get_level(struct cpufreq_cooling_device *cpufreq_dev,
+                              unsigned int freq)
+{
+       unsigned long level;
+
+       for (level = 0; level <= cpufreq_dev->max_level; level++) {
+               if (freq == cpufreq_dev->freq_table[level])
+                       return level;
+
+               if (freq > cpufreq_dev->freq_table[level])
+                       break;
+       }
+
+       return THERMAL_CSTATE_INVALID;
+}
+
+/**
+ * cpufreq_cooling_get_level - for a given cpu, return the cooling level.
+ * @cpu: cpu for which the level is required
+ * @freq: the frequency of interest
+ *
+ * This function will match the cooling level corresponding to the
+ * requested @freq and return it.
+ *
+ * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID
+ * otherwise.
+ */
+unsigned long cpufreq_cooling_get_level(unsigned int cpu, unsigned int freq)
+{
+       struct cpufreq_cooling_device *cpufreq_dev;
+
+       mutex_lock(&cooling_cpufreq_lock);
+       list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
+               if (cpumask_test_cpu(cpu, &cpufreq_dev->allowed_cpus)) {
+                       mutex_unlock(&cooling_cpufreq_lock);
+                       return get_level(cpufreq_dev, freq);
+               }
+       }
+       mutex_unlock(&cooling_cpufreq_lock);
+
+       pr_err("%s: cpu:%d not part of any cooling device\n", __func__, cpu);
+       return THERMAL_CSTATE_INVALID;
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_get_level);
+
+/**
+ * cpufreq_thermal_notifier - notifier callback for cpufreq policy change.
+ * @nb:        struct notifier_block * with callback info.
+ * @event: value showing cpufreq event for which this function invoked.
+ * @data: callback-specific data
+ *
+ * Callback to hijack the notification on cpufreq policy transition.
+ * Every time there is a change in policy, we will intercept and
+ * update the cpufreq policy with thermal constraints.
+ *
+ * Return: 0 (success)
+ */
+static int cpufreq_thermal_notifier(struct notifier_block *nb,
+                                   unsigned long event, void *data)
+{
+       struct cpufreq_policy *policy = data;
+       unsigned long max_freq = 0;
+       struct cpufreq_cooling_device *cpufreq_dev;
+
+       if (event != CPUFREQ_ADJUST)
+               return 0;
+
+       mutex_lock(&cooling_cpufreq_lock);
+       list_for_each_entry(cpufreq_dev, &cpufreq_dev_list, node) {
+               if (!cpumask_test_cpu(policy->cpu,
+                                       &cpufreq_dev->allowed_cpus))
+                       continue;
+
+               max_freq = cpufreq_dev->cpufreq_val;
+
+               if (policy->max != max_freq)
+                       cpufreq_verify_within_limits(policy, 0, max_freq);
+       }
+       mutex_unlock(&cooling_cpufreq_lock);
+
+       return 0;
+}
+
+/* cpufreq cooling device callback functions are defined below */
+
+/**
+ * cpufreq_get_max_state - callback function to get the max cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the max cooling state.
+ *
+ * Callback for the thermal cooling device to return the cpufreq
+ * max cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_get_max_state(struct thermal_cooling_device *cdev,
+                                unsigned long *state)
+{
+       struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+       *state = cpufreq_device->max_level;
+       return 0;
+}
+
+/**
+ * cpufreq_get_cur_state - callback function to get the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: fill this variable with the current cooling state.
+ *
+ * Callback for the thermal cooling device to return the cpufreq
+ * current cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_get_cur_state(struct thermal_cooling_device *cdev,
+                                unsigned long *state)
+{
+       struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+
+       *state = cpufreq_device->cpufreq_state;
+
+       return 0;
+}
+
+/**
+ * cpufreq_set_cur_state - callback function to set the current cooling state.
+ * @cdev: thermal cooling device pointer.
+ * @state: set this variable to the current cooling state.
+ *
+ * Callback for the thermal cooling device to change the cpufreq
+ * current cooling state.
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
+                                unsigned long state)
+{
+       struct cpufreq_cooling_device *cpufreq_device = cdev->devdata;
+       unsigned int cpu = cpumask_any(&cpufreq_device->allowed_cpus);
+       unsigned int clip_freq;
+
+       /* Request state should be less than max_level */
+       if (WARN_ON(state > cpufreq_device->max_level))
+               return -EINVAL;
+
+       /* Check if the old cooling action is same as new cooling action */
+       if (cpufreq_device->cpufreq_state == state)
+               return 0;
+
+       clip_freq = cpufreq_device->freq_table[state];
+       cpufreq_device->cpufreq_state = state;
+       cpufreq_device->cpufreq_val = clip_freq;
+
+       cpufreq_update_policy(cpu);
+
+       return 0;
+}
+
+/* Bind cpufreq callbacks to thermal cooling device ops */
+static struct thermal_cooling_device_ops const cpufreq_cooling_ops = {
+       .get_max_state = cpufreq_get_max_state,
+       .get_cur_state = cpufreq_get_cur_state,
+       .set_cur_state = cpufreq_set_cur_state,
+};
+
+/* Notifier for cpufreq policy change */
+static struct notifier_block thermal_cpufreq_notifier_block = {
+       .notifier_call = cpufreq_thermal_notifier,
+};
+
+static unsigned int find_next_max(struct cpufreq_frequency_table *table,
+                                 unsigned int prev_max)
+{
+       struct cpufreq_frequency_table *pos;
+       unsigned int max = 0;
+
+       cpufreq_for_each_valid_entry(pos, table) {
+               if (pos->frequency > max && pos->frequency < prev_max)
+                       max = pos->frequency;
+       }
+
+       return max;
+}
+
+/**
+ * __cpufreq_cooling_register - helper function to create cpufreq cooling device
+ * @np: a valid struct device_node to the cooling device device tree node
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ * Normally this should be same as cpufreq policy->related_cpus.
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices. It also gives the opportunity to link the cooling device
+ * with a device tree node, in order to bind it via the thermal DT code.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+static struct thermal_cooling_device *
+__cpufreq_cooling_register(struct device_node *np,
+                          const struct cpumask *clip_cpus)
+{
+       struct thermal_cooling_device *cool_dev;
+       struct cpufreq_cooling_device *cpufreq_dev;
+       char dev_name[THERMAL_NAME_LENGTH];
+       struct cpufreq_frequency_table *pos, *table;
+       unsigned int freq, i;
+       int ret;
+
+       table = cpufreq_frequency_get_table(cpumask_first(clip_cpus));
+       if (!table) {
+               pr_debug("%s: CPUFreq table not found\n", __func__);
+               return ERR_PTR(-EPROBE_DEFER);
+       }
+
+       cpufreq_dev = kzalloc(sizeof(*cpufreq_dev), GFP_KERNEL);
+       if (!cpufreq_dev)
+               return ERR_PTR(-ENOMEM);
+
+       /* Find max levels */
+       cpufreq_for_each_valid_entry(pos, table)
+               cpufreq_dev->max_level++;
+
+       cpufreq_dev->freq_table = kmalloc(sizeof(*cpufreq_dev->freq_table) *
+                                         cpufreq_dev->max_level, GFP_KERNEL);
+       if (!cpufreq_dev->freq_table) {
+               cool_dev = ERR_PTR(-ENOMEM);
+               goto free_cdev;
+       }
+
+       /* max_level is an index, not a counter */
+       cpufreq_dev->max_level--;
+
+       cpumask_copy(&cpufreq_dev->allowed_cpus, clip_cpus);
+
+       ret = get_idr(&cpufreq_idr, &cpufreq_dev->id);
+       if (ret) {
+               cool_dev = ERR_PTR(ret);
+               goto free_table;
+       }
+
+       snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d",
+                cpufreq_dev->id);
+
+       cool_dev = thermal_of_cooling_device_register(np, dev_name, cpufreq_dev,
+                                                     &cpufreq_cooling_ops);
+       if (IS_ERR(cool_dev))
+               goto remove_idr;
+
+       /* Fill freq-table in descending order of frequencies */
+       for (i = 0, freq = -1; i <= cpufreq_dev->max_level; i++) {
+               freq = find_next_max(table, freq);
+               cpufreq_dev->freq_table[i] = freq;
+
+               /* Warn for duplicate entries */
+               if (!freq)
+                       pr_warn("%s: table has duplicate entries\n", __func__);
+               else
+                       pr_debug("%s: freq:%u KHz\n", __func__, freq);
+       }
+
+       cpufreq_dev->cpufreq_val = cpufreq_dev->freq_table[0];
+       cpufreq_dev->cool_dev = cool_dev;
+
+       mutex_lock(&cooling_cpufreq_lock);
+
+       /* Register the notifier for first cpufreq cooling device */
+       if (list_empty(&cpufreq_dev_list))
+               cpufreq_register_notifier(&thermal_cpufreq_notifier_block,
+                                         CPUFREQ_POLICY_NOTIFIER);
+       list_add(&cpufreq_dev->node, &cpufreq_dev_list);
+
+       mutex_unlock(&cooling_cpufreq_lock);
+
+       return cool_dev;
+
+remove_idr:
+       release_idr(&cpufreq_idr, cpufreq_dev->id);
+free_table:
+       kfree(cpufreq_dev->freq_table);
+free_cdev:
+       kfree(cpufreq_dev);
+
+       return cool_dev;
+}
+
+/**
+ * cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+cpufreq_cooling_register(const struct cpumask *clip_cpus)
+{
+       return __cpufreq_cooling_register(NULL, clip_cpus);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_register);
+
+/**
+ * of_cpufreq_cooling_register - function to create cpufreq cooling device.
+ * @np: a valid struct device_node to the cooling device device tree node
+ * @clip_cpus: cpumask of cpus where the frequency constraints will happen.
+ *
+ * This interface function registers the cpufreq cooling device with the name
+ * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq
+ * cooling devices. Using this API, the cpufreq cooling device will be
+ * linked to the device tree node provided.
+ *
+ * Return: a valid struct thermal_cooling_device pointer on success,
+ * on failure, it returns a corresponding ERR_PTR().
+ */
+struct thermal_cooling_device *
+of_cpufreq_cooling_register(struct device_node *np,
+                           const struct cpumask *clip_cpus)
+{
+       if (!np)
+               return ERR_PTR(-EINVAL);
+
+       return __cpufreq_cooling_register(np, clip_cpus);
+}
+EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
+
+/**
+ * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
+ * @cdev: thermal cooling device pointer.
+ *
+ * This interface function unregisters the "thermal-cpufreq-%x" cooling device.
+ */
+void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+{
+       struct cpufreq_cooling_device *cpufreq_dev;
+
+       if (!cdev)
+               return;
+
+       cpufreq_dev = cdev->devdata;
+       mutex_lock(&cooling_cpufreq_lock);
+       list_del(&cpufreq_dev->node);
+
+       /* Unregister the notifier for the last cpufreq cooling device */
+       if (list_empty(&cpufreq_dev_list))
+               cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
+                                           CPUFREQ_POLICY_NOTIFIER);
+       mutex_unlock(&cooling_cpufreq_lock);
+
+       thermal_cooling_device_unregister(cpufreq_dev->cool_dev);
+       release_idr(&cpufreq_idr, cpufreq_dev->id);
+       kfree(cpufreq_dev->freq_table);
+       kfree(cpufreq_dev);
+}
+EXPORT_SYMBOL_GPL(cpufreq_cooling_unregister);