diff --git a/MAINTAINERS b/MAINTAINERS index 56765f542244..fd45bd0ec68e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16342,12 +16342,15 @@ F: Documentation/devicetree/bindings/thermal/ THERMAL/CPU_COOLING M: Amit Daniel Kachhap +M: Daniel Lezcano M: Viresh Kumar M: Javi Merino L: linux-pm@vger.kernel.org S: Supported F: Documentation/driver-api/thermal/cpu-cooling-api.rst +F: Documentation/driver-api/thermal/cpu-idle-cooling.rst F: drivers/thermal/cpu_cooling.c +F: drivers/thermal/cpuidle_cooling.c F: include/linux/cpu_cooling.h THERMAL DRIVER FOR AMLOGIC SOCS diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 62c1c83f5d31..dc36941aef6e 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -170,6 +170,13 @@ config CPU_FREQ_THERMAL This will be useful for platforms using the generic thermal interface and not the ACPI interface. +config CPU_IDLE_THERMAL + bool "CPU idle cooling device" + depends on IDLE_INJECT + help + This implements the CPU cooling mechanism through + idle injection. This will throttle the CPU by injecting + idle cycle. endif config CLOCK_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 3f3533c01aa3..6929e6fad1ac 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -20,6 +20,7 @@ thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o # cpufreq cooling thermal_sys-$(CONFIG_CPU_FREQ_THERMAL) += cpu_cooling.o +thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o # clock cooling thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c new file mode 100644 index 000000000000..0bb843246f59 --- /dev/null +++ b/drivers/thermal/cpuidle_cooling.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Linaro Limited. + * + * Author: Daniel Lezcano + * + */ +#include +#include +#include +#include +#include +#include +#include + +/** + * struct cpuidle_cooling_device - data for the idle cooling device + * @ii_dev: an atomic to keep track of the last task exiting the idle cycle + * @state: a normalized integer giving the state of the cooling device + */ +struct cpuidle_cooling_device { + struct idle_inject_device *ii_dev; + unsigned long state; +}; + +static DEFINE_IDA(cpuidle_ida); + +/** + * cpuidle_cooling_runtime - Running time computation + * @idle_duration_us: the idle cooling device + * @state: a percentile based number + * + * The running duration is computed from the idle injection duration + * which is fixed. If we reach 100% of idle injection ratio, that + * means the running duration is zero. If we have a 50% ratio + * injection, that means we have equal duration for idle and for + * running duration. + * + * The formula is deduced as follows: + * + * running = idle x ((100 / ratio) - 1) + * + * For precision purpose for integer math, we use the following: + * + * running = (idle x 100) / ratio - idle + * + * For example, if we have an injected duration of 50%, then we end up + * with 10ms of idle injection and 10ms of running duration. + * + * Return: An unsigned int for a usec based runtime duration. + */ +static unsigned int cpuidle_cooling_runtime(unsigned int idle_duration_us, + unsigned long state) +{ + if (!state) + return 0; + + return ((idle_duration_us * 100) / state) - idle_duration_us; +} + +/** + * cpuidle_cooling_get_max_state - Get the maximum state + * @cdev : the thermal cooling device + * @state : a pointer to the state variable to be filled + * + * The function always returns 100 as the injection ratio. It is + * percentile based for consistency accross different platforms. + * + * Return: The function can not fail, it is always zero + */ +static int cpuidle_cooling_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + /* + * Depending on the configuration or the hardware, the running + * cycle and the idle cycle could be different. We want to + * unify that to an 0..100 interval, so the set state + * interface will be the same whatever the platform is. + * + * The state 100% will make the cluster 100% ... idle. A 0% + * injection ratio means no idle injection at all and 50% + * means for 10ms of idle injection, we have 10ms of running + * time. + */ + *state = 100; + + return 0; +} + +/** + * cpuidle_cooling_get_cur_state - Get the current cooling state + * @cdev: the thermal cooling device + * @state: a pointer to the state + * + * The function just copies the state value from the private thermal + * cooling device structure, the mapping is 1 <-> 1. + * + * Return: The function can not fail, it is always zero + */ +static int cpuidle_cooling_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cpuidle_cooling_device *idle_cdev = cdev->devdata; + + *state = idle_cdev->state; + + return 0; +} + +/** + * cpuidle_cooling_set_cur_state - Set the current cooling state + * @cdev: the thermal cooling device + * @state: the target state + * + * The function checks first if we are initiating the mitigation which + * in turn wakes up all the idle injection tasks belonging to the idle + * cooling device. In any case, it updates the internal state for the + * cooling device. + * + * Return: The function can not fail, it is always zero + */ +static int cpuidle_cooling_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct cpuidle_cooling_device *idle_cdev = cdev->devdata; + struct idle_inject_device *ii_dev = idle_cdev->ii_dev; + unsigned long current_state = idle_cdev->state; + unsigned int runtime_us, idle_duration_us; + + idle_cdev->state = state; + + idle_inject_get_duration(ii_dev, &runtime_us, &idle_duration_us); + + runtime_us = cpuidle_cooling_runtime(idle_duration_us, state); + + idle_inject_set_duration(ii_dev, runtime_us, idle_duration_us); + + if (current_state == 0 && state > 0) { + idle_inject_start(ii_dev); + } else if (current_state > 0 && !state) { + idle_inject_stop(ii_dev); + } + + return 0; +} + +/** + * cpuidle_cooling_ops - thermal cooling device ops + */ +static struct thermal_cooling_device_ops cpuidle_cooling_ops = { + .get_max_state = cpuidle_cooling_get_max_state, + .get_cur_state = cpuidle_cooling_get_cur_state, + .set_cur_state = cpuidle_cooling_set_cur_state, +}; + +/** + * cpuidle_of_cooling_register - Idle cooling device initialization function + * @drv: a cpuidle driver structure pointer + * @np: a node pointer to a device tree cooling device node + * + * This function is in charge of creating a cooling device per cpuidle + * driver and register it to thermal framework. + * + * Return: zero on success, or negative value corresponding to the + * error detected in the underlying subsystems. + */ +int cpuidle_of_cooling_register(struct device_node *np, + struct cpuidle_driver *drv) +{ + struct idle_inject_device *ii_dev; + struct cpuidle_cooling_device *idle_cdev; + struct thermal_cooling_device *cdev; + char dev_name[THERMAL_NAME_LENGTH]; + int id, ret; + + idle_cdev = kzalloc(sizeof(*idle_cdev), GFP_KERNEL); + if (!idle_cdev) { + ret = -ENOMEM; + goto out; + } + + id = ida_simple_get(&cpuidle_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + ret = id; + goto out_kfree; + } + + ii_dev = idle_inject_register(drv->cpumask); + if (!ii_dev) { + ret = -EINVAL; + goto out_id; + } + + idle_inject_set_duration(ii_dev, TICK_USEC, TICK_USEC); + + idle_cdev->ii_dev = ii_dev; + + snprintf(dev_name, sizeof(dev_name), "thermal-idle-%d", id); + + cdev = thermal_of_cooling_device_register(np, dev_name, idle_cdev, + &cpuidle_cooling_ops); + if (IS_ERR(cdev)) { + ret = PTR_ERR(cdev); + goto out_unregister; + } + + return 0; + +out_unregister: + idle_inject_unregister(ii_dev); +out_id: + ida_simple_remove(&cpuidle_ida, id); +out_kfree: + kfree(idle_cdev); +out: + return ret; +} + +/** + * cpuidle_cooling_register - Idle cooling device initialization function + * @drv: a cpuidle driver structure pointer + * + * This function is in charge of creating a cooling device per cpuidle + * driver and register it to thermal framework. + * + * Return: zero on success, or negative value corresponding to the + * error detected in the underlying subsystems. + */ +int cpuidle_cooling_register(struct cpuidle_driver *drv) +{ + return cpuidle_of_cooling_register(NULL, drv); +} diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h index 3cdd85f987d7..65501d8f9778 100644 --- a/include/linux/cpu_cooling.h +++ b/include/linux/cpu_cooling.h @@ -60,4 +60,22 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy) } #endif /* CONFIG_CPU_FREQ_THERMAL */ +struct cpuidle_driver; + +#ifdef CONFIG_CPU_IDLE_THERMAL +int cpuidle_cooling_register(struct cpuidle_driver *drv); +int cpuidle_of_cooling_register(struct device_node *np, + struct cpuidle_driver *drv); +#else /* CONFIG_CPU_IDLE_THERMAL */ +static inline int cpuidle_cooling_register(struct cpuidle_driver *drv) +{ + return 0; +} +static inline int cpuidle_of_cooling_register(struct device_node *np, + struct cpuidle_driver *drv) +{ + return 0; +} +#endif /* CONFIG_CPU_IDLE_THERMAL */ + #endif /* __CPU_COOLING_H__ */