From bfaf4a48e5074dce76aa63a9fc2acf6f0eed41da Mon Sep 17 00:00:00 2001 From: Park Chungwoo Date: Fri, 18 Mar 2016 14:34:27 +0900 Subject: [PATCH] thermal: Add isp cooling related code. Change-Id: Id25553799131a6685a27fcb516902ae2fee2ce43 Signed-off-by: Park Chungwoo Signed-off-by: Hyeonseong Gil --- drivers/thermal/Kconfig | 9 + drivers/thermal/Makefile | 2 + drivers/thermal/isp_cooling.c | 459 +++++++++++++++++++++++++++ drivers/thermal/samsung/exynos_tmu.c | 88 +++++ include/linux/isp_cooling.h | 129 ++++++++ 5 files changed, 687 insertions(+) create mode 100644 drivers/thermal/isp_cooling.c create mode 100644 include/linux/isp_cooling.h diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index f45874eb85b2..f12fb9935ec7 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -189,6 +189,15 @@ config GPU_THERMAL If you want this support, you should say Y here. +config ISP_THERMAL + bool "generic isp cooling support" + depends on THERMAL_OF + help + This implements the generic isp cooling mechanism through fps + reduction. + + If you want this support, you should say Y here. + config THERMAL_EMULATION bool "Thermal emulation mode support" help diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 2cf1a1fe0a06..a8b70cface84 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -29,6 +29,8 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o thermal_sys-$(CONFIG_GPU_THERMAL) += gpu_cooling.o +thermal_sys-$(CONFIG_ISP_THERMAL) += isp_cooling.o + # platform thermal drivers obj-y += broadcom/ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o diff --git a/drivers/thermal/isp_cooling.c b/drivers/thermal/isp_cooling.c new file mode 100644 index 000000000000..4d411d2dc373 --- /dev/null +++ b/drivers/thermal/isp_cooling.c @@ -0,0 +1,459 @@ +/* + * linux/drivers/thermal/isp_cooling.c + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com) + * Copyright (C) 2012 Amit Daniel + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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 +#include +#include +#include +#include +#include + +#include + +/** + * struct isp_cooling_device - data for cooling device with isp + * @id: unique integer value corresponding to each isp_cooling_device + * registered. + * @cool_dev: thermal_cooling_device pointer to keep track of the + * registered cooling device. + * @isp_state: integer value representing the current state of isp + * cooling devices. + * @isp_val: integer value representing the absolute value of the clipped + * fps. + * @allowed_isp: all the isp involved for this isp_cooling_device. + * + * This structure is required for keeping information of each + * isp_cooling_device registered. In order to prevent corruption of this a + * mutex lock cooling_isp_lock is used. + */ +struct isp_cooling_device { + int id; + struct thermal_cooling_device *cool_dev; + unsigned int isp_state; + unsigned int isp_val; +}; +static DEFINE_IDR(isp_idr); +static DEFINE_MUTEX(cooling_isp_lock); +static BLOCKING_NOTIFIER_HEAD(isp_notifier); + +static unsigned int isp_dev_count; + +extern struct isp_fps_table isp_fps_table[]; + +/** + * 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_isp_lock); + ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL); + mutex_unlock(&cooling_isp_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_isp_lock); + idr_remove(idr, id); + mutex_unlock(&cooling_isp_lock); +} + +/* Below code defines functions to be used for isp as cooling device */ + +enum isp_cooling_property { + GET_LEVEL, + GET_FREQ, + GET_MAXL, +}; + +/** + * get_property - fetch a property of interest for a give isp. + * @isp: isp for which the property is required + * @input: query parameter + * @output: query return + * @property: type of query (fps, level, max level) + * + * This is the common function to + * 1. get maximum isp cooling states + * 2. translate fps to cooling state + * 3. translate cooling state to fps + * Note that the code may be not in good shape + * but it is written in this way in order to: + * a) reduce duplicate code as most of the code can be shared. + * b) make sure the logic is consistent when translating between + * cooling states and fps. + * + * Return: 0 on success, -EINVAL when invalid parameters are passed. + */ +static int get_property(unsigned int isp, unsigned long input, + unsigned int *output, + enum isp_cooling_property property) +{ + int i; + unsigned long max_level = 0, level = 0; + unsigned int fps = ISP_FPS_ENTRY_INVALID; + int descend = -1; + struct isp_fps_table *pos, *table = + isp_fps_table; + + if (!output) + return -EINVAL; + + if (!table) + return -EINVAL; + + isp_fps_for_each_valid_entry(pos, table) { + /* ignore duplicate entry */ + if (fps == pos->fps) + continue; + + /* get the fps order */ + if (fps != ISP_FPS_ENTRY_INVALID && descend == -1) + descend = fps > pos->fps; + + fps = pos->fps; + max_level++; + } + + /* No valid cpu fps entry */ + if (max_level == 0) + return -EINVAL; + + /* max_level is an index, not a counter */ + max_level--; + + /* get max level */ + if (property == GET_MAXL) { + *output = (unsigned int)max_level; + return 0; + } + + i = 0; + isp_fps_for_each_valid_entry(pos, table) { + /* ignore duplicate entry */ + if (fps == pos->fps) + continue; + + /* now we have a valid fps entry */ + fps = pos->fps; + + if (property == GET_LEVEL && (unsigned int)input == fps) { + /* get level by fps */ + *output = descend ? i : (max_level - i); + return 0; + } + if (property == GET_FREQ && level == i) { + /* get fps by level */ + *output = fps; + return 0; + } + i++; + } + + return -EINVAL; +} + +/** + * isp_cooling_get_level - for a give isp, return the cooling level. + * @isp: isp for which the level is required + * @fps: the fps of interest + * + * This function will match the cooling level corresponding to the + * requested @fps and return it. + * + * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID + * otherwise. + */ +unsigned long isp_cooling_get_fps(unsigned int isp, unsigned int fps) +{ + unsigned int val; + + if (get_property(isp, (unsigned long)fps, &val, GET_LEVEL)) + return THERMAL_CSTATE_INVALID; + + return (unsigned long)val; +} +EXPORT_SYMBOL_GPL(isp_cooling_get_fps); + +/** + * isp_apply_cooling - function to apply fps clipping. + * @isp_device: isp_cooling_device pointer containing fps + * clipping data. + * @cooling_state: value of the cooling state. + * + * Function used to make sure the isp layer is aware of current thermal + * limits. The limits are applied by updating the isp policy. + * + * Return: 0 on success, an error code otherwise (-EINVAL in case wrong + * cooling state). + */ +static int isp_apply_cooling(struct isp_cooling_device *isp_device, + unsigned long cooling_state) +{ + /* Check if the old cooling action is same as new cooling action */ + if (isp_device->isp_state == cooling_state) + return 0; + + isp_device->isp_state = cooling_state; + + blocking_notifier_call_chain(&isp_notifier, cooling_state, &cooling_state); + + return 0; +} + +/* isp cooling device callback functions are defined below */ + +/** + * isp_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 isp + * max cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int isp_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + unsigned int count = 0; + int ret; + + ret = get_property(0, 0, &count, GET_MAXL); + + if (count > 0) + *state = count; + + return ret; +} + +/** + * isp_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 isp + * current cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int isp_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct isp_cooling_device *isp_device = cdev->devdata; + + *state = isp_device->isp_state; + + return 0; +} + +/** + * isp_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 isp + * current cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int isp_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct isp_cooling_device *isp_device = cdev->devdata; + + return isp_apply_cooling(isp_device, state); +} + +static enum tmu_noti_state_t isp_tstate = ISP_COLD; + +static int isp_set_cur_temp(struct thermal_cooling_device *cdev, + bool suspended, int temp) +{ + enum tmu_noti_state_t tstate; + + if (suspended || temp < EXYNOS_COLD_TEMP) + tstate = ISP_COLD; + else + tstate = ISP_NORMAL; + + if (isp_tstate == tstate) + return 0; + + isp_tstate = tstate; + + blocking_notifier_call_chain(&isp_notifier, tstate, &tstate); + + return 0; +} + +/* Bind isp callbacks to thermal cooling device ops */ +static struct thermal_cooling_device_ops const isp_cooling_ops = { + .get_max_state = isp_get_max_state, + .get_cur_state = isp_get_cur_state, + .set_cur_state = isp_set_cur_state, + .set_cur_temp = isp_set_cur_temp, +}; + + +int exynos_tmu_isp_add_notifier(struct notifier_block *n) +{ + return blocking_notifier_chain_register(&isp_notifier, n); +} + +/** + * __isp_cooling_register - helper function to create isp cooling device + * @np: a valid struct device_node to the cooling device device tree node + * @clip_isp: ispmask of isp where the fps constraints will happen. + * + * This interface function registers the isp cooling device with the name + * "thermal-isp-%x". This api can support multiple instances of isp + * 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 * +__isp_cooling_register(struct device_node *np, + const struct cpumask *clip_isp) +{ + struct thermal_cooling_device *cool_dev; + struct isp_cooling_device *isp_dev = NULL; + char dev_name[THERMAL_NAME_LENGTH]; + int ret = 0; + + isp_dev = kzalloc(sizeof(struct isp_cooling_device), + GFP_KERNEL); + if (!isp_dev) + return ERR_PTR(-ENOMEM); + + ret = get_idr(&isp_idr, &isp_dev->id); + if (ret) { + kfree(isp_dev); + return ERR_PTR(-EINVAL); + } + + snprintf(dev_name, sizeof(dev_name), "thermal-isp-%d", + isp_dev->id); + + cool_dev = thermal_of_cooling_device_register(np, dev_name, isp_dev, + &isp_cooling_ops); + if (IS_ERR(cool_dev)) { + release_idr(&isp_idr, isp_dev->id); + kfree(isp_dev); + return cool_dev; + } + isp_dev->cool_dev = cool_dev; + isp_dev->isp_state = 0; + mutex_lock(&cooling_isp_lock); + + isp_dev_count++; + + mutex_unlock(&cooling_isp_lock); + + return cool_dev; +} + +/** + * isp_cooling_register - function to create isp cooling device. + * @clip_isp: cpumask of gpus where the fps constraints will happen. + * + * This interface function registers the isp cooling device with the name + * "thermal-isp-%x". This api can support multiple instances of isp + * cooling devices. + * + * Return: a valid struct thermal_cooling_device pointer on success, + * on failure, it returns a corresponding ERR_PTR(). + */ +struct thermal_cooling_device * +isp_cooling_register(const struct cpumask *clip_isp) +{ + return __isp_cooling_register(NULL, clip_isp); +} +EXPORT_SYMBOL_GPL(isp_cooling_register); + +/** + * of_isp_cooling_register - function to create isp cooling device. + * @np: a valid struct device_node to the cooling device device tree node + * @clip_isp: cpumask of gpus where the fps constraints will happen. + * + * This interface function registers the isp cooling device with the name + * "thermal-isp-%x". This api can support multiple instances of isp + * cooling devices. Using this API, the isp 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_isp_cooling_register(struct device_node *np, + const struct cpumask *clip_isp) +{ + if (!np) + return ERR_PTR(-EINVAL); + + return __isp_cooling_register(np, clip_isp); +} +EXPORT_SYMBOL_GPL(of_isp_cooling_register); + +/** + * isp_cooling_unregister - function to remove isp cooling device. + * @cdev: thermal cooling device pointer. + * + * This interface function unregisters the "thermal-isp-%x" cooling device. + */ +void isp_cooling_unregister(struct thermal_cooling_device *cdev) +{ + struct isp_cooling_device *isp_dev; + + if (!cdev) + return; + + isp_dev = cdev->devdata; + mutex_lock(&cooling_isp_lock); + isp_dev_count--; + mutex_unlock(&cooling_isp_lock); + + thermal_cooling_device_unregister(isp_dev->cool_dev); + release_idr(&isp_idr, isp_dev->id); + kfree(isp_dev); +} +EXPORT_SYMBOL_GPL(isp_cooling_unregister); diff --git a/drivers/thermal/samsung/exynos_tmu.c b/drivers/thermal/samsung/exynos_tmu.c index c3496d6a99f0..940ed3a89382 100644 --- a/drivers/thermal/samsung/exynos_tmu.c +++ b/drivers/thermal/samsung/exynos_tmu.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -747,6 +748,48 @@ static int gpu_cooling_table_init(struct platform_device *pdev) static int gpu_cooling_table_init(struct platform_device *pdev) {return 0;} #endif +#ifdef CONFIG_ISP_THERMAL +struct isp_fps_table isp_fps_table[10]; + +static int isp_cooling_table_init(struct platform_device *pdev) +{ + struct isp_fps_table *table_ptr; + unsigned int table_size; + u32 isp_idx_num = 0; + int ret = 0, i = 0; + + /* isp cooling frequency table parse */ + ret = of_property_read_u32(pdev->dev.of_node, "isp_idx_num", + &isp_idx_num); + if (ret < 0) + dev_err(&pdev->dev, "isp_idx_num happend error value\n"); + + if (isp_idx_num) { + table_ptr = kzalloc(sizeof(struct isp_fps_table) + * isp_idx_num, GFP_KERNEL); + if (!table_ptr) { + dev_err(&pdev->dev, "failed to allocate for isp_table\n"); + return -ENODEV; + } + table_size = sizeof(struct isp_fps_table) / sizeof(unsigned int); + ret = of_property_read_u32_array(pdev->dev.of_node, "isp_cooling_table", + (unsigned int *)table_ptr, table_size * isp_idx_num); + + for (i = 0; i < isp_idx_num; i++) { + isp_fps_table[i].flags = table_ptr[i].flags; + isp_fps_table[i].driver_data = table_ptr[i].driver_data; + isp_fps_table[i].fps = table_ptr[i].fps; + dev_info(&pdev->dev, "[ISP TMU] index : %d, fps : %d \n", + isp_fps_table[i].driver_data, isp_fps_table[i].fps); + } + kfree(table_ptr); + } + return ret; +} +#else +static int isp_cooling_table_init(struct platform_device *pdev) {return 0;} +#endif + struct pm_qos_request thermal_cpu_hotplug_request; static int exynos_throttle_cpu_hotplug(void *p, int temp) { @@ -868,6 +911,41 @@ static int exynos_gpufreq_cooling_register(struct exynos_tmu_data *data) static int exynos_gpufreq_cooling_register(struct exynos_tmu_data *data) {return 0;} #endif +#ifdef CONFIG_ISP_THERMAL +static int exynos_isp_cooling_register(struct exynos_tmu_data *data) +{ + struct device_node *np, *child = NULL, *gchild, *ggchild; + struct device_node *cool_np; + struct of_phandle_args cooling_spec; + int ret, i; + + np = of_find_node_by_name(NULL, "thermal-zones"); + if (!np) + return -ENODEV; + + /* Regist isp cooling device */ + for (i = 0; i <= data->id; i++) { + child = of_get_next_child(np, child); + if (i == data->id) + break; + } + gchild = of_get_child_by_name(child, "cooling-maps"); + ggchild = of_get_next_child(gchild, NULL); + ret = of_parse_phandle_with_args(ggchild, "cooling-device", "#cooling-cells", + 0, &cooling_spec); + if (ret < 0) { + pr_err("exynos_tmu do not get cooling spec \n"); + } + cool_np = cooling_spec.np; + + data->cool_dev = of_isp_cooling_register(cool_np, NULL); + + return ret; +} +#else +static int exynos_isp_cooling_register(struct exynos_tmu_data *data) {return 0;} +#endif + static int exynos_tmu_probe(struct platform_device *pdev) { struct exynos_tmu_data *data; @@ -904,6 +982,16 @@ static int exynos_tmu_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Failed cooling register \n"); goto err_sensor; } + } else if (data->id == 3) { + ret = isp_cooling_table_init(pdev); + if (ret) + goto err_sensor; + + ret = exynos_isp_cooling_register(data); + if (ret) { + dev_err(&pdev->dev, "Failed cooling register \n"); + goto err_sensor; + } } INIT_WORK(&data->irq_work, exynos_tmu_work); diff --git a/include/linux/isp_cooling.h b/include/linux/isp_cooling.h new file mode 100644 index 000000000000..52b2efd5f50b --- /dev/null +++ b/include/linux/isp_cooling.h @@ -0,0 +1,129 @@ +/* + * linux/include/linux/gpu_cooling.h + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com) + * Copyright (C) 2012 Amit Daniel + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * 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 __ISP_COOLING_H__ +#define __ISP_COOLING_H__ + +#include +#include +#include + +#ifdef CONFIG_ISP_THERMAL + +#define ISP_FPS_ENTRY_INVALID ~0 +#define ISP_FPS_TABLE_END ~1 + +struct isp_fps_table { + unsigned int flags; + unsigned int driver_data; /* driver specific data, not used by core */ + unsigned int fps; /* kHz - doesn't need to be in ascending + * order */ +}; + +static inline bool isp_fps_next_valid(struct isp_fps_table **pos) +{ + while ((*pos)->fps != ISP_FPS_TABLE_END) + if ((*pos)->fps != ISP_FPS_ENTRY_INVALID) + return true; + else + (*pos)++; + return false; +} + +/* + * isp_fps_for_each_entry - iterate over a cpufreq_frequency_table + * @pos: the cpufreq_frequency_table * to use as a loop cursor. + * @table: the cpufreq_frequency_table * to iterate over. + */ + +#define isp_fps_for_each_entry(pos, table) \ + for (pos = table; pos->fps != ISP_FPS_TABLE_END; pos++) + +/* + * isp_fps_for_each_valid_entry - iterate over a cpufreq_frequency_table + * excluding CPUFREQ_ENTRY_INVALID frequencies. + * @pos: the cpufreq_frequency_table * to use as a loop cursor. + * @table: the cpufreq_frequency_table * to iterate over. + */ + +#define isp_fps_for_each_valid_entry(pos, table) \ + for (pos = table; isp_fps_next_valid(&pos); pos++) + + +/** + * isp_cooling_register - function to create isp cooling device. + * @clip_gpus: cpumask of gpus where the frequency constraints will happen + */ +struct thermal_cooling_device * +isp_cooling_register(const struct cpumask *clip_gpus); + +/** + * of_isp_cooling_register - create isp cooling device based on DT. + * @np: a valid struct device_node to the cooling device device tree node. + * @clip_gpus: cpumask of gpus where the frequency constraints will happen + */ +#ifdef CONFIG_THERMAL_OF +struct thermal_cooling_device * +of_isp_cooling_register(struct device_node *np, + const struct cpumask *clip_gpus); +#else +static inline struct thermal_cooling_device * +of_isp_cooling_register(struct device_node *np, + const struct cpumask *clip_gpus) +{ + return NULL; +} +#endif + +/** + * isp_cooling_unregister - function to remove isp cooling device. + * @cdev: thermal cooling device pointer. + */ +void isp_cooling_unregister(struct thermal_cooling_device *cdev); + +unsigned long isp_cooling_get_fps(unsigned int isp, unsigned int fps); +#else /* !CONFIG_GPU_THERMAL */ +static inline struct thermal_cooling_device * +isp_cooling_register(const struct cpumask *clip_gpus) +{ + return NULL; +} +static inline struct thermal_cooling_device * +of_isp_cooling_register(struct device_node *np, + const struct cpumask *clip_gpus) +{ + return NULL; +} +static inline +void isp_cooling_unregister(struct thermal_cooling_device *cdev) +{ + return; +} +static inline +unsigned long isp_cooling_get_fps(unsigned int isp, unsigned int fps) +{ + return THERMAL_CSTATE_INVALID; +} +#endif /* CONFIG_ISP_THERMAL */ + +#endif /* __ISP_COOLING_H__ */ -- 2.20.1