diff options
-rw-r--r-- | drivers/thermal/Kconfig | 13 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 3 | ||||
-rw-r--r-- | drivers/thermal/devfreq_cooling.c | 309 | ||||
-rw-r--r-- | include/linux/devfreq_cooling.h | 80 |
4 files changed, 405 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index af40db0df58e..a99badf5b241 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -112,6 +112,19 @@ config CPU_THERMAL If you want this support, you should say Y here. +config DEVFREQ_THERMAL + bool "generic devfreq cooling support" + depends on PM_DEVFREQ + depends on THERMAL_OF + help + This implements the generic devfreq cooling mechanism through frequency + reduction. An ACPI version of this already exists + (drivers/acpi/processor_thermal.c). + This will be useful for platforms using the generic thermal interface + and not the ACPI interface. + + If you want this support, you should say Y here. + config CLOCK_THERMAL bool "Generic clock cooling support" depends on COMMON_CLK diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index fa0dc486790f..2864cd50c255 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -18,6 +18,9 @@ thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o # cpufreq cooling thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o +# devfreq cooling +thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o + # clock cooling thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c new file mode 100644 index 000000000000..4868499b54b3 --- /dev/null +++ b/drivers/thermal/devfreq_cooling.c @@ -0,0 +1,309 @@ +/* + * linux/drivers/thermal/devfreq_cooling.c + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd + * Author: Chanwoo Choi <cw00.choi@samsung.com> + * + * This driver is based on drivers/thermal/cpu_cooling.c. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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/err.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include <linux/devfreq.h> +#include <linux/devfreq_cooling.h> + +/* + * Cooling state <-> devfreq 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 devfreq_cooling_device - data for cooling device with devfreq + * @cool_dev: thermal_cooling_device pointer to keep track of the + * registered cooling device. + * @devfreq: the devfreq instance. + * @cur_state: integer value representing the current state of devfreq + * cooling devices. + * @max_state: interger value representing the maximum state of devfreq + * cooling devices. + * + * This structure is required for keeping information of each registered + * devfreq_cooling_device. + */ +struct devfreq_cooling_device { + struct thermal_cooling_device *cool_dev; + struct devfreq *devfreq; + + unsigned int cur_state; + unsigned int max_state; + + unsigned int *freq_table; /* In descending order */ +}; + +/* devfreq cooling device callback functions are defined below */ + +/** + * devfreq_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 devfreq + * max cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int devfreq_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct devfreq_cooling_device *devfreq_device = cdev->devdata; + + *state = devfreq_device->max_state; + + return 0; +} + +/** + * devfreq_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 devfreq + * current cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int devfreq_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct devfreq_cooling_device *devfreq_device = cdev->devdata; + + *state = devfreq_device->cur_state; + + return 0; +} + +/** + * devfreq_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 devfreq + * current cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int devfreq_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct devfreq_cooling_device *devfreq_dev = cdev->devdata; + unsigned int limited_freq; + + /* Request state should be less than max_level */ + if (WARN_ON(state > devfreq_dev->max_state)) + return -EINVAL; + + /* Check if the old cooling action is same as new cooling action */ + if (devfreq_dev->cur_state == state) + return 0; + + limited_freq = devfreq_dev->freq_table[state]; + + devfreq_dev->cur_state = state; + + /* Set the limited frequency to maximum frequency of devfreq */ + devfreq_dev->devfreq->max_freq = limited_freq; + + return 0; +} + +/* Bind devfreq callbacks to thermal cooling device ops */ +static struct thermal_cooling_device_ops const devfreq_cooling_ops = { + .get_max_state = devfreq_get_max_state, + .get_cur_state = devfreq_get_cur_state, + .set_cur_state = devfreq_set_cur_state, +}; + +/** + * __devfreq_cooling_register - helper function to create devfreq cooling device + * @np: a valid struct device_node to the cooling device tree node + * @devfreq: the devfreq instance. + * + * This interface function registers the devfreq cooling device with the name + * "thermal-devfreq-%x". This api can support multiple instances of devfreq + * 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 * +__devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq) +{ + struct thermal_cooling_device *cool_dev; + struct devfreq_cooling_device *devfreq_dev; + struct devfreq_dev_profile *devfreq_profile = devfreq->profile; + struct dev_pm_opp *opp; + static atomic_t devfreq_cooling_no = ATOMIC_INIT(-1); + char dev_name[THERMAL_NAME_LENGTH]; + unsigned long freq; + int i; + + devfreq_dev = kzalloc(sizeof(*devfreq_dev), GFP_KERNEL); + if (!devfreq_dev) + return ERR_PTR(-ENOMEM); + + rcu_read_lock(); + devfreq_dev->max_state = dev_pm_opp_get_opp_count(devfreq->dev.parent); + if (devfreq_dev->max_state <= 0) { + rcu_read_unlock(); + cool_dev = ERR_PTR(-EINVAL); + goto free_cdev; + } + rcu_read_unlock(); + + /* + * Use the freq_table of devfreq_dev_profile structure + * if the devfreq_dev_profile includes already filled frequency table. + */ + if (devfreq_profile->freq_table) { + devfreq_dev->freq_table = devfreq_profile->freq_table; + goto register_cooling_dev; + } + + /* Allocate the frequency table and fill it */ + rcu_read_lock(); + devfreq_dev->freq_table = kzalloc(sizeof(*devfreq_dev->freq_table) * + devfreq_dev->max_state, GFP_KERNEL); + if (!devfreq_dev->freq_table) { + rcu_read_unlock(); + cool_dev = ERR_PTR(-ENOMEM); + goto free_cdev; + } + devfreq_dev->max_state -= 1; + + freq = ULONG_MAX; + for (i = 0; i <= devfreq_dev->max_state; i++, freq--) { + opp = dev_pm_opp_find_freq_floor(devfreq->dev.parent, + &freq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + cool_dev = ERR_PTR(-EINVAL); + goto free_table; + } + devfreq_dev->freq_table[i] = freq; + } + rcu_read_unlock(); + +register_cooling_dev: + /* Register cooling device with devfreq device */ + snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", + atomic_inc_return(&devfreq_cooling_no)); + + cool_dev = thermal_of_cooling_device_register(np, dev_name, devfreq_dev, + &devfreq_cooling_ops); + if (IS_ERR(cool_dev)) + goto free_table; + + devfreq_dev->devfreq = devfreq; + devfreq_dev->cool_dev = cool_dev; + + return cool_dev; + +free_table: + kfree(devfreq_dev->freq_table); +free_cdev: + kfree(devfreq_dev); + + return cool_dev; +} + +/** + * of_devfreq_cooling_register - function to create devfreq cooling device. + * @np: a valid struct device_node to the cooling device tree node + * @devfreq: the devfreq instance. + * + * This interface function registers the devfreq cooling device with the name + * "thermal-devfreq-%x". This api can support multiple instances of devfreq + * cooling devices. Using this API, the devfreq 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_devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq) +{ + if (!np || !devfreq) + return ERR_PTR(-EINVAL); + + return __devfreq_cooling_register(np, devfreq); +} +EXPORT_SYMBOL_GPL(of_devfreq_cooling_register); + +/** + * devfreq_cooling_register - function to create devfreq cooling device. + * @devfreq: the devfreq instance. + * + * This interface function registers the devfreq cooling device with the name + * "thermal-devfreq-%x". This api can support multiple instances of devfreq + * cooling devices. + * + * Return: a valid struct thermal_cooling_device pointer on success, + * on failure, it returns a corresponding ERR_PTR(). + */ +struct thermal_cooling_device * +devfreq_cooling_register(struct devfreq *devfreq) +{ + if (!devfreq) + return ERR_PTR(-EINVAL); + + return __devfreq_cooling_register(NULL, devfreq); +} +EXPORT_SYMBOL_GPL(devfreq_cooling_register); + +/** + * devfreq_cooling_unregister - function to remove devfreq cooling device. + * @cdev: thermal cooling device pointer. + * + * This interface function unregisters the "thermal-devfreq-%x" cooling device. + */ +void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) +{ + struct devfreq_cooling_device *devfreq_dev; + + if (!cdev) + return; + + devfreq_dev = cdev->devdata; + + thermal_cooling_device_unregister(devfreq_dev->cool_dev); + kfree(devfreq_dev->freq_table); + kfree(devfreq_dev); +} +EXPORT_SYMBOL_GPL(devfreq_cooling_unregister); diff --git a/include/linux/devfreq_cooling.h b/include/linux/devfreq_cooling.h new file mode 100644 index 000000000000..194cfa97fc75 --- /dev/null +++ b/include/linux/devfreq_cooling.h @@ -0,0 +1,80 @@ +/* + * linux/include/linux/devfreq_cooling.h + * + * Copyright (C) 2015 Samsung Electronics Co., Ltd + * Author: Chanwoo Choi <cw00.choi@samsung.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __DEVFREQ_COOLING_H__ +#define __DEVFREQ_COOLING_H__ + +#include <linux/of.h> +#include <linux/thermal.h> +#include <linux/devfreq.h> + +#ifdef CONFIG_DEVFREQ_THERMAL +/** + * devfreq_cooling_register - function to create cpufreq cooling device. + * @devfreq: the devfreq instance. + */ +struct thermal_cooling_device * +devfreq_cooling_register(struct devfreq *devfreq); + +/** + * of_devfreq_cooling_register - create cpufreq cooling device based on DT. + * @np: a valid struct device_node to the cooling device tree node + * @devfreq: the devfreq instance. + */ +#ifdef CONFIG_THERMAL_OF +struct thermal_cooling_device * +of_devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq); +#else +static inline struct thermal_cooling_device * +of_devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq) +{ + return ERR_PTR(-ENOSYS); +} +#endif + +/** + * devfreq_cooling_unregister - function to remove cpufreq cooling device. + * @cdev: thermal cooling device pointer. + */ +void devfreq_cooling_unregister(struct thermal_cooling_device *cdev); + +unsigned long devfreq_cooling_get_level(unsigned int cpu, unsigned int freq); +#else /* !CONFIG_DEVFREQ_THERMAL */ +static inline struct thermal_cooling_device * +devfreq_cooling_register(struct devfreq *devfreq) +{ + return ERR_PTR(-ENOSYS); +} +static inline struct thermal_cooling_device * +of_devfreq_cooling_register(struct device_node *np, struct devfreq *devfreq) +{ + return ERR_PTR(-ENOSYS); +} + +static inline +void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) +{ + return; +} +#endif /* CONFIG_DEVFREQ_THERMAL */ +#endif /* __DEVFREQ_COOLING_H__ */ |