From d2bc4dd91da6095a769fdc9bc519d3be7ad5f97a Mon Sep 17 00:00:00 2001 From: Anson Huang Date: Thu, 26 Mar 2020 11:13:31 +0800 Subject: thermal: imx_sc_thermal: Add hwmon support Expose i.MX SC thermal sensors as HWMON devices. Signed-off-by: Anson Huang Reviewed-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/1585192411-25593-1-git-send-email-Anson.Huang@nxp.com --- drivers/thermal/imx_sc_thermal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/thermal/imx_sc_thermal.c b/drivers/thermal/imx_sc_thermal.c index a8723b1eb8b0..b2b68c943738 100644 --- a/drivers/thermal/imx_sc_thermal.c +++ b/drivers/thermal/imx_sc_thermal.c @@ -14,6 +14,7 @@ #include #include "thermal_core.h" +#include "thermal_hwmon.h" #define IMX_SC_MISC_FUNC_GET_TEMP 13 @@ -115,6 +116,9 @@ static int imx_sc_thermal_probe(struct platform_device *pdev) ret = PTR_ERR(sensor->tzd); break; } + + if (devm_thermal_add_hwmon_sysfs(sensor->tzd)) + dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n"); } of_node_put(sensor_np); -- cgit v1.2.3 From bceb5646a15dad49ceb29ec16b8acc10075d0627 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 31 Mar 2020 18:54:48 +0200 Subject: thermal: core: Make thermal_zone_set_trips private The function thermal_zone_set_trips() is used by the thermal core code in order to update the next trip points, there are no other users. Move the function definition in the thermal_core.h, remove the EXPORT_SYMBOL_GPL and document the function. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Reviewed-by: Amit Kucheria Link: https://lore.kernel.org/r/20200331165449.30355-1-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 3 +++ drivers/thermal/thermal_helpers.c | 13 ++++++++++++- include/linux/thermal.h | 3 --- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index a9bf00e91d64..37cd4e2bead2 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -69,6 +69,9 @@ void thermal_zone_device_unbind_exception(struct thermal_zone_device *, int thermal_zone_device_set_policy(struct thermal_zone_device *, char *); int thermal_build_list_of_policies(char *buf); +/* Helpers */ +void thermal_zone_set_trips(struct thermal_zone_device *tz); + /* sysfs I/F */ int thermal_zone_create_device_groups(struct thermal_zone_device *, int); void thermal_zone_destroy_device_groups(struct thermal_zone_device *); diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c index 2ba756af76b7..59eaf2d0fdb3 100644 --- a/drivers/thermal/thermal_helpers.c +++ b/drivers/thermal/thermal_helpers.c @@ -113,6 +113,18 @@ exit: } EXPORT_SYMBOL_GPL(thermal_zone_get_temp); +/** + * thermal_zone_set_trips - Computes the next trip points for the driver + * @tz: a pointer to a thermal zone device structure + * + * The function computes the next temperature boundaries by browsing + * the trip points. The result is the closer low and high trip points + * to the current temperature. These values are passed to the backend + * driver to let it set its own notification mechanism (usually an + * interrupt). + * + * It does not return a value + */ void thermal_zone_set_trips(struct thermal_zone_device *tz) { int low = -INT_MAX; @@ -161,7 +173,6 @@ void thermal_zone_set_trips(struct thermal_zone_device *tz) exit: mutex_unlock(&tz->lock); } -EXPORT_SYMBOL_GPL(thermal_zone_set_trips); void thermal_cdev_update(struct thermal_cooling_device *cdev) { diff --git a/include/linux/thermal.h b/include/linux/thermal.h index c91b1e344d56..448841ab0dca 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -439,7 +439,6 @@ int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int, struct thermal_cooling_device *); void thermal_zone_device_update(struct thermal_zone_device *, enum thermal_notify_event); -void thermal_zone_set_trips(struct thermal_zone_device *); struct thermal_cooling_device *thermal_cooling_device_register(const char *, void *, const struct thermal_cooling_device_ops *); @@ -497,8 +496,6 @@ static inline int thermal_zone_unbind_cooling_device( static inline void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { } -static inline void thermal_zone_set_trips(struct thermal_zone_device *tz) -{ } static inline struct thermal_cooling_device * thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops) -- cgit v1.2.3 From 44fc73223eebaf4a0c970072819d34ff1d92ce7b Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Tue, 31 Mar 2020 18:54:49 +0200 Subject: thermal: core: Remove pointless debug traces The last temperature and the current temperature are show via a dev_debug. The line before, those temperature are also traced. It is pointless to duplicate the traces for the temperatures, remove the dev_dbg traces. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Reviewed-by: Amit Kucheria Link: https://lore.kernel.org/r/20200331165449.30355-2-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 9a321dc548c8..c06550930979 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -447,12 +447,6 @@ static void update_temperature(struct thermal_zone_device *tz) mutex_unlock(&tz->lock); trace_thermal_temperature(tz); - if (tz->last_temperature == THERMAL_TEMP_INVALID) - dev_dbg(&tz->device, "last_temperature N/A, current_temperature=%d\n", - tz->temperature); - else - dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n", - tz->last_temperature, tz->temperature); } static void thermal_zone_device_init(struct thermal_zone_device *tz) -- cgit v1.2.3 From 04fa9c804b0ed50a3c6bae1d4b60a2494c81a23f Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Wed, 18 Mar 2020 11:45:46 +0000 Subject: thermal: devfreq_cooling: Use PM QoS to set frequency limits Now that devfreq supports limiting the frequency range of a device through PM QoS make use of it instead of disabling OPPs that should not be used. The switch from disabling OPPs to PM QoS introduces a subtle behavioral change in case of conflicting requests (min > max): PM QoS gives precedence to the MIN_FREQUENCY request, while higher OPPs disabled with dev_pm_opp_disable() would override MIN_FREQUENCY. Signed-off-by: Matthias Kaehlcke Reviewed-by: Lukasz Luba Reviewed-by: Chanwoo Choi Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200318114548.19916-4-lukasz.luba@arm.com --- drivers/thermal/devfreq_cooling.c | 70 +++++++++++++-------------------------- 1 file changed, 23 insertions(+), 47 deletions(-) diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index a87d4fa031c8..f7f32e98331b 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -24,11 +24,13 @@ #include #include #include +#include #include #include -#define SCALE_ERROR_MITIGATION 100 +#define HZ_PER_KHZ 1000 +#define SCALE_ERROR_MITIGATION 100 static DEFINE_IDA(devfreq_ida); @@ -54,6 +56,8 @@ static DEFINE_IDA(devfreq_ida); * The 'res_util' range is from 100 to (power_table[state] * 100) * for the corresponding 'state'. * @capped_state: index to cooling state with in dynamic power budget + * @req_max_freq: PM QoS request for limiting the maximum frequency + * of the devfreq device. */ struct devfreq_cooling_device { int id; @@ -66,49 +70,9 @@ struct devfreq_cooling_device { struct devfreq_cooling_power *power_ops; u32 res_util; int capped_state; + struct dev_pm_qos_request req_max_freq; }; -/** - * partition_enable_opps() - disable all opps above a given state - * @dfc: Pointer to devfreq we are operating on - * @cdev_state: cooling device state we're setting - * - * Go through the OPPs of the device, enabling all OPPs until - * @cdev_state and disabling those frequencies above it. - */ -static int partition_enable_opps(struct devfreq_cooling_device *dfc, - unsigned long cdev_state) -{ - int i; - struct device *dev = dfc->devfreq->dev.parent; - - for (i = 0; i < dfc->freq_table_size; i++) { - struct dev_pm_opp *opp; - int ret = 0; - unsigned int freq = dfc->freq_table[i]; - bool want_enable = i >= cdev_state ? true : false; - - opp = dev_pm_opp_find_freq_exact(dev, freq, !want_enable); - - if (PTR_ERR(opp) == -ERANGE) - continue; - else if (IS_ERR(opp)) - return PTR_ERR(opp); - - dev_pm_opp_put(opp); - - if (want_enable) - ret = dev_pm_opp_enable(dev, freq); - else - ret = dev_pm_opp_disable(dev, freq); - - if (ret) - return ret; - } - - return 0; -} - static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { @@ -135,7 +99,7 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev, struct devfreq_cooling_device *dfc = cdev->devdata; struct devfreq *df = dfc->devfreq; struct device *dev = df->dev.parent; - int ret; + unsigned long freq; if (state == dfc->cooling_state) return 0; @@ -145,9 +109,10 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev, if (state >= dfc->freq_table_size) return -EINVAL; - ret = partition_enable_opps(dfc, state); - if (ret) - return ret; + freq = dfc->freq_table[state]; + + dev_pm_qos_update_request(&dfc->req_max_freq, + DIV_ROUND_UP(freq, HZ_PER_KHZ)); dfc->cooling_state = state; @@ -530,9 +495,15 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, if (err) goto free_dfc; - err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL); + err = dev_pm_qos_add_request(df->dev.parent, &dfc->req_max_freq, + DEV_PM_QOS_MAX_FREQUENCY, + PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); if (err < 0) goto free_tables; + + err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL); + if (err < 0) + goto remove_qos_req; dfc->id = err; snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id); @@ -553,6 +524,10 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, release_ida: ida_simple_remove(&devfreq_ida, dfc->id); + +remove_qos_req: + dev_pm_qos_remove_request(&dfc->req_max_freq); + free_tables: kfree(dfc->power_table); kfree(dfc->freq_table); @@ -601,6 +576,7 @@ void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) thermal_cooling_device_unregister(dfc->cdev); ida_simple_remove(&devfreq_ida, dfc->id); + dev_pm_qos_remove_request(&dfc->req_max_freq); kfree(dfc->power_table); kfree(dfc->freq_table); -- cgit v1.2.3 From 8097db407a08f33236b6fa6ba8b62d669321c720 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:39 +0200 Subject: thermal: Move default governor config option to the internal header The default governor set at compilation time is a thermal internal business, no need to export to the global thermal header. Move the config options to the internal header. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-1-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 11 +++++++++++ include/linux/thermal.h | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 37cd4e2bead2..828305508556 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -12,6 +12,17 @@ #include #include +/* Default Thermal Governor */ +#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE) +#define DEFAULT_THERMAL_GOVERNOR "step_wise" +#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE) +#define DEFAULT_THERMAL_GOVERNOR "fair_share" +#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE) +#define DEFAULT_THERMAL_GOVERNOR "user_space" +#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR) +#define DEFAULT_THERMAL_GOVERNOR "power_allocator" +#endif + /* Initial state of a cooling device during binding */ #define THERMAL_NO_TARGET -1UL diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 448841ab0dca..71cff87dcb46 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -32,17 +32,6 @@ /* use value, which < 0K, to indicate an invalid/uninitialized temperature */ #define THERMAL_TEMP_INVALID -274000 -/* Default Thermal Governor */ -#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE) -#define DEFAULT_THERMAL_GOVERNOR "step_wise" -#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE) -#define DEFAULT_THERMAL_GOVERNOR "fair_share" -#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE) -#define DEFAULT_THERMAL_GOVERNOR "user_space" -#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR) -#define DEFAULT_THERMAL_GOVERNOR "power_allocator" -#endif - struct thermal_zone_device; struct thermal_cooling_device; struct thermal_instance; -- cgit v1.2.3 From c68df440b07f88210c5839d4507b5cbfa35e3df9 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:40 +0200 Subject: thermal: Move struct thermal_attr to the private header The structure belongs to the thermal core internals but it is exported in the include/linux/thermal.h For better self-encapsulation and less impact for the compilation if a change is made on it. Move the structure in the thermal core internal header file. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-2-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 5 +++++ include/linux/thermal.h | 6 +----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 828305508556..5d08ad60d9df 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -41,6 +41,11 @@ extern struct thermal_governor *__governor_thermal_table_end[]; __governor < __governor_thermal_table_end; \ __governor++) +struct thermal_attr { + struct device_attribute attr; + char name[THERMAL_NAME_LENGTH]; +}; + /* * This structure is used to describe the behavior of * a certain cooling device on a certain trip point diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 71cff87dcb46..5aa80fb2fb61 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -35,6 +35,7 @@ struct thermal_zone_device; struct thermal_cooling_device; struct thermal_instance; +struct thermal_attr; enum thermal_device_mode { THERMAL_DEVICE_DISABLED = 0, @@ -119,11 +120,6 @@ struct thermal_cooling_device { struct list_head node; }; -struct thermal_attr { - struct device_attribute attr; - char name[THERMAL_NAME_LENGTH]; -}; - /** * struct thermal_zone_device - structure for a thermal zone * @id: unique id number for each thermal zone -- cgit v1.2.3 From 33a88af10944edc7fd390000cd6bc9bbde918bc3 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:41 +0200 Subject: thermal: Move internal IPA functions The exported IPA functions are used by the IPA. It is pointless to declare the functions in the thermal.h file. For better self-encapsulation and less impact for the compilation if a change is made on it. Move the code in the thermal core internal header file. As the users depends on THERMAL then it is pointless to have the stub, remove them. Take also the opportunity to fix checkpatch warnings/errors when moving the code around. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-3-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 13 +++++++++++++ include/linux/thermal.h | 24 ------------------------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 5d08ad60d9df..f99551ce9838 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -46,6 +46,19 @@ struct thermal_attr { char name[THERMAL_NAME_LENGTH]; }; +static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev) +{ + return cdev->ops->get_requested_power && cdev->ops->state2power && + cdev->ops->power2state; +} + +int power_actor_get_max_power(struct thermal_cooling_device *cdev, + struct thermal_zone_device *tz, u32 *max_power); +int power_actor_get_min_power(struct thermal_cooling_device *cdev, + struct thermal_zone_device *tz, u32 *min_power); +int power_actor_set_power(struct thermal_cooling_device *cdev, + struct thermal_instance *ti, u32 power); + /* * This structure is used to describe the behavior of * a certain cooling device on a certain trip point diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 5aa80fb2fb61..e0279f7b43f4 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -399,18 +399,6 @@ void devm_thermal_zone_of_sensor_unregister(struct device *dev, #endif #if IS_ENABLED(CONFIG_THERMAL) -static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev) -{ - return cdev->ops->get_requested_power && cdev->ops->state2power && - cdev->ops->power2state; -} - -int power_actor_get_max_power(struct thermal_cooling_device *, - struct thermal_zone_device *tz, u32 *max_power); -int power_actor_get_min_power(struct thermal_cooling_device *, - struct thermal_zone_device *tz, u32 *min_power); -int power_actor_set_power(struct thermal_cooling_device *, - struct thermal_instance *, u32); struct thermal_zone_device *thermal_zone_device_register(const char *, int, int, void *, struct thermal_zone_device_ops *, struct thermal_zone_params *, int, int); @@ -447,18 +435,6 @@ struct thermal_instance *get_thermal_instance(struct thermal_zone_device *, void thermal_cdev_update(struct thermal_cooling_device *); void thermal_notify_framework(struct thermal_zone_device *, int); #else -static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev) -{ return false; } -static inline int power_actor_get_max_power(struct thermal_cooling_device *cdev, - struct thermal_zone_device *tz, u32 *max_power) -{ return 0; } -static inline int power_actor_get_min_power(struct thermal_cooling_device *cdev, - struct thermal_zone_device *tz, - u32 *min_power) -{ return -ENODEV; } -static inline int power_actor_set_power(struct thermal_cooling_device *cdev, - struct thermal_instance *tz, u32 power) -{ return 0; } static inline struct thermal_zone_device *thermal_zone_device_register( const char *type, int trips, int mask, void *devdata, struct thermal_zone_device_ops *ops, -- cgit v1.2.3 From 2e7700dc336dde93cdc9394d10ccd79593fff214 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:42 +0200 Subject: thermal: Move trip point structure definition to private header The struct thermal_trip is only used by the thermal internals, it is pointless to export the definition in the global header. Move the structure to the thermal_core.h internal header. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-4-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 13 +++++++++++++ include/linux/thermal.h | 15 --------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index f99551ce9838..d37de708c28a 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -58,6 +58,19 @@ int power_actor_get_min_power(struct thermal_cooling_device *cdev, struct thermal_zone_device *tz, u32 *min_power); int power_actor_set_power(struct thermal_cooling_device *cdev, struct thermal_instance *ti, u32 power); +/** + * struct thermal_trip - representation of a point in temperature domain + * @np: pointer to struct device_node that this trip point was created from + * @temperature: temperature value in miliCelsius + * @hysteresis: relative hysteresis in miliCelsius + * @type: trip point type + */ +struct thermal_trip { + struct device_node *np; + int temperature; + int hysteresis; + enum thermal_trip_type type; +}; /* * This structure is used to describe the behavior of diff --git a/include/linux/thermal.h b/include/linux/thermal.h index e0279f7b43f4..7adbfe092281 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -332,21 +332,6 @@ struct thermal_zone_of_device_ops { int (*set_trip_temp)(void *, int, int); }; -/** - * struct thermal_trip - representation of a point in temperature domain - * @np: pointer to struct device_node that this trip point was created from - * @temperature: temperature value in miliCelsius - * @hysteresis: relative hysteresis in miliCelsius - * @type: trip point type - */ - -struct thermal_trip { - struct device_node *np; - int temperature; - int hysteresis; - enum thermal_trip_type type; -}; - /* Function declarations */ #ifdef CONFIG_THERMAL_OF int thermal_zone_of_get_sensor_id(struct device_node *tz_np, -- cgit v1.2.3 From f0129c231772a85a726b233756cdc58ea42a9d84 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:43 +0200 Subject: thermal: Move get_tz_trend to the internal header The function is not used any place other than the thermal directory. It does not make sense to export its definition in the global header as there is no use of it. Move the definition to the internal header and allow better self-encapsulation. Take the opportunity to add the parameter names to make checkpatch happy and remove the pointless stubs. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-5-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 2 ++ include/linux/thermal.h | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index d37de708c28a..5fb2bd9c7034 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -72,6 +72,8 @@ struct thermal_trip { enum thermal_trip_type type; }; +int get_tz_trend(struct thermal_zone_device *tz, int trip); + /* * This structure is used to describe the behavior of * a certain cooling device on a certain trip point diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 7adbfe092281..8006ba5de855 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -414,7 +414,6 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp); int thermal_zone_get_slope(struct thermal_zone_device *tz); int thermal_zone_get_offset(struct thermal_zone_device *tz); -int get_tz_trend(struct thermal_zone_device *, int); struct thermal_instance *get_thermal_instance(struct thermal_zone_device *, struct thermal_cooling_device *, int); void thermal_cdev_update(struct thermal_cooling_device *); @@ -473,8 +472,7 @@ static inline int thermal_zone_get_slope( static inline int thermal_zone_get_offset( struct thermal_zone_device *tz) { return -ENODEV; } -static inline int get_tz_trend(struct thermal_zone_device *tz, int trip) -{ return -ENODEV; } + static inline struct thermal_instance * get_thermal_instance(struct thermal_zone_device *tz, struct thermal_cooling_device *cdev, int trip) -- cgit v1.2.3 From 06f1041f5023c00a54f63c269b997c61d1b3b739 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:44 +0200 Subject: thermal: Move get_thermal_instance to the internal header The function is not used any place other than the thermal directory. It does not make sense to export its definition in the global header as there is no use of it. Move the definition to the internal header and allow better self-encapsulation. Take the opportunity to add the parameter names to make checkpatch happy and remove the pointless stubs. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-6-daniel.lezcano@linaro.org --- drivers/thermal/thermal_core.h | 5 +++++ include/linux/thermal.h | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 5fb2bd9c7034..c95689586e19 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -74,6 +74,11 @@ struct thermal_trip { int get_tz_trend(struct thermal_zone_device *tz, int trip); +struct thermal_instance * +get_thermal_instance(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev, + int trip); + /* * This structure is used to describe the behavior of * a certain cooling device on a certain trip point diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 8006ba5de855..47e745c5dfca 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -414,8 +414,6 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp); int thermal_zone_get_slope(struct thermal_zone_device *tz); int thermal_zone_get_offset(struct thermal_zone_device *tz); -struct thermal_instance *get_thermal_instance(struct thermal_zone_device *, - struct thermal_cooling_device *, int); void thermal_cdev_update(struct thermal_cooling_device *); void thermal_notify_framework(struct thermal_zone_device *, int); #else @@ -473,10 +471,6 @@ static inline int thermal_zone_get_offset( struct thermal_zone_device *tz) { return -ENODEV; } -static inline struct thermal_instance * -get_thermal_instance(struct thermal_zone_device *tz, - struct thermal_cooling_device *cdev, int trip) -{ return ERR_PTR(-ENODEV); } static inline void thermal_cdev_update(struct thermal_cooling_device *cdev) { } static inline void thermal_notify_framework(struct thermal_zone_device *tz, -- cgit v1.2.3 From 60518260cab21e749704baa5246ff13f7559fa91 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:45 +0200 Subject: thermal: Change IS_ENABLED to IFDEF in the header file The thermal framework can not be compiled as a module. The IS_ENABLED macro is useless here and can be replaced by an ifdef. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-7-daniel.lezcano@linaro.org --- include/linux/thermal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 47e745c5dfca..12df9ff0182d 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -383,7 +383,7 @@ void devm_thermal_zone_of_sensor_unregister(struct device *dev, #endif -#if IS_ENABLED(CONFIG_THERMAL) +#ifdef CONFIG_THERMAL struct thermal_zone_device *thermal_zone_device_register(const char *, int, int, void *, struct thermal_zone_device_ops *, struct thermal_zone_params *, int, int); -- cgit v1.2.3 From 708418500644c248d6f266e4df0bf43ce53bf746 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:46 +0200 Subject: thermal: Remove stubs for thermal_zone_[un]bind_cooling_device All callers of the functions depends on THERMAL, it is pointless to define stubs. Remove them. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-8-daniel.lezcano@linaro.org --- include/linux/thermal.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 12df9ff0182d..7b3dbfe15b59 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -426,16 +426,6 @@ static inline struct thermal_zone_device *thermal_zone_device_register( static inline void thermal_zone_device_unregister( struct thermal_zone_device *tz) { } -static inline int thermal_zone_bind_cooling_device( - struct thermal_zone_device *tz, int trip, - struct thermal_cooling_device *cdev, - unsigned long upper, unsigned long lower, - unsigned int weight) -{ return -ENODEV; } -static inline int thermal_zone_unbind_cooling_device( - struct thermal_zone_device *tz, int trip, - struct thermal_cooling_device *cdev) -{ return -ENODEV; } static inline void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { } -- cgit v1.2.3 From 0145f67866b71e8c1da3c1d9412623db7ba8a0c8 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Thu, 2 Apr 2020 16:27:47 +0200 Subject: thermal: Remove thermal_zone_device_update() stub All users of the function depends on THERMAL, no stub is needed. Remove it. Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Acked-by: Zhang Rui Link: https://lore.kernel.org/r/20200402142747.8307-9-daniel.lezcano@linaro.org --- include/linux/thermal.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 7b3dbfe15b59..216185bb3014 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -426,9 +426,6 @@ static inline struct thermal_zone_device *thermal_zone_device_register( static inline void thermal_zone_device_unregister( struct thermal_zone_device *tz) { } -static inline void thermal_zone_device_update(struct thermal_zone_device *tz, - enum thermal_notify_event event) -{ } static inline struct thermal_cooling_device * thermal_cooling_device_register(char *type, void *devdata, const struct thermal_cooling_device_ops *ops) -- cgit v1.2.3 From 8cb775bb005c568857ba7909a1c5a297ed4c33ee Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 5 Apr 2020 18:35:16 +0200 Subject: thermal: Delete an error message in four functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function “platform_get_irq” can log an error already. Thus omit redundant messages for the exception handling in the calling functions. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Reviewed-by: Amit Kucheria Reviewed-by: Keerthy Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/05f49ae7-5cc7-d6a0-fc3d-abaf2a0b373c@web.de --- drivers/thermal/rockchip_thermal.c | 4 +--- drivers/thermal/st/st_thermal_memmap.c | 4 +--- drivers/thermal/st/stm_thermal.c | 4 +--- drivers/thermal/ti-soc-thermal/ti-bandgap.c | 5 ++--- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/thermal/rockchip_thermal.c b/drivers/thermal/rockchip_thermal.c index 7c1a8bccdcba..15a71ecc916c 100644 --- a/drivers/thermal/rockchip_thermal.c +++ b/drivers/thermal/rockchip_thermal.c @@ -1241,10 +1241,8 @@ static int rockchip_thermal_probe(struct platform_device *pdev) return -ENXIO; irq = platform_get_irq(pdev, 0); - if (irq < 0) { - dev_err(&pdev->dev, "no irq resource?\n"); + if (irq < 0) return -EINVAL; - } thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data), GFP_KERNEL); diff --git a/drivers/thermal/st/st_thermal_memmap.c b/drivers/thermal/st/st_thermal_memmap.c index a824b78dabf8..a0114452d11f 100644 --- a/drivers/thermal/st/st_thermal_memmap.c +++ b/drivers/thermal/st/st_thermal_memmap.c @@ -94,10 +94,8 @@ static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor) int ret; sensor->irq = platform_get_irq(pdev, 0); - if (sensor->irq < 0) { - dev_err(dev, "failed to register IRQ\n"); + if (sensor->irq < 0) return sensor->irq; - } ret = devm_request_threaded_irq(dev, sensor->irq, NULL, st_mmap_thermal_trip_handler, diff --git a/drivers/thermal/st/stm_thermal.c b/drivers/thermal/st/stm_thermal.c index 9314e3df6a42..331e2b768df5 100644 --- a/drivers/thermal/st/stm_thermal.c +++ b/drivers/thermal/st/stm_thermal.c @@ -385,10 +385,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor) int ret; sensor->irq = platform_get_irq(pdev, 0); - if (sensor->irq < 0) { - dev_err(dev, "%s: Unable to find IRQ\n", __func__); + if (sensor->irq < 0) return sensor->irq; - } ret = devm_request_threaded_irq(dev, sensor->irq, NULL, diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 263b0420fbe4..ab19ceff6e2a 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -772,10 +772,9 @@ static int ti_bandgap_talert_init(struct ti_bandgap *bgp, int ret; bgp->irq = platform_get_irq(pdev, 0); - if (bgp->irq < 0) { - dev_err(&pdev->dev, "get_irq failed\n"); + if (bgp->irq < 0) return bgp->irq; - } + ret = request_threaded_irq(bgp->irq, NULL, ti_bandgap_talert_irq_handler, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, -- cgit v1.2.3 From 3dc748754d68b5893307c3c68acfc67f76110ecf Mon Sep 17 00:00:00 2001 From: Keerthy Date: Tue, 7 Apr 2020 11:21:13 +0530 Subject: dt-bindings: thermal: k3: Add VTM bindings documentation Add VTM bindings documentation. In the Voltage Thermal Management Module(VTM), K3 AM654 supplies a voltage reference and a temperature sensor feature that are gathered in the band gap voltage and temperature sensor (VBGAPTS) module. The band gap provides current and voltage reference for its internal circuits and other analog IP blocks. The analog-to-digital converter (ADC) produces an output value that is proportional to the silicon temperature. Signed-off-by: Keerthy Reviewed-by: Rob Herring Reviewed-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200407055116.16082-2-j-keerthy@ti.com --- .../bindings/thermal/ti,am654-thermal.yaml | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/ti,am654-thermal.yaml diff --git a/Documentation/devicetree/bindings/thermal/ti,am654-thermal.yaml b/Documentation/devicetree/bindings/thermal/ti,am654-thermal.yaml new file mode 100644 index 000000000000..25b9209c2e5d --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/ti,am654-thermal.yaml @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/ti,am654-thermal.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments AM654 VTM (DTS) binding + +maintainers: + - Keerthy + +properties: + compatible: + const: ti,am654-vtm + + reg: + maxItems: 1 + + power-domains: + maxItems: 1 + + "#thermal-sensor-cells": + const: 1 + +required: + - compatible + - reg + - power-domains + - "#thermal-sensor-cells" + +additionalProperties: false + +examples: + - | + #include + vtm: thermal@42050000 { + compatible = "ti,am654-vtm"; + reg = <0x0 0x42050000 0x0 0x25c>; + power-domains = <&k3_pds 80 TI_SCI_PD_EXCLUSIVE>; + #thermal-sensor-cells = <1>; + }; + + mpu0_thermal: mpu0_thermal { + polling-delay-passive = <250>; /* milliseconds */ + polling-delay = <500>; /* milliseconds */ + thermal-sensors = <&vtm0 0>; + + trips { + mpu0_crit: mpu0_crit { + temperature = <125000>; /* milliCelsius */ + hysteresis = <2000>; /* milliCelsius */ + type = "critical"; + }; + }; + }; +... -- cgit v1.2.3 From 48b2bce8c7db92601145e1204fa7048b1f74e442 Mon Sep 17 00:00:00 2001 From: Keerthy Date: Tue, 7 Apr 2020 11:21:14 +0530 Subject: thermal: k3: Add support for bandgap sensors Add VTM thermal support. In the Voltage Thermal Management Module(VTM), K3 AM654 supplies a voltage reference and a temperature sensor feature that are gathered in the band gap voltage and temperature sensor (VBGAPTS) module. The band gap provides current and voltage reference for its internal circuits and other analog IP blocks. The analog-to-digital converter (ADC) produces an output value that is proportional to the silicon temperature. Currently reading temperatures only is supported. There are no active/passive cooling agent supported. Signed-off-by: Keerthy Reviewed-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200407055116.16082-3-j-keerthy@ti.com --- drivers/thermal/Kconfig | 10 ++ drivers/thermal/Makefile | 1 + drivers/thermal/k3_bandgap.c | 264 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 drivers/thermal/k3_bandgap.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 91af271e9bb0..e53314ea9e25 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -273,6 +273,16 @@ config IMX8MM_THERMAL cpufreq is used as the cooling device to throttle CPUs when the passive trip is crossed. +config K3_THERMAL + tristate "Texas Instruments K3 thermal support" + depends on ARCH_K3 || COMPILE_TEST + help + If you say yes here you get thermal support for the Texas Instruments + K3 SoC family. The current chip supported is: + - AM654 + + This includes temperature reading functionality. + config MAX77620_THERMAL tristate "Temperature sensor driver for Maxim MAX77620 PMIC" depends on MFD_MAX77620 diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 8c8ed7b79915..86c506410cc0 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -28,6 +28,7 @@ thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o # devfreq cooling thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o +obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o # platform thermal drivers obj-y += broadcom/ obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o diff --git a/drivers/thermal/k3_bandgap.c b/drivers/thermal/k3_bandgap.c new file mode 100644 index 000000000000..35f41e8a0b75 --- /dev/null +++ b/drivers/thermal/k3_bandgap.c @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TI Bandgap temperature sensor driver for K3 SoC Family + * + * Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4 +#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0 +#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80 +#define K3_VTM_REGS_PER_TS 0x10 +#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff +#define K3_VTM_TMPSENS_CTRL_CBIASSEL BIT(0) +#define K3_VTM_TMPSENS_CTRL_SOC BIT(5) +#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6) +#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7) + +#define K3_VTM_ADC_BEGIN_VAL 540 +#define K3_VTM_ADC_END_VAL 944 + +static const int k3_adc_to_temp[] = { + -40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200, + -37800, -37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200, + -33800, -33400, -33000, -32600, -32200, -31800, -31400, -31000, -30600, + -30200, -29800, -29400, -29000, -28600, -28200, -27700, -27100, -26600, + -26200, -25800, -25400, -25000, -24600, -24200, -23800, -23400, -23000, + -22600, -22200, -21800, -21400, -21000, -20500, -19900, -19400, -19000, + -18600, -18200, -17800, -17400, -17000, -16600, -16200, -15800, -15400, + -15000, -14600, -14200, -13800, -13400, -13000, -12500, -11900, -11400, + -11000, -10600, -10200, -9800, -9400, -9000, -8600, -8200, -7800, -7400, + -7000, -6600, -6200, -5800, -5400, -5000, -4500, -3900, -3400, -3000, + -2600, -2200, -1800, -1400, -1000, -600, -200, 200, 600, 1000, 1400, + 1800, 2200, 2600, 3000, 3400, 3900, 4500, 5000, 5400, 5800, 6200, 6600, + 7000, 7400, 7800, 8200, 8600, 9000, 9400, 9800, 10200, 10600, 11000, + 11400, 11800, 12200, 12700, 13300, 13800, 14200, 14600, 15000, 15400, + 15800, 16200, 16600, 17000, 17400, 17800, 18200, 18600, 19000, 19400, + 19800, 20200, 20600, 21000, 21400, 21900, 22500, 23000, 23400, 23800, + 24200, 24600, 25000, 25400, 25800, 26200, 26600, 27000, 27400, 27800, + 28200, 28600, 29000, 29400, 29800, 30200, 30600, 31000, 31400, 31900, + 32500, 33000, 33400, 33800, 34200, 34600, 35000, 35400, 35800, 36200, + 36600, 37000, 37400, 37800, 38200, 38600, 39000, 39400, 39800, 40200, + 40600, 41000, 41400, 41800, 42200, 42600, 43100, 43700, 44200, 44600, + 45000, 45400, 45800, 46200, 46600, 47000, 47400, 47800, 48200, 48600, + 49000, 49400, 49800, 50200, 50600, 51000, 51400, 51800, 52200, 52600, + 53000, 53400, 53800, 54200, 54600, 55000, 55400, 55900, 56500, 57000, + 57400, 57800, 58200, 58600, 59000, 59400, 59800, 60200, 60600, 61000, + 61400, 61800, 62200, 62600, 63000, 63400, 63800, 64200, 64600, 65000, + 65400, 65800, 66200, 66600, 67000, 67400, 67800, 68200, 68600, 69000, + 69400, 69800, 70200, 70600, 71000, 71500, 72100, 72600, 73000, 73400, + 73800, 74200, 74600, 75000, 75400, 75800, 76200, 76600, 77000, 77400, + 77800, 78200, 78600, 79000, 79400, 79800, 80200, 80600, 81000, 81400, + 81800, 82200, 82600, 83000, 83400, 83800, 84200, 84600, 85000, 85400, + 85800, 86200, 86600, 87000, 87400, 87800, 88200, 88600, 89000, 89400, + 89800, 90200, 90600, 91000, 91400, 91800, 92200, 92600, 93000, 93400, + 93800, 94200, 94600, 95000, 95400, 95800, 96200, 96600, 97000, 97500, + 98100, 98600, 99000, 99400, 99800, 100200, 100600, 101000, 101400, + 101800, 102200, 102600, 103000, 103400, 103800, 104200, 104600, 105000, + 105400, 105800, 106200, 106600, 107000, 107400, 107800, 108200, 108600, + 109000, 109400, 109800, 110200, 110600, 111000, 111400, 111800, 112200, + 112600, 113000, 113400, 113800, 114200, 114600, 115000, 115400, 115800, + 116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000, 119400, + 119800, 120200, 120600, 121000, 121400, 121800, 122200, 122600, 123000, + 123400, 123800, 124200, 124600, 124900, 125000, +}; + +struct k3_bandgap { + void __iomem *base; + const struct k3_bandgap_data *conf; +}; + +/* common data structures */ +struct k3_thermal_data { + struct thermal_zone_device *tzd; + struct k3_bandgap *bgp; + int sensor_id; + u32 ctrl_offset; + u32 stat_offset; +}; + +static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1, + unsigned int s2) +{ + int d01 = abs(s0 - s1); + int d02 = abs(s0 - s2); + int d12 = abs(s1 - s2); + + if (d01 <= d02 && d01 <= d12) + return (s0 + s1) / 2; + + if (d02 <= d01 && d02 <= d12) + return (s0 + s2) / 2; + + return (s1 + s2) / 2; +} + +static int k3_bgp_read_temp(struct k3_thermal_data *devdata, + int *temp) +{ + struct k3_bandgap *bgp; + unsigned int dtemp, s0, s1, s2; + + bgp = devdata->bgp; + + /* + * Errata is applicable for am654 pg 1.0 silicon. There + * is a variation of the order for 8-10 degree centigrade. + * Work around that by getting the average of two closest + * readings out of three readings everytime we want to + * report temperatures. + * + * Errata workaround. + */ + s0 = readl(bgp->base + devdata->stat_offset) & + K3_VTM_TS_STAT_DTEMP_MASK; + s1 = readl(bgp->base + devdata->stat_offset) & + K3_VTM_TS_STAT_DTEMP_MASK; + s2 = readl(bgp->base + devdata->stat_offset) & + K3_VTM_TS_STAT_DTEMP_MASK; + dtemp = vtm_get_best_value(s0, s1, s2); + + if (dtemp < K3_VTM_ADC_BEGIN_VAL || dtemp > K3_VTM_ADC_END_VAL) + return -EINVAL; + + *temp = k3_adc_to_temp[dtemp - K3_VTM_ADC_BEGIN_VAL]; + + return 0; +} + +static int k3_thermal_get_temp(void *devdata, int *temp) +{ + struct k3_thermal_data *data = devdata; + int ret = 0; + + ret = k3_bgp_read_temp(data, temp); + if (ret) + return ret; + + return ret; +} + +static const struct thermal_zone_of_device_ops k3_of_thermal_ops = { + .get_temp = k3_thermal_get_temp, +}; + +static const struct of_device_id of_k3_bandgap_match[]; + +static int k3_bandgap_probe(struct platform_device *pdev) +{ + int ret = 0, cnt, val, id; + struct resource *res; + struct device *dev = &pdev->dev; + struct k3_bandgap *bgp; + struct k3_thermal_data *data; + + if (ARRAY_SIZE(k3_adc_to_temp) != (K3_VTM_ADC_END_VAL + 1 - + K3_VTM_ADC_BEGIN_VAL)) + return -EINVAL; + + bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL); + if (!bgp) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + bgp->base = devm_ioremap_resource(dev, res); + if (IS_ERR(bgp->base)) + return PTR_ERR(bgp->base); + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + pm_runtime_put_noidle(dev); + pm_runtime_disable(dev); + return ret; + } + + /* Get the sensor count in the VTM */ + val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET); + cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK; + cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK); + + data = devm_kcalloc(dev, cnt, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err_alloc; + } + + /* Register the thermal sensors */ + for (id = 0; id < cnt; id++) { + data[id].sensor_id = id; + data[id].bgp = bgp; + data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET + + id * K3_VTM_REGS_PER_TS; + data[id].stat_offset = data[id].ctrl_offset + 0x8; + + val = readl(data[id].bgp->base + data[id].ctrl_offset); + val |= (K3_VTM_TMPSENS_CTRL_SOC | + K3_VTM_TMPSENS_CTRL_CLRZ | + K3_VTM_TMPSENS_CTRL_CLKON_REQ); + val &= ~K3_VTM_TMPSENS_CTRL_CBIASSEL; + writel(val, data[id].bgp->base + data[id].ctrl_offset); + + data[id].tzd = + devm_thermal_zone_of_sensor_register(dev, id, + &data[id], + &k3_of_thermal_ops); + if (IS_ERR(data[id].tzd)) { + dev_err(dev, "thermal zone device is NULL\n"); + ret = PTR_ERR(data[id].tzd); + goto err_alloc; + } + } + + platform_set_drvdata(pdev, bgp); + + return 0; + +err_alloc: + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + return ret; +} + +static int k3_bandgap_remove(struct platform_device *pdev) +{ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id of_k3_bandgap_match[] = { + { + .compatible = "ti,am654-vtm", + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, of_k3_bandgap_match); + +static struct platform_driver k3_bandgap_sensor_driver = { + .probe = k3_bandgap_probe, + .remove = k3_bandgap_remove, + .driver = { + .name = "k3-soc-thermal", + .of_match_table = of_k3_bandgap_match, + }, +}; + +module_platform_driver(k3_bandgap_sensor_driver); + +MODULE_DESCRIPTION("K3 bandgap temperature sensor driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("J Keerthy "); -- cgit v1.2.3 From 79799562bf087b30d9dd0fddf5bed2d3b038be08 Mon Sep 17 00:00:00 2001 From: Andrzej Pietrasiewicz Date: Tue, 14 Apr 2020 20:00:57 +0200 Subject: thermal: int3400_thermal: Statically initialize .get_mode()/.set_mode() ops int3400_thermal_ops is used inside int3400_thermal_probe() only after the assignments, which can just as well be made statically at struct's initizer. Signed-off-by: Andrzej Pietrasiewicz Reviewed-by: Bartlomiej Zolnierkiewicz Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200414180105.20042-2-andrzej.p@collabora.com --- drivers/thermal/intel/int340x_thermal/int3400_thermal.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index ceef89c956bd..e802922a13cf 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -271,6 +271,8 @@ static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, static struct thermal_zone_device_ops int3400_thermal_ops = { .get_temp = int3400_thermal_get_temp, + .get_mode = int3400_thermal_get_mode, + .set_mode = int3400_thermal_set_mode, }; static struct thermal_zone_params int3400_thermal_params = { @@ -309,9 +311,6 @@ static int int3400_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); - int3400_thermal_ops.get_mode = int3400_thermal_get_mode; - int3400_thermal_ops.set_mode = int3400_thermal_set_mode; - priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, priv, &int3400_thermal_ops, &int3400_thermal_params, 0, 0); -- cgit v1.2.3 From 770ae40cd6d23eb331572b8913f51dc715c9d460 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Sun, 16 Feb 2020 14:02:52 +0100 Subject: MAINTAINERS: Add entry for Renesas R-Car thermal drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an entry to make myself a maintainer of the Renesas R-Car thermal drivers. Signed-off-by: Niklas Söderlund Acked-by: Yoshihiro Shimoda Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200216130252.125100-1-niklas.soderlund+renesas@ragnatech.se --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e64e5db31497..728c6cf55c35 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14366,6 +14366,15 @@ F: Documentation/devicetree/bindings/i2c/renesas,iic.txt F: drivers/i2c/busses/i2c-rcar.c F: drivers/i2c/busses/i2c-sh_mobile.c +RENESAS R-CAR THERMAL DRIVERS +M: Niklas Söderlund +L: linux-renesas-soc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/thermal/rcar-gen3-thermal.txt +F: Documentation/devicetree/bindings/thermal/rcar-thermal.txt +F: drivers/thermal/rcar_gen3_thermal.c +F: drivers/thermal/rcar_thermal.c + RENESAS RIIC DRIVER M: Chris Brandt S: Supported -- cgit v1.2.3 From 7440f518dad9d861d76c64956641eeddd3586f75 Mon Sep 17 00:00:00 2001 From: Sudip Mukherjee Date: Fri, 24 Apr 2020 17:19:44 +0100 Subject: thermal/drivers/ti-soc-thermal: Avoid dereferencing ERR_PTR On error the function ti_bandgap_get_sensor_data() returns the error code in ERR_PTR() but we only checked if the return value is NULL or not. And, so we can dereference an error code inside ERR_PTR. While at it, convert a check to IS_ERR_OR_NULL. Signed-off-by: Sudip Mukherjee Reviewed-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200424161944.6044-1-sudipm.mukherjee@gmail.com --- drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index d3e959d01606..85776db4bf34 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -169,7 +169,7 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id, data = ti_bandgap_get_sensor_data(bgp, id); - if (!data || IS_ERR(data)) + if (!IS_ERR_OR_NULL(data)) data = ti_thermal_build_data(bgp, id); if (!data) @@ -196,7 +196,7 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) data = ti_bandgap_get_sensor_data(bgp, id); - if (data && data->ti_thermal) { + if (!IS_ERR_OR_NULL(data) && data->ti_thermal) { if (data->our_zone) thermal_zone_device_unregister(data->ti_thermal); } @@ -262,7 +262,7 @@ int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id) data = ti_bandgap_get_sensor_data(bgp, id); - if (data) { + if (!IS_ERR_OR_NULL(data)) { cpufreq_cooling_unregister(data->cool_dev); if (data->policy) cpufreq_cpu_put(data->policy); -- cgit v1.2.3 From 333cff6c963fbc8b9820ca2b6a8b2e22a572cd43 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Wed, 29 Apr 2020 12:36:39 +0200 Subject: powercap/drivers/idle_inject: Specify idle state max latency Currently the idle injection framework uses the play_idle() function which puts the current CPU in an idle state. The idle state is the deepest one, as specified by the latency constraint when calling the subsequent play_idle_precise() function with the INT_MAX. The idle_injection is used by the cpuidle_cooling device which computes the idle / run duration to mitigate the temperature by injecting idle cycles. The cooling device has no control on the depth of the idle state. Allow finer control of the idle injection mechanism by allowing to specify the latency for the idle state. Thus the cooling device has the ability to have a guarantee on the exit latency of the idle states it is injecting. Acked-by: Rafael J. Wysocki Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Link: https://lore.kernel.org/r/20200429103644.5492-1-daniel.lezcano@linaro.org --- drivers/powercap/idle_inject.c | 16 +++++++++++++++- include/linux/idle_inject.h | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/powercap/idle_inject.c b/drivers/powercap/idle_inject.c index e9bbd3c42eef..c90f0990968b 100644 --- a/drivers/powercap/idle_inject.c +++ b/drivers/powercap/idle_inject.c @@ -61,12 +61,14 @@ struct idle_inject_thread { * @timer: idle injection period timer * @idle_duration_us: duration of CPU idle time to inject * @run_duration_us: duration of CPU run time to allow + * @latency_us: max allowed latency * @cpumask: mask of CPUs affected by idle injection */ struct idle_inject_device { struct hrtimer timer; unsigned int idle_duration_us; unsigned int run_duration_us; + unsigned int latency_us; unsigned long cpumask[]; }; @@ -138,7 +140,8 @@ static void idle_inject_fn(unsigned int cpu) */ iit->should_run = 0; - play_idle(READ_ONCE(ii_dev->idle_duration_us)); + play_idle_precise(READ_ONCE(ii_dev->idle_duration_us) * NSEC_PER_USEC, + READ_ONCE(ii_dev->latency_us) * NSEC_PER_USEC); } /** @@ -169,6 +172,16 @@ void idle_inject_get_duration(struct idle_inject_device *ii_dev, *idle_duration_us = READ_ONCE(ii_dev->idle_duration_us); } +/** + * idle_inject_set_latency - set the maximum latency allowed + * @latency_us: set the latency requirement for the idle state + */ +void idle_inject_set_latency(struct idle_inject_device *ii_dev, + unsigned int latency_us) +{ + WRITE_ONCE(ii_dev->latency_us, latency_us); +} + /** * idle_inject_start - start idle injections * @ii_dev: idle injection control device structure @@ -297,6 +310,7 @@ struct idle_inject_device *idle_inject_register(struct cpumask *cpumask) cpumask_copy(to_cpumask(ii_dev->cpumask), cpumask); hrtimer_init(&ii_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ii_dev->timer.function = idle_inject_timer_fn; + ii_dev->latency_us = UINT_MAX; for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) { diff --git a/include/linux/idle_inject.h b/include/linux/idle_inject.h index a445cd1a36c5..91a8612b8bf9 100644 --- a/include/linux/idle_inject.h +++ b/include/linux/idle_inject.h @@ -26,4 +26,8 @@ void idle_inject_set_duration(struct idle_inject_device *ii_dev, void idle_inject_get_duration(struct idle_inject_device *ii_dev, unsigned int *run_duration_us, unsigned int *idle_duration_us); + +void idle_inject_set_latency(struct idle_inject_device *ii_dev, + unsigned int latency_ns); + #endif /* __IDLE_INJECT_H__ */ -- cgit v1.2.3 From 3b25846fbbca2ee3aaa67fe5abb750806d28a98e Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Wed, 29 Apr 2020 12:36:40 +0200 Subject: dt-bindings: thermal: Add the idle cooling device Some devices are not able to cool down by reducing their voltage / frequency because it could be not available or the system does not allow voltage scaling. In this configuration, it is not possible to use this strategy and the idle injection cooling device can be used instead. One idle cooling device is now present for the CPU as implemented by the combination of the idle injection framework belonging to the power capping framework and the thermal cooling device. The missing part is the DT binding providing a way to describe how the cooling device will work on the system. A first iteration was done by making the cooling device to point to the idle state. Unfortunately it does not make sense because it would need to duplicate the idle state description for each CPU in order to have a different phandle and make the thermal internal framework happy. It was proposed to add an cooling-cells to <3>, unfortunately the thermal framework is expecting a value of <2> as stated by the documentation and it is not possible from the cooling device generic code to loop this third value to the back end cooling device. Another proposal was to add a child 'thermal-idle' node as the SCMI does. This approach allows to have a self-contained configuration for the idle cooling device without colliding with the cpufreq cooling device which is based on the CPU node. In addition, it allows to have the cpufreq cooling device and the idle cooling device to co-exist together as shown in the example. Reviewed-by: Rob Herring Signed-off-by: Daniel Lezcano Reviewed-by: Amit Kucheria Link: https://lore.kernel.org/r/20200429103644.5492-2-daniel.lezcano@linaro.org --- .../devicetree/bindings/thermal/thermal-idle.yaml | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/thermal-idle.yaml diff --git a/Documentation/devicetree/bindings/thermal/thermal-idle.yaml b/Documentation/devicetree/bindings/thermal/thermal-idle.yaml new file mode 100644 index 000000000000..7a922f540934 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/thermal-idle.yaml @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright 2020 Linaro Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/thermal-idle.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Thermal idle cooling device binding + +maintainers: + - Daniel Lezcano + +description: | + The thermal idle cooling device allows the system to passively + mitigate the temperature on the device by injecting idle cycles, + forcing it to cool down. + + This binding describes the thermal idle node. + +properties: + $nodename: + const: thermal-idle + description: | + A thermal-idle node describes the idle cooling device properties to + cool down efficiently the attached thermal zone. + + '#cooling-cells': + const: 2 + description: | + Must be 2, in order to specify minimum and maximum cooling state used in + the cooling-maps reference. The first cell is the minimum cooling state + and the second cell is the maximum cooling state requested. + + duration-us: + description: | + The idle duration in microsecond the device should cool down. + + exit-latency-us: + description: | + The exit latency constraint in microsecond for the injected + idle state for the device. It is the latency constraint to + apply when selecting an idle state from among all the present + ones. + +required: + - '#cooling-cells' + +examples: + - | + #include + + // Example: Combining idle cooling device on big CPUs with cpufreq cooling device + cpus { + #address-cells = <2>; + #size-cells = <0>; + + /* ... */ + + cpu_b0: cpu@100 { + device_type = "cpu"; + compatible = "arm,cortex-a72"; + reg = <0x0 0x100>; + enable-method = "psci"; + capacity-dmips-mhz = <1024>; + dynamic-power-coefficient = <436>; + #cooling-cells = <2>; /* min followed by max */ + cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>; + thermal-idle { + #cooling-cells = <2>; + duration-us = <10000>; + exit-latency-us = <500>; + }; + }; + + cpu_b1: cpu@101 { + device_type = "cpu"; + compatible = "arm,cortex-a72"; + reg = <0x0 0x101>; + enable-method = "psci"; + capacity-dmips-mhz = <1024>; + dynamic-power-coefficient = <436>; + #cooling-cells = <2>; /* min followed by max */ + cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>; + thermal-idle { + #cooling-cells = <2>; + duration-us = <10000>; + exit-latency-us = <500>; + }; + }; + + /* ... */ + + }; + + /* ... */ + + thermal_zones { + cpu_thermal: cpu { + polling-delay-passive = <100>; + polling-delay = <1000>; + + /* ... */ + + trips { + cpu_alert0: cpu_alert0 { + temperature = <65000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu_alert1: cpu_alert1 { + temperature = <70000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu_alert2: cpu_alert2 { + temperature = <75000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu_crit: cpu_crit { + temperature = <95000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu_alert1>; + cooling-device = <&{/cpus/cpu@100/thermal-idle} 0 15 >, + <&{/cpus/cpu@101/thermal-idle} 0 15>; + }; + + map1 { + trip = <&cpu_alert2>; + cooling-device = + <&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; -- cgit v1.2.3 From dfd0bda3703cdaf1fccd5da72cb7101a4fedfe68 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Wed, 29 Apr 2020 12:36:41 +0200 Subject: thermal/drivers/cpuidle_cooling: Change the registration function Today, there is no user for the cpuidle cooling device. The targetted platform is ARM and ARM64. The cpuidle and the cpufreq cooling device are based on the device tree. As the cpuidle cooling device can have its own configuration depending on the platform and the available idle states. The DT node description will give the optional properties to set the cooling device up. Do no longer rely on the CPU node which is prone to error and will lead to a confusion in the DT because the cpufreq cooling device is also using it. Let initialize the cpuidle cooling device with the DT binding. This was tested on: - hikey960 - hikey6220 - rock960 - db845c Acked-by: Viresh Kumar Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Reviewed-by: Amit Kucheria Tested-by: Amit Kucheria Link: https://lore.kernel.org/r/20200429103644.5492-3-daniel.lezcano@linaro.org --- drivers/thermal/cpuidle_cooling.c | 63 +++++++++++++++++++++++++++++++-------- include/linux/cpu_cooling.h | 12 ++------ 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/drivers/thermal/cpuidle_cooling.c b/drivers/thermal/cpuidle_cooling.c index 0bb843246f59..78e3e8238116 100644 --- a/drivers/thermal/cpuidle_cooling.c +++ b/drivers/thermal/cpuidle_cooling.c @@ -5,11 +5,14 @@ * Author: Daniel Lezcano * */ +#define pr_fmt(fmt) "cpuidle cooling: " fmt + #include #include #include #include #include +#include #include #include @@ -154,22 +157,25 @@ static struct thermal_cooling_device_ops cpuidle_cooling_ops = { }; /** - * cpuidle_of_cooling_register - Idle cooling device initialization function + * __cpuidle_cooling_register: register the cooling device * @drv: a cpuidle driver structure pointer - * @np: a node pointer to a device tree cooling device node + * @np: a device node structure pointer used for the thermal binding * - * This function is in charge of creating a cooling device per cpuidle - * driver and register it to thermal framework. + * This function is in charge of allocating the cpuidle cooling device + * structure, the idle injection, initialize them and register the + * cooling device to the thermal framework. * - * Return: zero on success, or negative value corresponding to the - * error detected in the underlying subsystems. + * Return: zero on success, a negative value returned by one of the + * underlying subsystem in case of error */ -int cpuidle_of_cooling_register(struct device_node *np, - struct cpuidle_driver *drv) +static int __cpuidle_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; + unsigned int idle_duration_us = TICK_USEC; + unsigned int latency_us = UINT_MAX; char dev_name[THERMAL_NAME_LENGTH]; int id, ret; @@ -191,7 +197,11 @@ int cpuidle_of_cooling_register(struct device_node *np, goto out_id; } - idle_inject_set_duration(ii_dev, TICK_USEC, TICK_USEC); + of_property_read_u32(np, "duration-us", &idle_duration_us); + of_property_read_u32(np, "exit-latency-us", &latency_us); + + idle_inject_set_duration(ii_dev, TICK_USEC, idle_duration_us); + idle_inject_set_latency(ii_dev, latency_us); idle_cdev->ii_dev = ii_dev; @@ -204,6 +214,9 @@ int cpuidle_of_cooling_register(struct device_node *np, goto out_unregister; } + pr_debug("%s: Idle injection set with idle duration=%u, latency=%u\n", + dev_name, idle_duration_us, latency_us); + return 0; out_unregister: @@ -221,12 +234,38 @@ out: * @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. + * driver and register it to the 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) +void cpuidle_cooling_register(struct cpuidle_driver *drv) { - return cpuidle_of_cooling_register(NULL, drv); + struct device_node *cooling_node; + struct device_node *cpu_node; + int cpu, ret; + + for_each_cpu(cpu, drv->cpumask) { + + cpu_node = of_cpu_device_node_get(cpu); + + cooling_node = of_get_child_by_name(cpu_node, "thermal-idle"); + + of_node_put(cpu_node); + + if (!cooling_node) { + pr_debug("'thermal-idle' node not found for cpu%d\n", cpu); + continue; + } + + ret = __cpuidle_cooling_register(cooling_node, drv); + + of_node_put(cooling_node); + + if (ret) { + pr_err("Failed to register the cpuidle cooling device" \ + "for cpu%d: %d\n", cpu, ret); + break; + } + } } diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h index 65501d8f9778..a3bdc8a98f2c 100644 --- a/include/linux/cpu_cooling.h +++ b/include/linux/cpu_cooling.h @@ -63,18 +63,10 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy) 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); +void cpuidle_cooling_register(struct cpuidle_driver *drv); #else /* CONFIG_CPU_IDLE_THERMAL */ -static inline int cpuidle_cooling_register(struct cpuidle_driver *drv) +static inline void 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 */ -- cgit v1.2.3 From fc7a3d9e9cd01e2679076c655f2bc4b04efbfa01 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Wed, 29 Apr 2020 12:36:42 +0200 Subject: thermal: cpuidle: Register cpuidle cooling device The cpuidle driver can be used as a cooling device by injecting idle cycles. When the property is set, register the cpuidle driver with the idle state node pointer as a cooling device. The thermal framework will do the association automatically with the thermal zone via the cooling-device defined in the device tree cooling-maps section. Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Reviewed-by: Amit Kucheria Acked-by: Sudeep Holla Link: https://lore.kernel.org/r/20200429103644.5492-4-daniel.lezcano@linaro.org --- drivers/cpuidle/cpuidle-arm.c | 3 +++ drivers/cpuidle/cpuidle-psci.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/cpuidle/cpuidle-arm.c b/drivers/cpuidle/cpuidle-arm.c index 9e5156d39627..8c758920d699 100644 --- a/drivers/cpuidle/cpuidle-arm.c +++ b/drivers/cpuidle/cpuidle-arm.c @@ -8,6 +8,7 @@ #define pr_fmt(fmt) "CPUidle arm: " fmt +#include #include #include #include @@ -124,6 +125,8 @@ static int __init arm_idle_init_cpu(int cpu) if (ret) goto out_kfree_drv; + cpuidle_cooling_register(drv); + return 0; out_kfree_drv: diff --git a/drivers/cpuidle/cpuidle-psci.c b/drivers/cpuidle/cpuidle-psci.c index bae9140a65a5..1f38e0dfc9b2 100644 --- a/drivers/cpuidle/cpuidle-psci.c +++ b/drivers/cpuidle/cpuidle-psci.c @@ -9,6 +9,7 @@ #define pr_fmt(fmt) "CPUidle PSCI: " fmt #include +#include #include #include #include @@ -313,6 +314,8 @@ static int __init psci_idle_init_cpu(int cpu) if (ret) goto out_kfree_drv; + cpuidle_cooling_register(drv); + return 0; out_kfree_drv: -- cgit v1.2.3 From c1bba2c94decdec382c6a07d9ba722cefd617575 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Fri, 3 Apr 2020 12:31:46 +0530 Subject: dt-bindings: thermal: Add yaml bindings for thermal sensors As part of moving the thermal bindings to YAML, split it up into 3 bindings: thermal sensors, cooling devices and thermal zones. The property #thermal-sensor-cells is required in each device that acts as a thermal sensor. It is used to uniquely identify the instance of the thermal sensor inside the system. Signed-off-by: Amit Kucheria Reviewed-by: Rob Herring Reviewed-by: Lukasz Luba Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/a91b5603caea5b8854cc9f5325448e4c7228c328.1585748882.git.amit.kucheria@linaro.org --- .../bindings/thermal/thermal-sensor.yaml | 72 ++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/thermal-sensor.yaml diff --git a/Documentation/devicetree/bindings/thermal/thermal-sensor.yaml b/Documentation/devicetree/bindings/thermal/thermal-sensor.yaml new file mode 100644 index 000000000000..fcd25a0af38c --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/thermal-sensor.yaml @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: (GPL-2.0) +# Copyright 2020 Linaro Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/thermal-sensor.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Thermal sensor binding + +maintainers: + - Amit Kucheria + +description: | + Thermal management is achieved in devicetree by describing the sensor hardware + and the software abstraction of thermal zones required to take appropriate + action to mitigate thermal overloads. + + The following node types are used to completely describe a thermal management + system in devicetree: + - thermal-sensor: device that measures temperature, has SoC-specific bindings + - cooling-device: device used to dissipate heat either passively or actively + - thermal-zones: a container of the following node types used to describe all + thermal data for the platform + + This binding describes the thermal-sensor. + + Thermal sensor devices provide temperature sensing capabilities on thermal + zones. Typical devices are I2C ADC converters and bandgaps. Thermal sensor + devices may control one or more internal sensors. + +properties: + "#thermal-sensor-cells": + description: + Used to uniquely identify a thermal sensor instance within an IC. Will be + 0 on sensor nodes with only a single sensor and at least 1 on nodes + containing several internal sensors. + enum: [0, 1] + +examples: + - | + #include + + // Example 1: SDM845 TSENS + soc: soc@0 { + #address-cells = <2>; + #size-cells = <2>; + + /* ... */ + + tsens0: thermal-sensor@c263000 { + compatible = "qcom,sdm845-tsens", "qcom,tsens-v2"; + reg = <0 0x0c263000 0 0x1ff>, /* TM */ + <0 0x0c222000 0 0x1ff>; /* SROT */ + #qcom,sensors = <13>; + interrupts = , + ; + interrupt-names = "uplow", "critical"; + #thermal-sensor-cells = <1>; + }; + + tsens1: thermal-sensor@c265000 { + compatible = "qcom,sdm845-tsens", "qcom,tsens-v2"; + reg = <0 0x0c265000 0 0x1ff>, /* TM */ + <0 0x0c223000 0 0x1ff>; /* SROT */ + #qcom,sensors = <8>; + interrupts = , + ; + interrupt-names = "uplow", "critical"; + #thermal-sensor-cells = <1>; + }; + }; +... -- cgit v1.2.3 From 73c46acf915385e27f3312e77717d27eff74c08a Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Fri, 3 Apr 2020 12:31:47 +0530 Subject: dt-bindings: thermal: Add yaml bindings for thermal cooling-devices As part of moving the thermal bindings to YAML, split it up into 3 bindings: thermal sensors, cooling devices and thermal zones. The property #cooling-cells is required in each device that acts as a cooling device - whether active or passive. So any device that can throttle its performance to passively reduce heat dissipation (e.g. CPUs, GPUs) and any device that can actively dissipate heat at different levels (e.g. fans) will contain this property. Signed-off-by: Amit Kucheria Reviewed-by: Rob Herring Reviewed-by: Lukasz Luba Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/7a9ead7fb67585fb70ab3ffd481e7d567e96970e.1585748882.git.amit.kucheria@linaro.org --- .../bindings/thermal/thermal-cooling-devices.yaml | 116 +++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml diff --git a/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml b/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml new file mode 100644 index 000000000000..5145883d932e --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/thermal-cooling-devices.yaml @@ -0,0 +1,116 @@ +# SPDX-License-Identifier: (GPL-2.0) +# Copyright 2020 Linaro Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/thermal-cooling-devices.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Thermal cooling device binding + +maintainers: + - Amit Kucheria + +description: | + Thermal management is achieved in devicetree by describing the sensor hardware + and the software abstraction of cooling devices and thermal zones required to + take appropriate action to mitigate thermal overload. + + The following node types are used to completely describe a thermal management + system in devicetree: + - thermal-sensor: device that measures temperature, has SoC-specific bindings + - cooling-device: device used to dissipate heat either passively or actively + - thermal-zones: a container of the following node types used to describe all + thermal data for the platform + + This binding describes the cooling devices. + + There are essentially two ways to provide control on power dissipation: + - Passive cooling: by means of regulating device performance. A typical + passive cooling mechanism is a CPU that has dynamic voltage and frequency + scaling (DVFS), and uses lower frequencies as cooling states. + - Active cooling: by means of activating devices in order to remove the + dissipated heat, e.g. regulating fan speeds. + + Any cooling device has a range of cooling states (i.e. different levels of + heat dissipation). They also have a way to determine the state of cooling in + which the device is. For example, a fan's cooling states correspond to the + different fan speeds possible. Cooling states are referred to by single + unsigned integers, where larger numbers mean greater heat dissipation. The + precise set of cooling states associated with a device should be defined in + a particular device's binding. + +select: true + +properties: + "#cooling-cells": + description: + Must be 2, in order to specify minimum and maximum cooling state used in + the cooling-maps reference. The first cell is the minimum cooling state + and the second cell is the maximum cooling state requested. + const: 2 + +examples: + - | + #include + #include + + // Example 1: Cpufreq cooling device on CPU0 + cpus { + #address-cells = <2>; + #size-cells = <0>; + + CPU0: cpu@0 { + device_type = "cpu"; + compatible = "qcom,kryo385"; + reg = <0x0 0x0>; + enable-method = "psci"; + cpu-idle-states = <&LITTLE_CPU_SLEEP_0 + &LITTLE_CPU_SLEEP_1 + &CLUSTER_SLEEP_0>; + capacity-dmips-mhz = <607>; + dynamic-power-coefficient = <100>; + qcom,freq-domain = <&cpufreq_hw 0>; + #cooling-cells = <2>; + next-level-cache = <&L2_0>; + L2_0: l2-cache { + compatible = "cache"; + next-level-cache = <&L3_0>; + L3_0: l3-cache { + compatible = "cache"; + }; + }; + }; + + /* ... */ + + }; + + /* ... */ + + thermal-zones { + cpu0-thermal { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens0 1>; + + trips { + cpu0_alert0: trip-point0 { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu0_alert0>; + /* Corresponds to 1000MHz in OPP table */ + cooling-device = <&CPU0 5 5>; + }; + }; + }; + + /* ... */ + }; +... -- cgit v1.2.3 From 1202a442a31fd2e53cde1a9677d9f7005e48fd6e Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Fri, 3 Apr 2020 12:31:48 +0530 Subject: dt-bindings: thermal: Add yaml bindings for thermal zones As part of moving the thermal bindings to YAML, split it up into 3 bindings: thermal sensors, cooling devices and thermal zones. The thermal-zone binding is a software abstraction to capture the properties of each zone - how often they should be checked, the temperature thresholds (trips) at which mitigation actions need to be taken and the level of mitigation needed at those thresholds. Signed-off-by: Amit Kucheria Reviewed-by: Rob Herring Reviewed-by: Lukasz Luba Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/44e5c68bc654ccaf88945f70dc875fa186dd1480.1585748882.git.amit.kucheria@linaro.org --- .../devicetree/bindings/thermal/thermal-zones.yaml | 341 +++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/thermal-zones.yaml diff --git a/Documentation/devicetree/bindings/thermal/thermal-zones.yaml b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml new file mode 100644 index 000000000000..b8515d3eeaa2 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/thermal-zones.yaml @@ -0,0 +1,341 @@ +# SPDX-License-Identifier: (GPL-2.0) +# Copyright 2020 Linaro Ltd. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/thermal-zones.yaml# +$schema: http://devicetree.org/meta-schemas/base.yaml# + +title: Thermal zone binding + +maintainers: + - Amit Kucheria + +description: | + Thermal management is achieved in devicetree by describing the sensor hardware + and the software abstraction of cooling devices and thermal zones required to + take appropriate action to mitigate thermal overloads. + + The following node types are used to completely describe a thermal management + system in devicetree: + - thermal-sensor: device that measures temperature, has SoC-specific bindings + - cooling-device: device used to dissipate heat either passively or actively + - thermal-zones: a container of the following node types used to describe all + thermal data for the platform + + This binding describes the thermal-zones. + + The polling-delay properties of a thermal-zone are bound to the maximum dT/dt + (temperature derivative over time) in two situations for a thermal zone: + 1. when passive cooling is activated (polling-delay-passive) + 2. when the zone just needs to be monitored (polling-delay) or when + active cooling is activated. + + The maximum dT/dt is highly bound to hardware power consumption and + dissipation capability. The delays should be chosen to account for said + max dT/dt, such that a device does not cross several trip boundaries + unexpectedly between polls. Choosing the right polling delays shall avoid + having the device in temperature ranges that may damage the silicon structures + and reduce silicon lifetime. + +properties: + $nodename: + const: thermal-zones + description: + A /thermal-zones node is required in order to use the thermal framework to + manage input from the various thermal zones in the system in order to + mitigate thermal overload conditions. It does not represent a real device + in the system, but acts as a container to link a thermal sensor device, + platform-data regarding temperature thresholds and the mitigation actions + to take when the temperature crosses those thresholds. + +patternProperties: + "^[a-zA-Z][a-zA-Z0-9\\-]{1,12}-thermal$": + type: object + description: + Each thermal zone node contains information about how frequently it + must be checked, the sensor responsible for reporting temperature for + this zone, one sub-node containing the various trip points for this + zone and one sub-node containing all the zone cooling-maps. + + properties: + polling-delay: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + The maximum number of milliseconds to wait between polls when + checking this thermal zone. Setting this to 0 disables the polling + timers setup by the thermal framework and assumes that the thermal + sensors in this zone support interrupts. + + polling-delay-passive: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + The maximum number of milliseconds to wait between polls when + checking this thermal zone while doing passive cooling. Setting + this to 0 disables the polling timers setup by the thermal + framework and assumes that the thermal sensors in this zone + support interrupts. + + thermal-sensors: + $ref: /schemas/types.yaml#/definitions/phandle-array + maxItems: 1 + description: + The thermal sensor phandle and sensor specifier used to monitor this + thermal zone. + + coefficients: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + An array of integers containing the coefficients of a linear equation + that binds all the sensors listed in this thermal zone. + + The linear equation used is as follows, + z = c0 * x0 + c1 * x1 + ... + c(n-1) * x(n-1) + cn + where c0, c1, .., cn are the coefficients. + + Coefficients default to 1 in case this property is not specified. The + coefficients are ordered and are matched with sensors by means of the + sensor ID. Additional coefficients are interpreted as constant offset. + + sustainable-power: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + An estimate of the sustainable power (in mW) that this thermal zone + can dissipate at the desired control temperature. For reference, the + sustainable power of a 4-inch phone is typically 2000mW, while on a + 10-inch tablet is around 4500mW. + + trips: + type: object + description: + This node describes a set of points in the temperature domain at + which the thermal framework needs to take action. The actions to + be taken are defined in another node called cooling-maps. + + patternProperties: + "^[a-zA-Z][a-zA-Z0-9\\-_]{0,63}$": + type: object + + properties: + temperature: + $ref: /schemas/types.yaml#/definitions/int32 + minimum: -273000 + maximum: 200000 + description: + An integer expressing the trip temperature in millicelsius. + + hysteresis: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + An unsigned integer expressing the hysteresis delta with + respect to the trip temperature property above, also in + millicelsius. Any cooling action initiated by the framework is + maintained until the temperature falls below + (trip temperature - hysteresis). This potentially prevents a + situation where the trip gets constantly triggered soon after + cooling action is removed. + + type: + $ref: /schemas/types.yaml#/definitions/string + enum: + - active # enable active cooling e.g. fans + - passive # enable passive cooling e.g. throttling cpu + - hot # send notification to driver + - critical # send notification to driver, trigger shutdown + description: | + There are four valid trip types: active, passive, hot, + critical. + + The critical trip type is used to set the maximum + temperature threshold above which the HW becomes + unstable and underlying firmware might even trigger a + reboot. Hitting the critical threshold triggers a system + shutdown. + + The hot trip type can be used to send a notification to + the thermal driver (if a .notify callback is registered). + The action to be taken is left to the driver. + + The passive trip type can be used to slow down HW e.g. run + the CPU, GPU, bus at a lower frequency. + + The active trip type can be used to control other HW to + help in cooling e.g. fans can be sped up or slowed down + + required: + - temperature + - hysteresis + - type + additionalProperties: false + + additionalProperties: false + + cooling-maps: + type: object + description: + This node describes the action to be taken when a thermal zone + crosses one of the temperature thresholds described in the trips + node. The action takes the form of a mapping relation between a + trip and the target cooling device state. + + patternProperties: + "^map[-a-zA-Z0-9]*$": + type: object + + properties: + trip: + $ref: /schemas/types.yaml#/definitions/phandle + description: + A phandle of a trip point node within this thermal zone. + + cooling-device: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: + A list of cooling device phandles along with the minimum + and maximum cooling state specifiers for each cooling + device. Using the THERMAL_NO_LIMIT (-1UL) constant in the + cooling-device phandle limit specifier lets the framework + use the minimum and maximum cooling state for that cooling + device automatically. + + contribution: + $ref: /schemas/types.yaml#/definitions/uint32 + minimum: 0 + maximum: 100 + description: + The percentage contribution of the cooling devices at the + specific trip temperature referenced in this map + to this thermal zone + + required: + - trip + - cooling-device + additionalProperties: false + + required: + - polling-delay + - polling-delay-passive + - thermal-sensors + - trips + additionalProperties: false + +examples: + - | + #include + #include + + // Example 1: SDM845 TSENS + soc: soc@0 { + #address-cells = <2>; + #size-cells = <2>; + + /* ... */ + + tsens0: thermal-sensor@c263000 { + compatible = "qcom,sdm845-tsens", "qcom,tsens-v2"; + reg = <0 0x0c263000 0 0x1ff>, /* TM */ + <0 0x0c222000 0 0x1ff>; /* SROT */ + #qcom,sensors = <13>; + interrupts = , + ; + interrupt-names = "uplow", "critical"; + #thermal-sensor-cells = <1>; + }; + + tsens1: thermal-sensor@c265000 { + compatible = "qcom,sdm845-tsens", "qcom,tsens-v2"; + reg = <0 0x0c265000 0 0x1ff>, /* TM */ + <0 0x0c223000 0 0x1ff>; /* SROT */ + #qcom,sensors = <8>; + interrupts = , + ; + interrupt-names = "uplow", "critical"; + #thermal-sensor-cells = <1>; + }; + }; + + /* ... */ + + thermal-zones { + cpu0-thermal { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens0 1>; + + trips { + cpu0_alert0: trip-point0 { + temperature = <90000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu0_alert1: trip-point1 { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu0_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu0_alert0>; + /* Corresponds to 1400MHz in OPP table */ + cooling-device = <&CPU0 3 3>, <&CPU1 3 3>, + <&CPU2 3 3>, <&CPU3 3 3>; + }; + + map1 { + trip = <&cpu0_alert1>; + /* Corresponds to 1000MHz in OPP table */ + cooling-device = <&CPU0 5 5>, <&CPU1 5 5>, + <&CPU2 5 5>, <&CPU3 5 5>; + }; + }; + }; + + /* ... */ + + cluster0-thermal { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens0 5>; + + trips { + cluster0_alert0: trip-point0 { + temperature = <90000>; + hysteresis = <2000>; + type = "hot"; + }; + cluster0_crit: cluster0_crit { + temperature = <110000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + }; + + /* ... */ + + gpu-top-thermal { + polling-delay-passive = <250>; + polling-delay = <1000>; + + thermal-sensors = <&tsens0 11>; + + trips { + gpu1_alert0: trip-point0 { + temperature = <90000>; + hysteresis = <2000>; + type = "hot"; + }; + }; + }; + }; +... -- cgit v1.2.3 From f740e64c6cd6d9c26b4b9fc0a8d339b215147af7 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Thu, 7 May 2020 14:25:17 -0500 Subject: thermal: imx8mm: Replace zero-length array with flexible-array The current codebase makes use of the zero-length array language extension to the C90 standard, but the preferred mechanism to declare variable-length types such as these ones is a flexible array member[1][2], introduced in C99: struct foo { int stuff; struct boo array[]; }; By making use of the mechanism above, we will get a compiler warning in case the flexible array does not occur last in the structure, which will help us prevent some kind of undefined behavior bugs from being inadvertently introduced[3] to the codebase from now on. Also, notice that, dynamic memory allocations won't be affected by this change: "Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation of zero-length arrays, sizeof evaluates to zero."[1] sizeof(flexible-array-member) triggers a warning because flexible array members have incomplete type[1]. There are some instances of code in which the sizeof operator is being incorrectly/erroneously applied to zero-length arrays and the result is zero. Such instances may be hiding some bugs. So, this work (flexible-array member conversions) will also help to get completely rid of those sorts of issues. This issue was found with the help of Coccinelle. [1] https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html [2] https://github.com/KSPP/linux/issues/21 [3] commit 76497732932f ("cxgb3/l2t: Fix undefined behaviour") Signed-off-by: Gustavo A. R. Silva Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200507192517.GA16557@embeddedor --- drivers/thermal/imx8mm_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/imx8mm_thermal.c b/drivers/thermal/imx8mm_thermal.c index 0d60f8d7894f..e6061e26d4ac 100644 --- a/drivers/thermal/imx8mm_thermal.c +++ b/drivers/thermal/imx8mm_thermal.c @@ -54,7 +54,7 @@ struct imx8mm_tmu { void __iomem *base; struct clk *clk; const struct thermal_soc_data *socdata; - struct tmu_sensor sensors[0]; + struct tmu_sensor sensors[]; }; static int imx8mm_tmu_get_temp(void *data, int *temp) -- cgit v1.2.3 From 869495ccf52a707a21870ba5cba1cfd5ca720dd9 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:49 +0530 Subject: thermal/core: Get rid of MODULE_* tags The thermal framework can no longer be compiled as a module as of commit 554b3529fe01 ("thermal/drivers/core: Remove the module Kconfig's option"). Remove the MODULE_* tags. Rui is mentioned in the copyright line at the top of the file and the license is mentioned in the SPDX tags. So no loss of information. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/74339a09a55f8f3d86c4074fc2bf853a302d6186.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/thermal_core.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index c06550930979..dd3f4e87857b 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -27,10 +27,6 @@ #include "thermal_core.h" #include "thermal_hwmon.h" -MODULE_AUTHOR("Zhang Rui"); -MODULE_DESCRIPTION("Generic thermal management sysfs support"); -MODULE_LICENSE("GPL v2"); - static DEFINE_IDA(thermal_tz_ida); static DEFINE_IDA(thermal_cdev_ida); -- cgit v1.2.3 From 3f0cfea3dd6ed7cd176376bb4a5488b75b938d96 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:50 +0530 Subject: thermal/core: Replace module.h with export.h Thermal core cannot be modular, remove the unnecessary module.h include and replace with export.h to handle EXPORT_SYMBOL family of macros. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/33af23406dcdb0c62dae1e6401446b997ccb449f.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/thermal_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index dd3f4e87857b..b71196eaf90e 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -9,9 +9,9 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include #include #include +#include #include #include #include -- cgit v1.2.3 From 231b98af4da050138657febdd506951928981722 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:51 +0530 Subject: thermal/drivers/thermal_helpers: Sort headers alphabetically Sort headers to make it easier to read and find duplicate headers. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/133db154796f354e6c51e6310095f679e1f45441.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/thermal_helpers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c index 59eaf2d0fdb3..3d737143ec11 100644 --- a/drivers/thermal/thermal_helpers.c +++ b/drivers/thermal/thermal_helpers.c @@ -12,11 +12,11 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include #include #include #include #include +#include #include -- cgit v1.2.3 From 3a74c882dcc15c959e1cc14c3f62877d2f09aef8 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:52 +0530 Subject: thermal/drivers/thermal_helpers: Include export.h It is preferable to include export.h when you are using EXPORT_SYMBOL family of macros. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/fd3443f00dbba6ca90f35726c7451ae52145d2d4.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/thermal_helpers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/thermal_helpers.c b/drivers/thermal/thermal_helpers.c index 3d737143ec11..87b1256fa2f2 100644 --- a/drivers/thermal/thermal_helpers.c +++ b/drivers/thermal/thermal_helpers.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include -- cgit v1.2.3 From 1330e04f423661ed05a4d2a5235505e0332b8026 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:53 +0530 Subject: thermal/drivers/thermal_hwmon: Sort headers alphabetically Sort headers to make it easier to read and find duplicate headers. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/29b64f1fe81e674c753c8f8309c310acd782ebea.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/thermal_hwmon.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index c8d2620f2e42..e43ae551592d 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -10,10 +10,11 @@ * Copyright (C) 2013 Texas Instruments * Copyright (C) 2013 Eduardo Valentin */ +#include #include -#include #include -#include +#include + #include "thermal_hwmon.h" /* hwmon sys I/F */ -- cgit v1.2.3 From e5ebf357bbfc73d4bfca14195e104e0726c5a729 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:54 +0530 Subject: thermal/drivers/thermal_hwmon: Include export.h It is preferable to include export.h when you are using EXPORT_SYMBOL family of macros. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/f542962494a8441fdc8e550a11d0e535b92362a0.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/thermal_hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index e43ae551592d..8b92e00ff236 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -11,6 +11,7 @@ * Copyright (C) 2013 Eduardo Valentin */ #include +#include #include #include #include -- cgit v1.2.3 From d5d1f6e759dfc8218ae9500af222e280ead1aeec Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:55 +0530 Subject: thermal/drivers/clock_cooling: Sort headers alphabetically Sort headers to make it easier to read and find duplicate headers. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/f8e1258fd8b882bab018de63c7e713b4334fec30.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/clock_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/clock_cooling.c b/drivers/thermal/clock_cooling.c index 7cb3ae4b44ee..fd6bc6eefc88 100644 --- a/drivers/thermal/clock_cooling.c +++ b/drivers/thermal/clock_cooling.c @@ -12,6 +12,7 @@ * Copyright (C) 2012 Amit Daniel */ #include +#include #include #include #include @@ -20,7 +21,6 @@ #include #include #include -#include /** * struct clock_cooling_device - data for cooling device with clock -- cgit v1.2.3 From 1628d4b8ca9a877577aaf4116c02f1f45ea18a89 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:56 +0530 Subject: thermal/drivers/clock_cooling: Include export.h It is preferrable to include export.h when you are using EXPORT_SYMBOL family of macros. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/25f16415ab7b7587a052f1bce4133da318d58192.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/clock_cooling.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/clock_cooling.c b/drivers/thermal/clock_cooling.c index fd6bc6eefc88..56cb1f46a428 100644 --- a/drivers/thermal/clock_cooling.c +++ b/drivers/thermal/clock_cooling.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From 5ccb451e47fa6da8ae6cd6710b91758280197073 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:57 +0530 Subject: thermal/drivers/cpufreq_cooling: Sort headers alphabetically Sort headers to make it easier to read and find duplicate headers. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/4231f5dfe758b9bf716981be71cadf9642c83528.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/cpufreq_cooling.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index e297e135c031..1b5a63b4763d 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -11,16 +11,16 @@ * */ #include -#include +#include #include +#include +#include #include #include #include #include #include -#include -#include -#include +#include #include -- cgit v1.2.3 From c65f83c0667ae1b0013fa87918f009c4380443d5 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:58 +0530 Subject: thermal/drivers/cpufreq_cooling: Replace module.h with export.h cpufreq_cooling cannot be modular, remove the unnecessary module.h include and replace with export.h to handle EXPORT_SYMBOL family of macros. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/7a439e41e91d8bc5ff99207f99723fcf04ca36eb.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/cpufreq_cooling.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 1b5a63b4763d..9e124020519f 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -10,12 +10,12 @@ * Viresh Kumar * */ -#include #include #include #include #include #include +#include #include #include #include -- cgit v1.2.3 From 2b61314e76671e125b3d53a02eec3912204c5418 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:54:59 +0530 Subject: thermal/drivers/of-thermal: Sort headers alphabetically Sort headers to make it easier to read and find duplicate headers. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/f9f9d8117f1659872114ba65bbfa9ed4b813128f.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/of-thermal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 874a47d6923f..ddf88dbe7ba2 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -8,13 +8,13 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #include #include "thermal_core.h" -- cgit v1.2.3 From 6abea5d2af4cdd508b04d94ed9382c3710b99dfc Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:55:00 +0530 Subject: thermal/drivers/user_space: Sort headers alphabetically Sort headers to make it easier to read and find duplicate headers. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/406d0c7c961e997b42e25adf4e432fe4f57b315a.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/user_space.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c index 293cffd9c8ad..82a7198bbe71 100644 --- a/drivers/thermal/user_space.c +++ b/drivers/thermal/user_space.c @@ -10,8 +10,8 @@ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -#include #include +#include #include "thermal_core.h" -- cgit v1.2.3 From 0015d9a2a72745308ef9728a746ff7b1e82138bc Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:55:01 +0530 Subject: thermal/governors: Prefix all source files with gov_ Bang-bang governor source file is prefixed with gov_. Do the same for other governors for consistency so they're easy to find in the sources. Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/b9a85d3204712f14e320504948c12712dc0b291b.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/Makefile | 8 +- drivers/thermal/fair_share.c | 120 ------- drivers/thermal/gov_fair_share.c | 120 +++++++ drivers/thermal/gov_power_allocator.c | 654 ++++++++++++++++++++++++++++++++++ drivers/thermal/gov_step_wise.c | 209 +++++++++++ drivers/thermal/gov_user_space.c | 47 +++ drivers/thermal/power_allocator.c | 654 ---------------------------------- drivers/thermal/step_wise.c | 209 ----------- drivers/thermal/user_space.c | 47 --- 9 files changed, 1034 insertions(+), 1034 deletions(-) delete mode 100644 drivers/thermal/fair_share.c create mode 100644 drivers/thermal/gov_fair_share.c create mode 100644 drivers/thermal/gov_power_allocator.c create mode 100644 drivers/thermal/gov_step_wise.c create mode 100644 drivers/thermal/gov_user_space.c delete mode 100644 drivers/thermal/power_allocator.c delete mode 100644 drivers/thermal/step_wise.c delete mode 100644 drivers/thermal/user_space.c diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 86c506410cc0..757c40a71940 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -12,11 +12,11 @@ thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o # governors -thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o +thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += gov_fair_share.o thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o -thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o -thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o -thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o +thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += gov_step_wise.o +thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += gov_user_space.o +thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += gov_power_allocator.o # cpufreq cooling thermal_sys-$(CONFIG_CPU_FREQ_THERMAL) += cpufreq_cooling.o diff --git a/drivers/thermal/fair_share.c b/drivers/thermal/fair_share.c deleted file mode 100644 index aaa07180ab48..000000000000 --- a/drivers/thermal/fair_share.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * fair_share.c - A simple weight based Thermal governor - * - * Copyright (C) 2012 Intel Corp - * Copyright (C) 2012 Durgadoss R - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -#include -#include - -#include "thermal_core.h" - -/** - * get_trip_level: - obtains the current trip level for a zone - * @tz: thermal zone device - */ -static int get_trip_level(struct thermal_zone_device *tz) -{ - int count = 0; - int trip_temp; - enum thermal_trip_type trip_type; - - if (tz->trips == 0 || !tz->ops->get_trip_temp) - return 0; - - for (count = 0; count < tz->trips; count++) { - tz->ops->get_trip_temp(tz, count, &trip_temp); - if (tz->temperature < trip_temp) - break; - } - - /* - * count > 0 only if temperature is greater than first trip - * point, in which case, trip_point = count - 1 - */ - if (count > 0) { - tz->ops->get_trip_type(tz, count - 1, &trip_type); - trace_thermal_zone_trip(tz, count - 1, trip_type); - } - - return count; -} - -static long get_target_state(struct thermal_zone_device *tz, - struct thermal_cooling_device *cdev, int percentage, int level) -{ - unsigned long max_state; - - cdev->ops->get_max_state(cdev, &max_state); - - return (long)(percentage * level * max_state) / (100 * tz->trips); -} - -/** - * fair_share_throttle - throttles devices associated with the given zone - * @tz: thermal_zone_device - * @trip: trip point index - * - * Throttling Logic: This uses three parameters to calculate the new - * throttle state of the cooling devices associated with the given zone. - * - * Parameters used for Throttling: - * P1. max_state: Maximum throttle state exposed by the cooling device. - * P2. percentage[i]/100: - * How 'effective' the 'i'th device is, in cooling the given zone. - * P3. cur_trip_level/max_no_of_trips: - * This describes the extent to which the devices should be throttled. - * We do not want to throttle too much when we trip a lower temperature, - * whereas the throttling is at full swing if we trip critical levels. - * (Heavily assumes the trip points are in ascending order) - * new_state of cooling device = P3 * P2 * P1 - */ -static int fair_share_throttle(struct thermal_zone_device *tz, int trip) -{ - struct thermal_instance *instance; - int total_weight = 0; - int total_instance = 0; - int cur_trip_level = get_trip_level(tz); - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if (instance->trip != trip) - continue; - - total_weight += instance->weight; - total_instance++; - } - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - int percentage; - struct thermal_cooling_device *cdev = instance->cdev; - - if (instance->trip != trip) - continue; - - if (!total_weight) - percentage = 100 / total_instance; - else - percentage = (instance->weight * 100) / total_weight; - - instance->target = get_target_state(tz, cdev, percentage, - cur_trip_level); - - mutex_lock(&instance->cdev->lock); - instance->cdev->updated = false; - mutex_unlock(&instance->cdev->lock); - thermal_cdev_update(cdev); - } - return 0; -} - -static struct thermal_governor thermal_gov_fair_share = { - .name = "fair_share", - .throttle = fair_share_throttle, -}; -THERMAL_GOVERNOR_DECLARE(thermal_gov_fair_share); diff --git a/drivers/thermal/gov_fair_share.c b/drivers/thermal/gov_fair_share.c new file mode 100644 index 000000000000..aaa07180ab48 --- /dev/null +++ b/drivers/thermal/gov_fair_share.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fair_share.c - A simple weight based Thermal governor + * + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include + +#include "thermal_core.h" + +/** + * get_trip_level: - obtains the current trip level for a zone + * @tz: thermal zone device + */ +static int get_trip_level(struct thermal_zone_device *tz) +{ + int count = 0; + int trip_temp; + enum thermal_trip_type trip_type; + + if (tz->trips == 0 || !tz->ops->get_trip_temp) + return 0; + + for (count = 0; count < tz->trips; count++) { + tz->ops->get_trip_temp(tz, count, &trip_temp); + if (tz->temperature < trip_temp) + break; + } + + /* + * count > 0 only if temperature is greater than first trip + * point, in which case, trip_point = count - 1 + */ + if (count > 0) { + tz->ops->get_trip_type(tz, count - 1, &trip_type); + trace_thermal_zone_trip(tz, count - 1, trip_type); + } + + return count; +} + +static long get_target_state(struct thermal_zone_device *tz, + struct thermal_cooling_device *cdev, int percentage, int level) +{ + unsigned long max_state; + + cdev->ops->get_max_state(cdev, &max_state); + + return (long)(percentage * level * max_state) / (100 * tz->trips); +} + +/** + * fair_share_throttle - throttles devices associated with the given zone + * @tz: thermal_zone_device + * @trip: trip point index + * + * Throttling Logic: This uses three parameters to calculate the new + * throttle state of the cooling devices associated with the given zone. + * + * Parameters used for Throttling: + * P1. max_state: Maximum throttle state exposed by the cooling device. + * P2. percentage[i]/100: + * How 'effective' the 'i'th device is, in cooling the given zone. + * P3. cur_trip_level/max_no_of_trips: + * This describes the extent to which the devices should be throttled. + * We do not want to throttle too much when we trip a lower temperature, + * whereas the throttling is at full swing if we trip critical levels. + * (Heavily assumes the trip points are in ascending order) + * new_state of cooling device = P3 * P2 * P1 + */ +static int fair_share_throttle(struct thermal_zone_device *tz, int trip) +{ + struct thermal_instance *instance; + int total_weight = 0; + int total_instance = 0; + int cur_trip_level = get_trip_level(tz); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip) + continue; + + total_weight += instance->weight; + total_instance++; + } + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + int percentage; + struct thermal_cooling_device *cdev = instance->cdev; + + if (instance->trip != trip) + continue; + + if (!total_weight) + percentage = 100 / total_instance; + else + percentage = (instance->weight * 100) / total_weight; + + instance->target = get_target_state(tz, cdev, percentage, + cur_trip_level); + + mutex_lock(&instance->cdev->lock); + instance->cdev->updated = false; + mutex_unlock(&instance->cdev->lock); + thermal_cdev_update(cdev); + } + return 0; +} + +static struct thermal_governor thermal_gov_fair_share = { + .name = "fair_share", + .throttle = fair_share_throttle, +}; +THERMAL_GOVERNOR_DECLARE(thermal_gov_fair_share); diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c new file mode 100644 index 000000000000..44636475b2a3 --- /dev/null +++ b/drivers/thermal/gov_power_allocator.c @@ -0,0 +1,654 @@ +/* + * A power allocator to manage temperature + * + * Copyright (C) 2014 ARM Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "Power allocator: " fmt + +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +#include "thermal_core.h" + +#define INVALID_TRIP -1 + +#define FRAC_BITS 10 +#define int_to_frac(x) ((x) << FRAC_BITS) +#define frac_to_int(x) ((x) >> FRAC_BITS) + +/** + * mul_frac() - multiply two fixed-point numbers + * @x: first multiplicand + * @y: second multiplicand + * + * Return: the result of multiplying two fixed-point numbers. The + * result is also a fixed-point number. + */ +static inline s64 mul_frac(s64 x, s64 y) +{ + return (x * y) >> FRAC_BITS; +} + +/** + * div_frac() - divide two fixed-point numbers + * @x: the dividend + * @y: the divisor + * + * Return: the result of dividing two fixed-point numbers. The + * result is also a fixed-point number. + */ +static inline s64 div_frac(s64 x, s64 y) +{ + return div_s64(x << FRAC_BITS, y); +} + +/** + * struct power_allocator_params - parameters for the power allocator governor + * @allocated_tzp: whether we have allocated tzp for this thermal zone and + * it needs to be freed on unbind + * @err_integral: accumulated error in the PID controller. + * @prev_err: error in the previous iteration of the PID controller. + * Used to calculate the derivative term. + * @trip_switch_on: first passive trip point of the thermal zone. The + * governor switches on when this trip point is crossed. + * If the thermal zone only has one passive trip point, + * @trip_switch_on should be INVALID_TRIP. + * @trip_max_desired_temperature: last passive trip point of the thermal + * zone. The temperature we are + * controlling for. + */ +struct power_allocator_params { + bool allocated_tzp; + s64 err_integral; + s32 prev_err; + int trip_switch_on; + int trip_max_desired_temperature; +}; + +/** + * estimate_sustainable_power() - Estimate the sustainable power of a thermal zone + * @tz: thermal zone we are operating in + * + * For thermal zones that don't provide a sustainable_power in their + * thermal_zone_params, estimate one. Calculate it using the minimum + * power of all the cooling devices as that gives a valid value that + * can give some degree of functionality. For optimal performance of + * this governor, provide a sustainable_power in the thermal zone's + * thermal_zone_params. + */ +static u32 estimate_sustainable_power(struct thermal_zone_device *tz) +{ + u32 sustainable_power = 0; + struct thermal_instance *instance; + struct power_allocator_params *params = tz->governor_data; + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + struct thermal_cooling_device *cdev = instance->cdev; + u32 min_power; + + if (instance->trip != params->trip_max_desired_temperature) + continue; + + if (power_actor_get_min_power(cdev, tz, &min_power)) + continue; + + sustainable_power += min_power; + } + + return sustainable_power; +} + +/** + * estimate_pid_constants() - Estimate the constants for the PID controller + * @tz: thermal zone for which to estimate the constants + * @sustainable_power: sustainable power for the thermal zone + * @trip_switch_on: trip point number for the switch on temperature + * @control_temp: target temperature for the power allocator governor + * @force: whether to force the update of the constants + * + * This function is used to update the estimation of the PID + * controller constants in struct thermal_zone_parameters. + * Sustainable power is provided in case it was estimated. The + * estimated sustainable_power should not be stored in the + * thermal_zone_parameters so it has to be passed explicitly to this + * function. + * + * If @force is not set, the values in the thermal zone's parameters + * are preserved if they are not zero. If @force is set, the values + * in thermal zone's parameters are overwritten. + */ +static void estimate_pid_constants(struct thermal_zone_device *tz, + u32 sustainable_power, int trip_switch_on, + int control_temp, bool force) +{ + int ret; + int switch_on_temp; + u32 temperature_threshold; + + ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp); + if (ret) + switch_on_temp = 0; + + temperature_threshold = control_temp - switch_on_temp; + /* + * estimate_pid_constants() tries to find appropriate default + * values for thermal zones that don't provide them. If a + * system integrator has configured a thermal zone with two + * passive trip points at the same temperature, that person + * hasn't put any effort to set up the thermal zone properly + * so just give up. + */ + if (!temperature_threshold) + return; + + if (!tz->tzp->k_po || force) + tz->tzp->k_po = int_to_frac(sustainable_power) / + temperature_threshold; + + if (!tz->tzp->k_pu || force) + tz->tzp->k_pu = int_to_frac(2 * sustainable_power) / + temperature_threshold; + + if (!tz->tzp->k_i || force) + tz->tzp->k_i = int_to_frac(10) / 1000; + /* + * The default for k_d and integral_cutoff is 0, so we can + * leave them as they are. + */ +} + +/** + * pid_controller() - PID controller + * @tz: thermal zone we are operating in + * @control_temp: the target temperature in millicelsius + * @max_allocatable_power: maximum allocatable power for this thermal zone + * + * This PID controller increases the available power budget so that the + * temperature of the thermal zone gets as close as possible to + * @control_temp and limits the power if it exceeds it. k_po is the + * proportional term when we are overshooting, k_pu is the + * proportional term when we are undershooting. integral_cutoff is a + * threshold below which we stop accumulating the error. The + * accumulated error is only valid if the requested power will make + * the system warmer. If the system is mostly idle, there's no point + * in accumulating positive error. + * + * Return: The power budget for the next period. + */ +static u32 pid_controller(struct thermal_zone_device *tz, + int control_temp, + u32 max_allocatable_power) +{ + s64 p, i, d, power_range; + s32 err, max_power_frac; + u32 sustainable_power; + struct power_allocator_params *params = tz->governor_data; + + max_power_frac = int_to_frac(max_allocatable_power); + + if (tz->tzp->sustainable_power) { + sustainable_power = tz->tzp->sustainable_power; + } else { + sustainable_power = estimate_sustainable_power(tz); + estimate_pid_constants(tz, sustainable_power, + params->trip_switch_on, control_temp, + true); + } + + err = control_temp - tz->temperature; + err = int_to_frac(err); + + /* Calculate the proportional term */ + p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err); + + /* + * Calculate the integral term + * + * if the error is less than cut off allow integration (but + * the integral is limited to max power) + */ + i = mul_frac(tz->tzp->k_i, params->err_integral); + + if (err < int_to_frac(tz->tzp->integral_cutoff)) { + s64 i_next = i + mul_frac(tz->tzp->k_i, err); + + if (abs(i_next) < max_power_frac) { + i = i_next; + params->err_integral += err; + } + } + + /* + * Calculate the derivative term + * + * We do err - prev_err, so with a positive k_d, a decreasing + * error (i.e. driving closer to the line) results in less + * power being applied, slowing down the controller) + */ + d = mul_frac(tz->tzp->k_d, err - params->prev_err); + d = div_frac(d, tz->passive_delay); + params->prev_err = err; + + power_range = p + i + d; + + /* feed-forward the known sustainable dissipatable power */ + power_range = sustainable_power + frac_to_int(power_range); + + power_range = clamp(power_range, (s64)0, (s64)max_allocatable_power); + + trace_thermal_power_allocator_pid(tz, frac_to_int(err), + frac_to_int(params->err_integral), + frac_to_int(p), frac_to_int(i), + frac_to_int(d), power_range); + + return power_range; +} + +/** + * divvy_up_power() - divvy the allocated power between the actors + * @req_power: each actor's requested power + * @max_power: each actor's maximum available power + * @num_actors: size of the @req_power, @max_power and @granted_power's array + * @total_req_power: sum of @req_power + * @power_range: total allocated power + * @granted_power: output array: each actor's granted power + * @extra_actor_power: an appropriately sized array to be used in the + * function as temporary storage of the extra power given + * to the actors + * + * This function divides the total allocated power (@power_range) + * fairly between the actors. It first tries to give each actor a + * share of the @power_range according to how much power it requested + * compared to the rest of the actors. For example, if only one actor + * requests power, then it receives all the @power_range. If + * three actors each requests 1mW, each receives a third of the + * @power_range. + * + * If any actor received more than their maximum power, then that + * surplus is re-divvied among the actors based on how far they are + * from their respective maximums. + * + * Granted power for each actor is written to @granted_power, which + * should've been allocated by the calling function. + */ +static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors, + u32 total_req_power, u32 power_range, + u32 *granted_power, u32 *extra_actor_power) +{ + u32 extra_power, capped_extra_power; + int i; + + /* + * Prevent division by 0 if none of the actors request power. + */ + if (!total_req_power) + total_req_power = 1; + + capped_extra_power = 0; + extra_power = 0; + for (i = 0; i < num_actors; i++) { + u64 req_range = (u64)req_power[i] * power_range; + + granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range, + total_req_power); + + if (granted_power[i] > max_power[i]) { + extra_power += granted_power[i] - max_power[i]; + granted_power[i] = max_power[i]; + } + + extra_actor_power[i] = max_power[i] - granted_power[i]; + capped_extra_power += extra_actor_power[i]; + } + + if (!extra_power) + return; + + /* + * Re-divvy the reclaimed extra among actors based on + * how far they are from the max + */ + extra_power = min(extra_power, capped_extra_power); + if (capped_extra_power > 0) + for (i = 0; i < num_actors; i++) + granted_power[i] += (extra_actor_power[i] * + extra_power) / capped_extra_power; +} + +static int allocate_power(struct thermal_zone_device *tz, + int control_temp) +{ + struct thermal_instance *instance; + struct power_allocator_params *params = tz->governor_data; + u32 *req_power, *max_power, *granted_power, *extra_actor_power; + u32 *weighted_req_power; + u32 total_req_power, max_allocatable_power, total_weighted_req_power; + u32 total_granted_power, power_range; + int i, num_actors, total_weight, ret = 0; + int trip_max_desired_temperature = params->trip_max_desired_temperature; + + mutex_lock(&tz->lock); + + num_actors = 0; + total_weight = 0; + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if ((instance->trip == trip_max_desired_temperature) && + cdev_is_power_actor(instance->cdev)) { + num_actors++; + total_weight += instance->weight; + } + } + + if (!num_actors) { + ret = -ENODEV; + goto unlock; + } + + /* + * We need to allocate five arrays of the same size: + * req_power, max_power, granted_power, extra_actor_power and + * weighted_req_power. They are going to be needed until this + * function returns. Allocate them all in one go to simplify + * the allocation and deallocation logic. + */ + BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power)); + BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power)); + BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power)); + BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power)); + req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL); + if (!req_power) { + ret = -ENOMEM; + goto unlock; + } + + max_power = &req_power[num_actors]; + granted_power = &req_power[2 * num_actors]; + extra_actor_power = &req_power[3 * num_actors]; + weighted_req_power = &req_power[4 * num_actors]; + + i = 0; + total_weighted_req_power = 0; + total_req_power = 0; + max_allocatable_power = 0; + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + int weight; + struct thermal_cooling_device *cdev = instance->cdev; + + if (instance->trip != trip_max_desired_temperature) + continue; + + if (!cdev_is_power_actor(cdev)) + continue; + + if (cdev->ops->get_requested_power(cdev, tz, &req_power[i])) + continue; + + if (!total_weight) + weight = 1 << FRAC_BITS; + else + weight = instance->weight; + + weighted_req_power[i] = frac_to_int(weight * req_power[i]); + + if (power_actor_get_max_power(cdev, tz, &max_power[i])) + continue; + + total_req_power += req_power[i]; + max_allocatable_power += max_power[i]; + total_weighted_req_power += weighted_req_power[i]; + + i++; + } + + power_range = pid_controller(tz, control_temp, max_allocatable_power); + + divvy_up_power(weighted_req_power, max_power, num_actors, + total_weighted_req_power, power_range, granted_power, + extra_actor_power); + + total_granted_power = 0; + i = 0; + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip_max_desired_temperature) + continue; + + if (!cdev_is_power_actor(instance->cdev)) + continue; + + power_actor_set_power(instance->cdev, instance, + granted_power[i]); + total_granted_power += granted_power[i]; + + i++; + } + + trace_thermal_power_allocator(tz, req_power, total_req_power, + granted_power, total_granted_power, + num_actors, power_range, + max_allocatable_power, tz->temperature, + control_temp - tz->temperature); + + kfree(req_power); +unlock: + mutex_unlock(&tz->lock); + + return ret; +} + +/** + * get_governor_trips() - get the number of the two trip points that are key for this governor + * @tz: thermal zone to operate on + * @params: pointer to private data for this governor + * + * The power allocator governor works optimally with two trips points: + * a "switch on" trip point and a "maximum desired temperature". These + * are defined as the first and last passive trip points. + * + * If there is only one trip point, then that's considered to be the + * "maximum desired temperature" trip point and the governor is always + * on. If there are no passive or active trip points, then the + * governor won't do anything. In fact, its throttle function + * won't be called at all. + */ +static void get_governor_trips(struct thermal_zone_device *tz, + struct power_allocator_params *params) +{ + int i, last_active, last_passive; + bool found_first_passive; + + found_first_passive = false; + last_active = INVALID_TRIP; + last_passive = INVALID_TRIP; + + for (i = 0; i < tz->trips; i++) { + enum thermal_trip_type type; + int ret; + + ret = tz->ops->get_trip_type(tz, i, &type); + if (ret) { + dev_warn(&tz->device, + "Failed to get trip point %d type: %d\n", i, + ret); + continue; + } + + if (type == THERMAL_TRIP_PASSIVE) { + if (!found_first_passive) { + params->trip_switch_on = i; + found_first_passive = true; + } else { + last_passive = i; + } + } else if (type == THERMAL_TRIP_ACTIVE) { + last_active = i; + } else { + break; + } + } + + if (last_passive != INVALID_TRIP) { + params->trip_max_desired_temperature = last_passive; + } else if (found_first_passive) { + params->trip_max_desired_temperature = params->trip_switch_on; + params->trip_switch_on = INVALID_TRIP; + } else { + params->trip_switch_on = INVALID_TRIP; + params->trip_max_desired_temperature = last_active; + } +} + +static void reset_pid_controller(struct power_allocator_params *params) +{ + params->err_integral = 0; + params->prev_err = 0; +} + +static void allow_maximum_power(struct thermal_zone_device *tz) +{ + struct thermal_instance *instance; + struct power_allocator_params *params = tz->governor_data; + + mutex_lock(&tz->lock); + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if ((instance->trip != params->trip_max_desired_temperature) || + (!cdev_is_power_actor(instance->cdev))) + continue; + + instance->target = 0; + mutex_lock(&instance->cdev->lock); + instance->cdev->updated = false; + mutex_unlock(&instance->cdev->lock); + thermal_cdev_update(instance->cdev); + } + mutex_unlock(&tz->lock); +} + +/** + * power_allocator_bind() - bind the power_allocator governor to a thermal zone + * @tz: thermal zone to bind it to + * + * Initialize the PID controller parameters and bind it to the thermal + * zone. + * + * Return: 0 on success, or -ENOMEM if we ran out of memory. + */ +static int power_allocator_bind(struct thermal_zone_device *tz) +{ + int ret; + struct power_allocator_params *params; + int control_temp; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + if (!tz->tzp) { + tz->tzp = kzalloc(sizeof(*tz->tzp), GFP_KERNEL); + if (!tz->tzp) { + ret = -ENOMEM; + goto free_params; + } + + params->allocated_tzp = true; + } + + if (!tz->tzp->sustainable_power) + dev_warn(&tz->device, "power_allocator: sustainable_power will be estimated\n"); + + get_governor_trips(tz, params); + + if (tz->trips > 0) { + ret = tz->ops->get_trip_temp(tz, + params->trip_max_desired_temperature, + &control_temp); + if (!ret) + estimate_pid_constants(tz, tz->tzp->sustainable_power, + params->trip_switch_on, + control_temp, false); + } + + reset_pid_controller(params); + + tz->governor_data = params; + + return 0; + +free_params: + kfree(params); + + return ret; +} + +static void power_allocator_unbind(struct thermal_zone_device *tz) +{ + struct power_allocator_params *params = tz->governor_data; + + dev_dbg(&tz->device, "Unbinding from thermal zone %d\n", tz->id); + + if (params->allocated_tzp) { + kfree(tz->tzp); + tz->tzp = NULL; + } + + kfree(tz->governor_data); + tz->governor_data = NULL; +} + +static int power_allocator_throttle(struct thermal_zone_device *tz, int trip) +{ + int ret; + int switch_on_temp, control_temp; + struct power_allocator_params *params = tz->governor_data; + + /* + * We get called for every trip point but we only need to do + * our calculations once + */ + if (trip != params->trip_max_desired_temperature) + return 0; + + ret = tz->ops->get_trip_temp(tz, params->trip_switch_on, + &switch_on_temp); + if (!ret && (tz->temperature < switch_on_temp)) { + tz->passive = 0; + reset_pid_controller(params); + allow_maximum_power(tz); + return 0; + } + + tz->passive = 1; + + ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature, + &control_temp); + if (ret) { + dev_warn(&tz->device, + "Failed to get the maximum desired temperature: %d\n", + ret); + return ret; + } + + return allocate_power(tz, control_temp); +} + +static struct thermal_governor thermal_gov_power_allocator = { + .name = "power_allocator", + .bind_to_tz = power_allocator_bind, + .unbind_from_tz = power_allocator_unbind, + .throttle = power_allocator_throttle, +}; +THERMAL_GOVERNOR_DECLARE(thermal_gov_power_allocator); diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c new file mode 100644 index 000000000000..2ae7198d3067 --- /dev/null +++ b/drivers/thermal/gov_step_wise.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * step_wise.c - A step-by-step Thermal throttling governor + * + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include + +#include "thermal_core.h" + +/* + * If the temperature is higher than a trip point, + * a. if the trend is THERMAL_TREND_RAISING, use higher cooling + * state for this trip point + * b. if the trend is THERMAL_TREND_DROPPING, do nothing + * c. if the trend is THERMAL_TREND_RAISE_FULL, use upper limit + * for this trip point + * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit + * for this trip point + * If the temperature is lower than a trip point, + * a. if the trend is THERMAL_TREND_RAISING, do nothing + * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling + * state for this trip point, if the cooling state already + * equals lower limit, deactivate the thermal instance + * c. if the trend is THERMAL_TREND_RAISE_FULL, do nothing + * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit, + * if the cooling state already equals lower limit, + * deactivate the thermal instance + */ +static unsigned long get_target_state(struct thermal_instance *instance, + enum thermal_trend trend, bool throttle) +{ + struct thermal_cooling_device *cdev = instance->cdev; + unsigned long cur_state; + unsigned long next_target; + + /* + * We keep this instance the way it is by default. + * Otherwise, we use the current state of the + * cdev in use to determine the next_target. + */ + cdev->ops->get_cur_state(cdev, &cur_state); + next_target = instance->target; + dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state); + + if (!instance->initialized) { + if (throttle) { + next_target = (cur_state + 1) >= instance->upper ? + instance->upper : + ((cur_state + 1) < instance->lower ? + instance->lower : (cur_state + 1)); + } else { + next_target = THERMAL_NO_TARGET; + } + + return next_target; + } + + switch (trend) { + case THERMAL_TREND_RAISING: + if (throttle) { + next_target = cur_state < instance->upper ? + (cur_state + 1) : instance->upper; + if (next_target < instance->lower) + next_target = instance->lower; + } + break; + case THERMAL_TREND_RAISE_FULL: + if (throttle) + next_target = instance->upper; + break; + case THERMAL_TREND_DROPPING: + if (cur_state <= instance->lower) { + if (!throttle) + next_target = THERMAL_NO_TARGET; + } else { + if (!throttle) { + next_target = cur_state - 1; + if (next_target > instance->upper) + next_target = instance->upper; + } + } + break; + case THERMAL_TREND_DROP_FULL: + if (cur_state == instance->lower) { + if (!throttle) + next_target = THERMAL_NO_TARGET; + } else + next_target = instance->lower; + break; + default: + break; + } + + return next_target; +} + +static void update_passive_instance(struct thermal_zone_device *tz, + enum thermal_trip_type type, int value) +{ + /* + * If value is +1, activate a passive instance. + * If value is -1, deactivate a passive instance. + */ + if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE) + tz->passive += value; +} + +static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) +{ + int trip_temp; + enum thermal_trip_type trip_type; + enum thermal_trend trend; + struct thermal_instance *instance; + bool throttle = false; + int old_target; + + if (trip == THERMAL_TRIPS_NONE) { + trip_temp = tz->forced_passive; + trip_type = THERMAL_TRIPS_NONE; + } else { + tz->ops->get_trip_temp(tz, trip, &trip_temp); + tz->ops->get_trip_type(tz, trip, &trip_type); + } + + trend = get_tz_trend(tz, trip); + + if (tz->temperature >= trip_temp) { + throttle = true; + trace_thermal_zone_trip(tz, trip, trip_type); + } + + dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n", + trip, trip_type, trip_temp, trend, throttle); + + mutex_lock(&tz->lock); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (instance->trip != trip) + continue; + + old_target = instance->target; + instance->target = get_target_state(instance, trend, throttle); + dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n", + old_target, (int)instance->target); + + if (instance->initialized && old_target == instance->target) + continue; + + /* Activate a passive thermal instance */ + if (old_target == THERMAL_NO_TARGET && + instance->target != THERMAL_NO_TARGET) + update_passive_instance(tz, trip_type, 1); + /* Deactivate a passive thermal instance */ + else if (old_target != THERMAL_NO_TARGET && + instance->target == THERMAL_NO_TARGET) + update_passive_instance(tz, trip_type, -1); + + instance->initialized = true; + mutex_lock(&instance->cdev->lock); + instance->cdev->updated = false; /* cdev needs update */ + mutex_unlock(&instance->cdev->lock); + } + + mutex_unlock(&tz->lock); +} + +/** + * step_wise_throttle - throttles devices associated with the given zone + * @tz: thermal_zone_device + * @trip: trip point index + * + * Throttling Logic: This uses the trend of the thermal zone to throttle. + * If the thermal zone is 'heating up' this throttles all the cooling + * devices associated with the zone and its particular trip point, by one + * step. If the zone is 'cooling down' it brings back the performance of + * the devices by one step. + */ +static int step_wise_throttle(struct thermal_zone_device *tz, int trip) +{ + struct thermal_instance *instance; + + thermal_zone_trip_update(tz, trip); + + if (tz->forced_passive) + thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE); + + mutex_lock(&tz->lock); + + list_for_each_entry(instance, &tz->thermal_instances, tz_node) + thermal_cdev_update(instance->cdev); + + mutex_unlock(&tz->lock); + + return 0; +} + +static struct thermal_governor thermal_gov_step_wise = { + .name = "step_wise", + .throttle = step_wise_throttle, +}; +THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise); diff --git a/drivers/thermal/gov_user_space.c b/drivers/thermal/gov_user_space.c new file mode 100644 index 000000000000..82a7198bbe71 --- /dev/null +++ b/drivers/thermal/gov_user_space.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * user_space.c - A simple user space Thermal events notifier + * + * Copyright (C) 2012 Intel Corp + * Copyright (C) 2012 Durgadoss R + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#include +#include + +#include "thermal_core.h" + +/** + * notify_user_space - Notifies user space about thermal events + * @tz: thermal_zone_device + * @trip: trip point index + * + * This function notifies the user space through UEvents. + */ +static int notify_user_space(struct thermal_zone_device *tz, int trip) +{ + char *thermal_prop[5]; + int i; + + mutex_lock(&tz->lock); + thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type); + thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature); + thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip); + thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", tz->notify_event); + thermal_prop[4] = NULL; + kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop); + for (i = 0; i < 4; ++i) + kfree(thermal_prop[i]); + mutex_unlock(&tz->lock); + return 0; +} + +static struct thermal_governor thermal_gov_user_space = { + .name = "user_space", + .throttle = notify_user_space, +}; +THERMAL_GOVERNOR_DECLARE(thermal_gov_user_space); diff --git a/drivers/thermal/power_allocator.c b/drivers/thermal/power_allocator.c deleted file mode 100644 index 44636475b2a3..000000000000 --- a/drivers/thermal/power_allocator.c +++ /dev/null @@ -1,654 +0,0 @@ -/* - * A power allocator to manage temperature - * - * Copyright (C) 2014 ARM Ltd. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#define pr_fmt(fmt) "Power allocator: " fmt - -#include -#include -#include - -#define CREATE_TRACE_POINTS -#include - -#include "thermal_core.h" - -#define INVALID_TRIP -1 - -#define FRAC_BITS 10 -#define int_to_frac(x) ((x) << FRAC_BITS) -#define frac_to_int(x) ((x) >> FRAC_BITS) - -/** - * mul_frac() - multiply two fixed-point numbers - * @x: first multiplicand - * @y: second multiplicand - * - * Return: the result of multiplying two fixed-point numbers. The - * result is also a fixed-point number. - */ -static inline s64 mul_frac(s64 x, s64 y) -{ - return (x * y) >> FRAC_BITS; -} - -/** - * div_frac() - divide two fixed-point numbers - * @x: the dividend - * @y: the divisor - * - * Return: the result of dividing two fixed-point numbers. The - * result is also a fixed-point number. - */ -static inline s64 div_frac(s64 x, s64 y) -{ - return div_s64(x << FRAC_BITS, y); -} - -/** - * struct power_allocator_params - parameters for the power allocator governor - * @allocated_tzp: whether we have allocated tzp for this thermal zone and - * it needs to be freed on unbind - * @err_integral: accumulated error in the PID controller. - * @prev_err: error in the previous iteration of the PID controller. - * Used to calculate the derivative term. - * @trip_switch_on: first passive trip point of the thermal zone. The - * governor switches on when this trip point is crossed. - * If the thermal zone only has one passive trip point, - * @trip_switch_on should be INVALID_TRIP. - * @trip_max_desired_temperature: last passive trip point of the thermal - * zone. The temperature we are - * controlling for. - */ -struct power_allocator_params { - bool allocated_tzp; - s64 err_integral; - s32 prev_err; - int trip_switch_on; - int trip_max_desired_temperature; -}; - -/** - * estimate_sustainable_power() - Estimate the sustainable power of a thermal zone - * @tz: thermal zone we are operating in - * - * For thermal zones that don't provide a sustainable_power in their - * thermal_zone_params, estimate one. Calculate it using the minimum - * power of all the cooling devices as that gives a valid value that - * can give some degree of functionality. For optimal performance of - * this governor, provide a sustainable_power in the thermal zone's - * thermal_zone_params. - */ -static u32 estimate_sustainable_power(struct thermal_zone_device *tz) -{ - u32 sustainable_power = 0; - struct thermal_instance *instance; - struct power_allocator_params *params = tz->governor_data; - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - struct thermal_cooling_device *cdev = instance->cdev; - u32 min_power; - - if (instance->trip != params->trip_max_desired_temperature) - continue; - - if (power_actor_get_min_power(cdev, tz, &min_power)) - continue; - - sustainable_power += min_power; - } - - return sustainable_power; -} - -/** - * estimate_pid_constants() - Estimate the constants for the PID controller - * @tz: thermal zone for which to estimate the constants - * @sustainable_power: sustainable power for the thermal zone - * @trip_switch_on: trip point number for the switch on temperature - * @control_temp: target temperature for the power allocator governor - * @force: whether to force the update of the constants - * - * This function is used to update the estimation of the PID - * controller constants in struct thermal_zone_parameters. - * Sustainable power is provided in case it was estimated. The - * estimated sustainable_power should not be stored in the - * thermal_zone_parameters so it has to be passed explicitly to this - * function. - * - * If @force is not set, the values in the thermal zone's parameters - * are preserved if they are not zero. If @force is set, the values - * in thermal zone's parameters are overwritten. - */ -static void estimate_pid_constants(struct thermal_zone_device *tz, - u32 sustainable_power, int trip_switch_on, - int control_temp, bool force) -{ - int ret; - int switch_on_temp; - u32 temperature_threshold; - - ret = tz->ops->get_trip_temp(tz, trip_switch_on, &switch_on_temp); - if (ret) - switch_on_temp = 0; - - temperature_threshold = control_temp - switch_on_temp; - /* - * estimate_pid_constants() tries to find appropriate default - * values for thermal zones that don't provide them. If a - * system integrator has configured a thermal zone with two - * passive trip points at the same temperature, that person - * hasn't put any effort to set up the thermal zone properly - * so just give up. - */ - if (!temperature_threshold) - return; - - if (!tz->tzp->k_po || force) - tz->tzp->k_po = int_to_frac(sustainable_power) / - temperature_threshold; - - if (!tz->tzp->k_pu || force) - tz->tzp->k_pu = int_to_frac(2 * sustainable_power) / - temperature_threshold; - - if (!tz->tzp->k_i || force) - tz->tzp->k_i = int_to_frac(10) / 1000; - /* - * The default for k_d and integral_cutoff is 0, so we can - * leave them as they are. - */ -} - -/** - * pid_controller() - PID controller - * @tz: thermal zone we are operating in - * @control_temp: the target temperature in millicelsius - * @max_allocatable_power: maximum allocatable power for this thermal zone - * - * This PID controller increases the available power budget so that the - * temperature of the thermal zone gets as close as possible to - * @control_temp and limits the power if it exceeds it. k_po is the - * proportional term when we are overshooting, k_pu is the - * proportional term when we are undershooting. integral_cutoff is a - * threshold below which we stop accumulating the error. The - * accumulated error is only valid if the requested power will make - * the system warmer. If the system is mostly idle, there's no point - * in accumulating positive error. - * - * Return: The power budget for the next period. - */ -static u32 pid_controller(struct thermal_zone_device *tz, - int control_temp, - u32 max_allocatable_power) -{ - s64 p, i, d, power_range; - s32 err, max_power_frac; - u32 sustainable_power; - struct power_allocator_params *params = tz->governor_data; - - max_power_frac = int_to_frac(max_allocatable_power); - - if (tz->tzp->sustainable_power) { - sustainable_power = tz->tzp->sustainable_power; - } else { - sustainable_power = estimate_sustainable_power(tz); - estimate_pid_constants(tz, sustainable_power, - params->trip_switch_on, control_temp, - true); - } - - err = control_temp - tz->temperature; - err = int_to_frac(err); - - /* Calculate the proportional term */ - p = mul_frac(err < 0 ? tz->tzp->k_po : tz->tzp->k_pu, err); - - /* - * Calculate the integral term - * - * if the error is less than cut off allow integration (but - * the integral is limited to max power) - */ - i = mul_frac(tz->tzp->k_i, params->err_integral); - - if (err < int_to_frac(tz->tzp->integral_cutoff)) { - s64 i_next = i + mul_frac(tz->tzp->k_i, err); - - if (abs(i_next) < max_power_frac) { - i = i_next; - params->err_integral += err; - } - } - - /* - * Calculate the derivative term - * - * We do err - prev_err, so with a positive k_d, a decreasing - * error (i.e. driving closer to the line) results in less - * power being applied, slowing down the controller) - */ - d = mul_frac(tz->tzp->k_d, err - params->prev_err); - d = div_frac(d, tz->passive_delay); - params->prev_err = err; - - power_range = p + i + d; - - /* feed-forward the known sustainable dissipatable power */ - power_range = sustainable_power + frac_to_int(power_range); - - power_range = clamp(power_range, (s64)0, (s64)max_allocatable_power); - - trace_thermal_power_allocator_pid(tz, frac_to_int(err), - frac_to_int(params->err_integral), - frac_to_int(p), frac_to_int(i), - frac_to_int(d), power_range); - - return power_range; -} - -/** - * divvy_up_power() - divvy the allocated power between the actors - * @req_power: each actor's requested power - * @max_power: each actor's maximum available power - * @num_actors: size of the @req_power, @max_power and @granted_power's array - * @total_req_power: sum of @req_power - * @power_range: total allocated power - * @granted_power: output array: each actor's granted power - * @extra_actor_power: an appropriately sized array to be used in the - * function as temporary storage of the extra power given - * to the actors - * - * This function divides the total allocated power (@power_range) - * fairly between the actors. It first tries to give each actor a - * share of the @power_range according to how much power it requested - * compared to the rest of the actors. For example, if only one actor - * requests power, then it receives all the @power_range. If - * three actors each requests 1mW, each receives a third of the - * @power_range. - * - * If any actor received more than their maximum power, then that - * surplus is re-divvied among the actors based on how far they are - * from their respective maximums. - * - * Granted power for each actor is written to @granted_power, which - * should've been allocated by the calling function. - */ -static void divvy_up_power(u32 *req_power, u32 *max_power, int num_actors, - u32 total_req_power, u32 power_range, - u32 *granted_power, u32 *extra_actor_power) -{ - u32 extra_power, capped_extra_power; - int i; - - /* - * Prevent division by 0 if none of the actors request power. - */ - if (!total_req_power) - total_req_power = 1; - - capped_extra_power = 0; - extra_power = 0; - for (i = 0; i < num_actors; i++) { - u64 req_range = (u64)req_power[i] * power_range; - - granted_power[i] = DIV_ROUND_CLOSEST_ULL(req_range, - total_req_power); - - if (granted_power[i] > max_power[i]) { - extra_power += granted_power[i] - max_power[i]; - granted_power[i] = max_power[i]; - } - - extra_actor_power[i] = max_power[i] - granted_power[i]; - capped_extra_power += extra_actor_power[i]; - } - - if (!extra_power) - return; - - /* - * Re-divvy the reclaimed extra among actors based on - * how far they are from the max - */ - extra_power = min(extra_power, capped_extra_power); - if (capped_extra_power > 0) - for (i = 0; i < num_actors; i++) - granted_power[i] += (extra_actor_power[i] * - extra_power) / capped_extra_power; -} - -static int allocate_power(struct thermal_zone_device *tz, - int control_temp) -{ - struct thermal_instance *instance; - struct power_allocator_params *params = tz->governor_data; - u32 *req_power, *max_power, *granted_power, *extra_actor_power; - u32 *weighted_req_power; - u32 total_req_power, max_allocatable_power, total_weighted_req_power; - u32 total_granted_power, power_range; - int i, num_actors, total_weight, ret = 0; - int trip_max_desired_temperature = params->trip_max_desired_temperature; - - mutex_lock(&tz->lock); - - num_actors = 0; - total_weight = 0; - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if ((instance->trip == trip_max_desired_temperature) && - cdev_is_power_actor(instance->cdev)) { - num_actors++; - total_weight += instance->weight; - } - } - - if (!num_actors) { - ret = -ENODEV; - goto unlock; - } - - /* - * We need to allocate five arrays of the same size: - * req_power, max_power, granted_power, extra_actor_power and - * weighted_req_power. They are going to be needed until this - * function returns. Allocate them all in one go to simplify - * the allocation and deallocation logic. - */ - BUILD_BUG_ON(sizeof(*req_power) != sizeof(*max_power)); - BUILD_BUG_ON(sizeof(*req_power) != sizeof(*granted_power)); - BUILD_BUG_ON(sizeof(*req_power) != sizeof(*extra_actor_power)); - BUILD_BUG_ON(sizeof(*req_power) != sizeof(*weighted_req_power)); - req_power = kcalloc(num_actors * 5, sizeof(*req_power), GFP_KERNEL); - if (!req_power) { - ret = -ENOMEM; - goto unlock; - } - - max_power = &req_power[num_actors]; - granted_power = &req_power[2 * num_actors]; - extra_actor_power = &req_power[3 * num_actors]; - weighted_req_power = &req_power[4 * num_actors]; - - i = 0; - total_weighted_req_power = 0; - total_req_power = 0; - max_allocatable_power = 0; - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - int weight; - struct thermal_cooling_device *cdev = instance->cdev; - - if (instance->trip != trip_max_desired_temperature) - continue; - - if (!cdev_is_power_actor(cdev)) - continue; - - if (cdev->ops->get_requested_power(cdev, tz, &req_power[i])) - continue; - - if (!total_weight) - weight = 1 << FRAC_BITS; - else - weight = instance->weight; - - weighted_req_power[i] = frac_to_int(weight * req_power[i]); - - if (power_actor_get_max_power(cdev, tz, &max_power[i])) - continue; - - total_req_power += req_power[i]; - max_allocatable_power += max_power[i]; - total_weighted_req_power += weighted_req_power[i]; - - i++; - } - - power_range = pid_controller(tz, control_temp, max_allocatable_power); - - divvy_up_power(weighted_req_power, max_power, num_actors, - total_weighted_req_power, power_range, granted_power, - extra_actor_power); - - total_granted_power = 0; - i = 0; - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if (instance->trip != trip_max_desired_temperature) - continue; - - if (!cdev_is_power_actor(instance->cdev)) - continue; - - power_actor_set_power(instance->cdev, instance, - granted_power[i]); - total_granted_power += granted_power[i]; - - i++; - } - - trace_thermal_power_allocator(tz, req_power, total_req_power, - granted_power, total_granted_power, - num_actors, power_range, - max_allocatable_power, tz->temperature, - control_temp - tz->temperature); - - kfree(req_power); -unlock: - mutex_unlock(&tz->lock); - - return ret; -} - -/** - * get_governor_trips() - get the number of the two trip points that are key for this governor - * @tz: thermal zone to operate on - * @params: pointer to private data for this governor - * - * The power allocator governor works optimally with two trips points: - * a "switch on" trip point and a "maximum desired temperature". These - * are defined as the first and last passive trip points. - * - * If there is only one trip point, then that's considered to be the - * "maximum desired temperature" trip point and the governor is always - * on. If there are no passive or active trip points, then the - * governor won't do anything. In fact, its throttle function - * won't be called at all. - */ -static void get_governor_trips(struct thermal_zone_device *tz, - struct power_allocator_params *params) -{ - int i, last_active, last_passive; - bool found_first_passive; - - found_first_passive = false; - last_active = INVALID_TRIP; - last_passive = INVALID_TRIP; - - for (i = 0; i < tz->trips; i++) { - enum thermal_trip_type type; - int ret; - - ret = tz->ops->get_trip_type(tz, i, &type); - if (ret) { - dev_warn(&tz->device, - "Failed to get trip point %d type: %d\n", i, - ret); - continue; - } - - if (type == THERMAL_TRIP_PASSIVE) { - if (!found_first_passive) { - params->trip_switch_on = i; - found_first_passive = true; - } else { - last_passive = i; - } - } else if (type == THERMAL_TRIP_ACTIVE) { - last_active = i; - } else { - break; - } - } - - if (last_passive != INVALID_TRIP) { - params->trip_max_desired_temperature = last_passive; - } else if (found_first_passive) { - params->trip_max_desired_temperature = params->trip_switch_on; - params->trip_switch_on = INVALID_TRIP; - } else { - params->trip_switch_on = INVALID_TRIP; - params->trip_max_desired_temperature = last_active; - } -} - -static void reset_pid_controller(struct power_allocator_params *params) -{ - params->err_integral = 0; - params->prev_err = 0; -} - -static void allow_maximum_power(struct thermal_zone_device *tz) -{ - struct thermal_instance *instance; - struct power_allocator_params *params = tz->governor_data; - - mutex_lock(&tz->lock); - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if ((instance->trip != params->trip_max_desired_temperature) || - (!cdev_is_power_actor(instance->cdev))) - continue; - - instance->target = 0; - mutex_lock(&instance->cdev->lock); - instance->cdev->updated = false; - mutex_unlock(&instance->cdev->lock); - thermal_cdev_update(instance->cdev); - } - mutex_unlock(&tz->lock); -} - -/** - * power_allocator_bind() - bind the power_allocator governor to a thermal zone - * @tz: thermal zone to bind it to - * - * Initialize the PID controller parameters and bind it to the thermal - * zone. - * - * Return: 0 on success, or -ENOMEM if we ran out of memory. - */ -static int power_allocator_bind(struct thermal_zone_device *tz) -{ - int ret; - struct power_allocator_params *params; - int control_temp; - - params = kzalloc(sizeof(*params), GFP_KERNEL); - if (!params) - return -ENOMEM; - - if (!tz->tzp) { - tz->tzp = kzalloc(sizeof(*tz->tzp), GFP_KERNEL); - if (!tz->tzp) { - ret = -ENOMEM; - goto free_params; - } - - params->allocated_tzp = true; - } - - if (!tz->tzp->sustainable_power) - dev_warn(&tz->device, "power_allocator: sustainable_power will be estimated\n"); - - get_governor_trips(tz, params); - - if (tz->trips > 0) { - ret = tz->ops->get_trip_temp(tz, - params->trip_max_desired_temperature, - &control_temp); - if (!ret) - estimate_pid_constants(tz, tz->tzp->sustainable_power, - params->trip_switch_on, - control_temp, false); - } - - reset_pid_controller(params); - - tz->governor_data = params; - - return 0; - -free_params: - kfree(params); - - return ret; -} - -static void power_allocator_unbind(struct thermal_zone_device *tz) -{ - struct power_allocator_params *params = tz->governor_data; - - dev_dbg(&tz->device, "Unbinding from thermal zone %d\n", tz->id); - - if (params->allocated_tzp) { - kfree(tz->tzp); - tz->tzp = NULL; - } - - kfree(tz->governor_data); - tz->governor_data = NULL; -} - -static int power_allocator_throttle(struct thermal_zone_device *tz, int trip) -{ - int ret; - int switch_on_temp, control_temp; - struct power_allocator_params *params = tz->governor_data; - - /* - * We get called for every trip point but we only need to do - * our calculations once - */ - if (trip != params->trip_max_desired_temperature) - return 0; - - ret = tz->ops->get_trip_temp(tz, params->trip_switch_on, - &switch_on_temp); - if (!ret && (tz->temperature < switch_on_temp)) { - tz->passive = 0; - reset_pid_controller(params); - allow_maximum_power(tz); - return 0; - } - - tz->passive = 1; - - ret = tz->ops->get_trip_temp(tz, params->trip_max_desired_temperature, - &control_temp); - if (ret) { - dev_warn(&tz->device, - "Failed to get the maximum desired temperature: %d\n", - ret); - return ret; - } - - return allocate_power(tz, control_temp); -} - -static struct thermal_governor thermal_gov_power_allocator = { - .name = "power_allocator", - .bind_to_tz = power_allocator_bind, - .unbind_from_tz = power_allocator_unbind, - .throttle = power_allocator_throttle, -}; -THERMAL_GOVERNOR_DECLARE(thermal_gov_power_allocator); diff --git a/drivers/thermal/step_wise.c b/drivers/thermal/step_wise.c deleted file mode 100644 index 2ae7198d3067..000000000000 --- a/drivers/thermal/step_wise.c +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * step_wise.c - A step-by-step Thermal throttling governor - * - * Copyright (C) 2012 Intel Corp - * Copyright (C) 2012 Durgadoss R - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -#include -#include - -#include "thermal_core.h" - -/* - * If the temperature is higher than a trip point, - * a. if the trend is THERMAL_TREND_RAISING, use higher cooling - * state for this trip point - * b. if the trend is THERMAL_TREND_DROPPING, do nothing - * c. if the trend is THERMAL_TREND_RAISE_FULL, use upper limit - * for this trip point - * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit - * for this trip point - * If the temperature is lower than a trip point, - * a. if the trend is THERMAL_TREND_RAISING, do nothing - * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling - * state for this trip point, if the cooling state already - * equals lower limit, deactivate the thermal instance - * c. if the trend is THERMAL_TREND_RAISE_FULL, do nothing - * d. if the trend is THERMAL_TREND_DROP_FULL, use lower limit, - * if the cooling state already equals lower limit, - * deactivate the thermal instance - */ -static unsigned long get_target_state(struct thermal_instance *instance, - enum thermal_trend trend, bool throttle) -{ - struct thermal_cooling_device *cdev = instance->cdev; - unsigned long cur_state; - unsigned long next_target; - - /* - * We keep this instance the way it is by default. - * Otherwise, we use the current state of the - * cdev in use to determine the next_target. - */ - cdev->ops->get_cur_state(cdev, &cur_state); - next_target = instance->target; - dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state); - - if (!instance->initialized) { - if (throttle) { - next_target = (cur_state + 1) >= instance->upper ? - instance->upper : - ((cur_state + 1) < instance->lower ? - instance->lower : (cur_state + 1)); - } else { - next_target = THERMAL_NO_TARGET; - } - - return next_target; - } - - switch (trend) { - case THERMAL_TREND_RAISING: - if (throttle) { - next_target = cur_state < instance->upper ? - (cur_state + 1) : instance->upper; - if (next_target < instance->lower) - next_target = instance->lower; - } - break; - case THERMAL_TREND_RAISE_FULL: - if (throttle) - next_target = instance->upper; - break; - case THERMAL_TREND_DROPPING: - if (cur_state <= instance->lower) { - if (!throttle) - next_target = THERMAL_NO_TARGET; - } else { - if (!throttle) { - next_target = cur_state - 1; - if (next_target > instance->upper) - next_target = instance->upper; - } - } - break; - case THERMAL_TREND_DROP_FULL: - if (cur_state == instance->lower) { - if (!throttle) - next_target = THERMAL_NO_TARGET; - } else - next_target = instance->lower; - break; - default: - break; - } - - return next_target; -} - -static void update_passive_instance(struct thermal_zone_device *tz, - enum thermal_trip_type type, int value) -{ - /* - * If value is +1, activate a passive instance. - * If value is -1, deactivate a passive instance. - */ - if (type == THERMAL_TRIP_PASSIVE || type == THERMAL_TRIPS_NONE) - tz->passive += value; -} - -static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip) -{ - int trip_temp; - enum thermal_trip_type trip_type; - enum thermal_trend trend; - struct thermal_instance *instance; - bool throttle = false; - int old_target; - - if (trip == THERMAL_TRIPS_NONE) { - trip_temp = tz->forced_passive; - trip_type = THERMAL_TRIPS_NONE; - } else { - tz->ops->get_trip_temp(tz, trip, &trip_temp); - tz->ops->get_trip_type(tz, trip, &trip_type); - } - - trend = get_tz_trend(tz, trip); - - if (tz->temperature >= trip_temp) { - throttle = true; - trace_thermal_zone_trip(tz, trip, trip_type); - } - - dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n", - trip, trip_type, trip_temp, trend, throttle); - - mutex_lock(&tz->lock); - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) { - if (instance->trip != trip) - continue; - - old_target = instance->target; - instance->target = get_target_state(instance, trend, throttle); - dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n", - old_target, (int)instance->target); - - if (instance->initialized && old_target == instance->target) - continue; - - /* Activate a passive thermal instance */ - if (old_target == THERMAL_NO_TARGET && - instance->target != THERMAL_NO_TARGET) - update_passive_instance(tz, trip_type, 1); - /* Deactivate a passive thermal instance */ - else if (old_target != THERMAL_NO_TARGET && - instance->target == THERMAL_NO_TARGET) - update_passive_instance(tz, trip_type, -1); - - instance->initialized = true; - mutex_lock(&instance->cdev->lock); - instance->cdev->updated = false; /* cdev needs update */ - mutex_unlock(&instance->cdev->lock); - } - - mutex_unlock(&tz->lock); -} - -/** - * step_wise_throttle - throttles devices associated with the given zone - * @tz: thermal_zone_device - * @trip: trip point index - * - * Throttling Logic: This uses the trend of the thermal zone to throttle. - * If the thermal zone is 'heating up' this throttles all the cooling - * devices associated with the zone and its particular trip point, by one - * step. If the zone is 'cooling down' it brings back the performance of - * the devices by one step. - */ -static int step_wise_throttle(struct thermal_zone_device *tz, int trip) -{ - struct thermal_instance *instance; - - thermal_zone_trip_update(tz, trip); - - if (tz->forced_passive) - thermal_zone_trip_update(tz, THERMAL_TRIPS_NONE); - - mutex_lock(&tz->lock); - - list_for_each_entry(instance, &tz->thermal_instances, tz_node) - thermal_cdev_update(instance->cdev); - - mutex_unlock(&tz->lock); - - return 0; -} - -static struct thermal_governor thermal_gov_step_wise = { - .name = "step_wise", - .throttle = step_wise_throttle, -}; -THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise); diff --git a/drivers/thermal/user_space.c b/drivers/thermal/user_space.c deleted file mode 100644 index 82a7198bbe71..000000000000 --- a/drivers/thermal/user_space.c +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * user_space.c - A simple user space Thermal events notifier - * - * Copyright (C) 2012 Intel Corp - * Copyright (C) 2012 Durgadoss R - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -#include -#include - -#include "thermal_core.h" - -/** - * notify_user_space - Notifies user space about thermal events - * @tz: thermal_zone_device - * @trip: trip point index - * - * This function notifies the user space through UEvents. - */ -static int notify_user_space(struct thermal_zone_device *tz, int trip) -{ - char *thermal_prop[5]; - int i; - - mutex_lock(&tz->lock); - thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", tz->type); - thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", tz->temperature); - thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=%d", trip); - thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", tz->notify_event); - thermal_prop[4] = NULL; - kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, thermal_prop); - for (i = 0; i < 4; ++i) - kfree(thermal_prop[i]); - mutex_unlock(&tz->lock); - return 0; -} - -static struct thermal_governor thermal_gov_user_space = { - .name = "user_space", - .throttle = notify_user_space, -}; -THERMAL_GOVERNOR_DECLARE(thermal_gov_user_space); -- cgit v1.2.3 From 14adf6c83f7c6953a136d9d4beda79004191e729 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Mon, 11 May 2020 17:55:02 +0530 Subject: thermal/of: Rename of-thermal.c Core thermal framework code files should start with thermal_*. of-thermal.c does not follow this pattern and can easily be confused with platform driver. Fix this by renaming it to thermal_of.c Signed-off-by: Amit Kucheria Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/f5e233d5c5dcc7c7cb56b3448da255cb2c9ef0d1.1589199124.git.amit.kucheria@linaro.org --- drivers/thermal/Makefile | 2 +- drivers/thermal/of-thermal.c | 1151 ------------------------------------------ drivers/thermal/thermal_of.c | 1151 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1152 insertions(+), 1152 deletions(-) delete mode 100644 drivers/thermal/of-thermal.c create mode 100644 drivers/thermal/thermal_of.c diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 757c40a71940..0c8b84a09b9a 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -9,7 +9,7 @@ thermal_sys-y += thermal_core.o thermal_sysfs.o \ # interface to/from other layers providing sensors thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o -thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o +thermal_sys-$(CONFIG_THERMAL_OF) += thermal_of.o # governors thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += gov_fair_share.o diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c deleted file mode 100644 index ddf88dbe7ba2..000000000000 --- a/drivers/thermal/of-thermal.c +++ /dev/null @@ -1,1151 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * of-thermal.c - Generic Thermal Management device tree support. - * - * Copyright (C) 2013 Texas Instruments - * Copyright (C) 2013 Eduardo Valentin - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "thermal_core.h" - -/*** Private data structures to represent thermal device tree data ***/ - -/** - * struct __thermal_cooling_bind_param - a cooling device for a trip point - * @cooling_device: a pointer to identify the referred cooling device - * @min: minimum cooling state used at this trip point - * @max: maximum cooling state used at this trip point - */ - -struct __thermal_cooling_bind_param { - struct device_node *cooling_device; - unsigned long min; - unsigned long max; -}; - -/** - * struct __thermal_bind_param - a match between trip and cooling device - * @tcbp: a pointer to an array of cooling devices - * @count: number of elements in array - * @trip_id: the trip point index - * @usage: the percentage (from 0 to 100) of cooling contribution - */ - -struct __thermal_bind_params { - struct __thermal_cooling_bind_param *tcbp; - unsigned int count; - unsigned int trip_id; - unsigned int usage; -}; - -/** - * struct __thermal_zone - internal representation of a thermal zone - * @mode: current thermal zone device mode (enabled/disabled) - * @passive_delay: polling interval while passive cooling is activated - * @polling_delay: zone polling interval - * @slope: slope of the temperature adjustment curve - * @offset: offset of the temperature adjustment curve - * @ntrips: number of trip points - * @trips: an array of trip points (0..ntrips - 1) - * @num_tbps: number of thermal bind params - * @tbps: an array of thermal bind params (0..num_tbps - 1) - * @sensor_data: sensor private data used while reading temperature and trend - * @ops: set of callbacks to handle the thermal zone based on DT - */ - -struct __thermal_zone { - enum thermal_device_mode mode; - int passive_delay; - int polling_delay; - int slope; - int offset; - - /* trip data */ - int ntrips; - struct thermal_trip *trips; - - /* cooling binding data */ - int num_tbps; - struct __thermal_bind_params *tbps; - - /* sensor interface */ - void *sensor_data; - const struct thermal_zone_of_device_ops *ops; -}; - -/*** DT thermal zone device callbacks ***/ - -static int of_thermal_get_temp(struct thermal_zone_device *tz, - int *temp) -{ - struct __thermal_zone *data = tz->devdata; - - if (!data->ops->get_temp) - return -EINVAL; - - return data->ops->get_temp(data->sensor_data, temp); -} - -static int of_thermal_set_trips(struct thermal_zone_device *tz, - int low, int high) -{ - struct __thermal_zone *data = tz->devdata; - - if (!data->ops || !data->ops->set_trips) - return -EINVAL; - - return data->ops->set_trips(data->sensor_data, low, high); -} - -/** - * of_thermal_get_ntrips - function to export number of available trip - * points. - * @tz: pointer to a thermal zone - * - * This function is a globally visible wrapper to get number of trip points - * stored in the local struct __thermal_zone - * - * Return: number of available trip points, -ENODEV when data not available - */ -int of_thermal_get_ntrips(struct thermal_zone_device *tz) -{ - struct __thermal_zone *data = tz->devdata; - - if (!data || IS_ERR(data)) - return -ENODEV; - - return data->ntrips; -} -EXPORT_SYMBOL_GPL(of_thermal_get_ntrips); - -/** - * of_thermal_is_trip_valid - function to check if trip point is valid - * - * @tz: pointer to a thermal zone - * @trip: trip point to evaluate - * - * This function is responsible for checking if passed trip point is valid - * - * Return: true if trip point is valid, false otherwise - */ -bool of_thermal_is_trip_valid(struct thermal_zone_device *tz, int trip) -{ - struct __thermal_zone *data = tz->devdata; - - if (!data || trip >= data->ntrips || trip < 0) - return false; - - return true; -} -EXPORT_SYMBOL_GPL(of_thermal_is_trip_valid); - -/** - * of_thermal_get_trip_points - function to get access to a globally exported - * trip points - * - * @tz: pointer to a thermal zone - * - * This function provides a pointer to trip points table - * - * Return: pointer to trip points table, NULL otherwise - */ -const struct thermal_trip * -of_thermal_get_trip_points(struct thermal_zone_device *tz) -{ - struct __thermal_zone *data = tz->devdata; - - if (!data) - return NULL; - - return data->trips; -} -EXPORT_SYMBOL_GPL(of_thermal_get_trip_points); - -/** - * of_thermal_set_emul_temp - function to set emulated temperature - * - * @tz: pointer to a thermal zone - * @temp: temperature to set - * - * This function gives the ability to set emulated value of temperature, - * which is handy for debugging - * - * Return: zero on success, error code otherwise - */ -static int of_thermal_set_emul_temp(struct thermal_zone_device *tz, - int temp) -{ - struct __thermal_zone *data = tz->devdata; - - return data->ops->set_emul_temp(data->sensor_data, temp); -} - -static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip, - enum thermal_trend *trend) -{ - struct __thermal_zone *data = tz->devdata; - - if (!data->ops->get_trend) - return -EINVAL; - - return data->ops->get_trend(data->sensor_data, trip, trend); -} - -static int of_thermal_bind(struct thermal_zone_device *thermal, - struct thermal_cooling_device *cdev) -{ - struct __thermal_zone *data = thermal->devdata; - struct __thermal_bind_params *tbp; - struct __thermal_cooling_bind_param *tcbp; - int i, j; - - if (!data || IS_ERR(data)) - return -ENODEV; - - /* find where to bind */ - for (i = 0; i < data->num_tbps; i++) { - tbp = data->tbps + i; - - for (j = 0; j < tbp->count; j++) { - tcbp = tbp->tcbp + j; - - if (tcbp->cooling_device == cdev->np) { - int ret; - - ret = thermal_zone_bind_cooling_device(thermal, - tbp->trip_id, cdev, - tcbp->max, - tcbp->min, - tbp->usage); - if (ret) - return ret; - } - } - } - - return 0; -} - -static int of_thermal_unbind(struct thermal_zone_device *thermal, - struct thermal_cooling_device *cdev) -{ - struct __thermal_zone *data = thermal->devdata; - struct __thermal_bind_params *tbp; - struct __thermal_cooling_bind_param *tcbp; - int i, j; - - if (!data || IS_ERR(data)) - return -ENODEV; - - /* find where to unbind */ - for (i = 0; i < data->num_tbps; i++) { - tbp = data->tbps + i; - - for (j = 0; j < tbp->count; j++) { - tcbp = tbp->tcbp + j; - - if (tcbp->cooling_device == cdev->np) { - int ret; - - ret = thermal_zone_unbind_cooling_device(thermal, - tbp->trip_id, cdev); - if (ret) - return ret; - } - } - } - - return 0; -} - -static int of_thermal_get_mode(struct thermal_zone_device *tz, - enum thermal_device_mode *mode) -{ - struct __thermal_zone *data = tz->devdata; - - *mode = data->mode; - - return 0; -} - -static int of_thermal_set_mode(struct thermal_zone_device *tz, - enum thermal_device_mode mode) -{ - struct __thermal_zone *data = tz->devdata; - - mutex_lock(&tz->lock); - - if (mode == THERMAL_DEVICE_ENABLED) { - tz->polling_delay = data->polling_delay; - tz->passive_delay = data->passive_delay; - } else { - tz->polling_delay = 0; - tz->passive_delay = 0; - } - - mutex_unlock(&tz->lock); - - data->mode = mode; - thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); - - return 0; -} - -static int of_thermal_get_trip_type(struct thermal_zone_device *tz, int trip, - enum thermal_trip_type *type) -{ - struct __thermal_zone *data = tz->devdata; - - if (trip >= data->ntrips || trip < 0) - return -EDOM; - - *type = data->trips[trip].type; - - return 0; -} - -static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip, - int *temp) -{ - struct __thermal_zone *data = tz->devdata; - - if (trip >= data->ntrips || trip < 0) - return -EDOM; - - *temp = data->trips[trip].temperature; - - return 0; -} - -static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip, - int temp) -{ - struct __thermal_zone *data = tz->devdata; - - if (trip >= data->ntrips || trip < 0) - return -EDOM; - - if (data->ops->set_trip_temp) { - int ret; - - ret = data->ops->set_trip_temp(data->sensor_data, trip, temp); - if (ret) - return ret; - } - - /* thermal framework should take care of data->mask & (1 << trip) */ - data->trips[trip].temperature = temp; - - return 0; -} - -static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip, - int *hyst) -{ - struct __thermal_zone *data = tz->devdata; - - if (trip >= data->ntrips || trip < 0) - return -EDOM; - - *hyst = data->trips[trip].hysteresis; - - return 0; -} - -static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip, - int hyst) -{ - struct __thermal_zone *data = tz->devdata; - - if (trip >= data->ntrips || trip < 0) - return -EDOM; - - /* thermal framework should take care of data->mask & (1 << trip) */ - data->trips[trip].hysteresis = hyst; - - return 0; -} - -static int of_thermal_get_crit_temp(struct thermal_zone_device *tz, - int *temp) -{ - struct __thermal_zone *data = tz->devdata; - int i; - - for (i = 0; i < data->ntrips; i++) - if (data->trips[i].type == THERMAL_TRIP_CRITICAL) { - *temp = data->trips[i].temperature; - return 0; - } - - return -EINVAL; -} - -static struct thermal_zone_device_ops of_thermal_ops = { - .get_mode = of_thermal_get_mode, - .set_mode = of_thermal_set_mode, - - .get_trip_type = of_thermal_get_trip_type, - .get_trip_temp = of_thermal_get_trip_temp, - .set_trip_temp = of_thermal_set_trip_temp, - .get_trip_hyst = of_thermal_get_trip_hyst, - .set_trip_hyst = of_thermal_set_trip_hyst, - .get_crit_temp = of_thermal_get_crit_temp, - - .bind = of_thermal_bind, - .unbind = of_thermal_unbind, -}; - -/*** sensor API ***/ - -static struct thermal_zone_device * -thermal_zone_of_add_sensor(struct device_node *zone, - struct device_node *sensor, void *data, - const struct thermal_zone_of_device_ops *ops) -{ - struct thermal_zone_device *tzd; - struct __thermal_zone *tz; - - tzd = thermal_zone_get_zone_by_name(zone->name); - if (IS_ERR(tzd)) - return ERR_PTR(-EPROBE_DEFER); - - tz = tzd->devdata; - - if (!ops) - return ERR_PTR(-EINVAL); - - mutex_lock(&tzd->lock); - tz->ops = ops; - tz->sensor_data = data; - - tzd->ops->get_temp = of_thermal_get_temp; - tzd->ops->get_trend = of_thermal_get_trend; - - /* - * The thermal zone core will calculate the window if they have set the - * optional set_trips pointer. - */ - if (ops->set_trips) - tzd->ops->set_trips = of_thermal_set_trips; - - if (ops->set_emul_temp) - tzd->ops->set_emul_temp = of_thermal_set_emul_temp; - - mutex_unlock(&tzd->lock); - - return tzd; -} - -/** - * thermal_zone_of_get_sensor_id - get sensor ID from a DT thermal zone - * @tz_np: a valid thermal zone device node. - * @sensor_np: a sensor node of a valid sensor device. - * @id: the sensor ID returned if success. - * - * This function will get sensor ID from a given thermal zone node and - * the sensor node must match the temperature provider @sensor_np. - * - * Return: 0 on success, proper error code otherwise. - */ - -int thermal_zone_of_get_sensor_id(struct device_node *tz_np, - struct device_node *sensor_np, - u32 *id) -{ - struct of_phandle_args sensor_specs; - int ret; - - ret = of_parse_phandle_with_args(tz_np, - "thermal-sensors", - "#thermal-sensor-cells", - 0, - &sensor_specs); - if (ret) - return ret; - - if (sensor_specs.np != sensor_np) { - of_node_put(sensor_specs.np); - return -ENODEV; - } - - if (sensor_specs.args_count > 1) - pr_warn("%pOFn: too many cells in sensor specifier %d\n", - sensor_specs.np, sensor_specs.args_count); - - *id = sensor_specs.args_count ? sensor_specs.args[0] : 0; - - of_node_put(sensor_specs.np); - - return 0; -} -EXPORT_SYMBOL_GPL(thermal_zone_of_get_sensor_id); - -/** - * thermal_zone_of_sensor_register - registers a sensor to a DT thermal zone - * @dev: a valid struct device pointer of a sensor device. Must contain - * a valid .of_node, for the sensor node. - * @sensor_id: a sensor identifier, in case the sensor IP has more - * than one sensors - * @data: a private pointer (owned by the caller) that will be passed - * back, when a temperature reading is needed. - * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp. - * - * This function will search the list of thermal zones described in device - * tree and look for the zone that refer to the sensor device pointed by - * @dev->of_node as temperature providers. For the zone pointing to the - * sensor node, the sensor will be added to the DT thermal zone device. - * - * The thermal zone temperature is provided by the @get_temp function - * pointer. When called, it will have the private pointer @data back. - * - * The thermal zone temperature trend is provided by the @get_trend function - * pointer. When called, it will have the private pointer @data back. - * - * TODO: - * 01 - This function must enqueue the new sensor instead of using - * it as the only source of temperature values. - * - * 02 - There must be a way to match the sensor with all thermal zones - * that refer to it. - * - * Return: On success returns a valid struct thermal_zone_device, - * otherwise, it returns a corresponding ERR_PTR(). Caller must - * check the return value with help of IS_ERR() helper. - */ -struct thermal_zone_device * -thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data, - const struct thermal_zone_of_device_ops *ops) -{ - struct device_node *np, *child, *sensor_np; - struct thermal_zone_device *tzd = ERR_PTR(-ENODEV); - - np = of_find_node_by_name(NULL, "thermal-zones"); - if (!np) - return ERR_PTR(-ENODEV); - - if (!dev || !dev->of_node) { - of_node_put(np); - return ERR_PTR(-ENODEV); - } - - sensor_np = of_node_get(dev->of_node); - - for_each_available_child_of_node(np, child) { - int ret, id; - - /* For now, thermal framework supports only 1 sensor per zone */ - ret = thermal_zone_of_get_sensor_id(child, sensor_np, &id); - if (ret) - continue; - - if (id == sensor_id) { - tzd = thermal_zone_of_add_sensor(child, sensor_np, - data, ops); - if (!IS_ERR(tzd)) - tzd->ops->set_mode(tzd, THERMAL_DEVICE_ENABLED); - - of_node_put(child); - goto exit; - } - } -exit: - of_node_put(sensor_np); - of_node_put(np); - - return tzd; -} -EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_register); - -/** - * thermal_zone_of_sensor_unregister - unregisters a sensor from a DT thermal zone - * @dev: a valid struct device pointer of a sensor device. Must contain - * a valid .of_node, for the sensor node. - * @tzd: a pointer to struct thermal_zone_device where the sensor is registered. - * - * This function removes the sensor callbacks and private data from the - * thermal zone device registered with thermal_zone_of_sensor_register() - * API. It will also silent the zone by remove the .get_temp() and .get_trend() - * thermal zone device callbacks. - * - * TODO: When the support to several sensors per zone is added, this - * function must search the sensor list based on @dev parameter. - * - */ -void thermal_zone_of_sensor_unregister(struct device *dev, - struct thermal_zone_device *tzd) -{ - struct __thermal_zone *tz; - - if (!dev || !tzd || !tzd->devdata) - return; - - tz = tzd->devdata; - - /* no __thermal_zone, nothing to be done */ - if (!tz) - return; - - mutex_lock(&tzd->lock); - tzd->ops->get_temp = NULL; - tzd->ops->get_trend = NULL; - tzd->ops->set_emul_temp = NULL; - - tz->ops = NULL; - tz->sensor_data = NULL; - mutex_unlock(&tzd->lock); -} -EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister); - -static void devm_thermal_zone_of_sensor_release(struct device *dev, void *res) -{ - thermal_zone_of_sensor_unregister(dev, - *(struct thermal_zone_device **)res); -} - -static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res, - void *data) -{ - struct thermal_zone_device **r = res; - - if (WARN_ON(!r || !*r)) - return 0; - - return *r == data; -} - -/** - * devm_thermal_zone_of_sensor_register - Resource managed version of - * thermal_zone_of_sensor_register() - * @dev: a valid struct device pointer of a sensor device. Must contain - * a valid .of_node, for the sensor node. - * @sensor_id: a sensor identifier, in case the sensor IP has more - * than one sensors - * @data: a private pointer (owned by the caller) that will be passed - * back, when a temperature reading is needed. - * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp. - * - * Refer thermal_zone_of_sensor_register() for more details. - * - * Return: On success returns a valid struct thermal_zone_device, - * otherwise, it returns a corresponding ERR_PTR(). Caller must - * check the return value with help of IS_ERR() helper. - * Registered thermal_zone_device device will automatically be - * released when device is unbounded. - */ -struct thermal_zone_device *devm_thermal_zone_of_sensor_register( - struct device *dev, int sensor_id, - void *data, const struct thermal_zone_of_device_ops *ops) -{ - struct thermal_zone_device **ptr, *tzd; - - ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr), - GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); - - tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops); - if (IS_ERR(tzd)) { - devres_free(ptr); - return tzd; - } - - *ptr = tzd; - devres_add(dev, ptr); - - return tzd; -} -EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_register); - -/** - * devm_thermal_zone_of_sensor_unregister - Resource managed version of - * thermal_zone_of_sensor_unregister(). - * @dev: Device for which which resource was allocated. - * @tzd: a pointer to struct thermal_zone_device where the sensor is registered. - * - * This function removes the sensor callbacks and private data from the - * thermal zone device registered with devm_thermal_zone_of_sensor_register() - * API. It will also silent the zone by remove the .get_temp() and .get_trend() - * thermal zone device callbacks. - * Normally this function will not need to be called and the resource - * management code will ensure that the resource is freed. - */ -void devm_thermal_zone_of_sensor_unregister(struct device *dev, - struct thermal_zone_device *tzd) -{ - WARN_ON(devres_release(dev, devm_thermal_zone_of_sensor_release, - devm_thermal_zone_of_sensor_match, tzd)); -} -EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_unregister); - -/*** functions parsing device tree nodes ***/ - -/** - * thermal_of_populate_bind_params - parse and fill cooling map data - * @np: DT node containing a cooling-map node - * @__tbp: data structure to be filled with cooling map info - * @trips: array of thermal zone trip points - * @ntrips: number of trip points inside trips. - * - * This function parses a cooling-map type of node represented by - * @np parameter and fills the read data into @__tbp data structure. - * It needs the already parsed array of trip points of the thermal zone - * in consideration. - * - * Return: 0 on success, proper error code otherwise - */ -static int thermal_of_populate_bind_params(struct device_node *np, - struct __thermal_bind_params *__tbp, - struct thermal_trip *trips, - int ntrips) -{ - struct of_phandle_args cooling_spec; - struct __thermal_cooling_bind_param *__tcbp; - struct device_node *trip; - int ret, i, count; - u32 prop; - - /* Default weight. Usage is optional */ - __tbp->usage = THERMAL_WEIGHT_DEFAULT; - ret = of_property_read_u32(np, "contribution", &prop); - if (ret == 0) - __tbp->usage = prop; - - trip = of_parse_phandle(np, "trip", 0); - if (!trip) { - pr_err("missing trip property\n"); - return -ENODEV; - } - - /* match using device_node */ - for (i = 0; i < ntrips; i++) - if (trip == trips[i].np) { - __tbp->trip_id = i; - break; - } - - if (i == ntrips) { - ret = -ENODEV; - goto end; - } - - count = of_count_phandle_with_args(np, "cooling-device", - "#cooling-cells"); - if (!count) { - pr_err("Add a cooling_device property with at least one device\n"); - goto end; - } - - __tcbp = kcalloc(count, sizeof(*__tcbp), GFP_KERNEL); - if (!__tcbp) - goto end; - - for (i = 0; i < count; i++) { - ret = of_parse_phandle_with_args(np, "cooling-device", - "#cooling-cells", i, &cooling_spec); - if (ret < 0) { - pr_err("Invalid cooling-device entry\n"); - goto free_tcbp; - } - - __tcbp[i].cooling_device = cooling_spec.np; - - if (cooling_spec.args_count >= 2) { /* at least min and max */ - __tcbp[i].min = cooling_spec.args[0]; - __tcbp[i].max = cooling_spec.args[1]; - } else { - pr_err("wrong reference to cooling device, missing limits\n"); - } - } - - __tbp->tcbp = __tcbp; - __tbp->count = count; - - goto end; - -free_tcbp: - for (i = i - 1; i >= 0; i--) - of_node_put(__tcbp[i].cooling_device); - kfree(__tcbp); -end: - of_node_put(trip); - - return ret; -} - -/* - * It maps 'enum thermal_trip_type' found in include/linux/thermal.h - * into the device tree binding of 'trip', property type. - */ -static const char * const trip_types[] = { - [THERMAL_TRIP_ACTIVE] = "active", - [THERMAL_TRIP_PASSIVE] = "passive", - [THERMAL_TRIP_HOT] = "hot", - [THERMAL_TRIP_CRITICAL] = "critical", -}; - -/** - * thermal_of_get_trip_type - Get phy mode for given device_node - * @np: Pointer to the given device_node - * @type: Pointer to resulting trip type - * - * The function gets trip type string from property 'type', - * and store its index in trip_types table in @type, - * - * Return: 0 on success, or errno in error case. - */ -static int thermal_of_get_trip_type(struct device_node *np, - enum thermal_trip_type *type) -{ - const char *t; - int err, i; - - err = of_property_read_string(np, "type", &t); - if (err < 0) - return err; - - for (i = 0; i < ARRAY_SIZE(trip_types); i++) - if (!strcasecmp(t, trip_types[i])) { - *type = i; - return 0; - } - - return -ENODEV; -} - -/** - * thermal_of_populate_trip - parse and fill one trip point data - * @np: DT node containing a trip point node - * @trip: trip point data structure to be filled up - * - * This function parses a trip point type of node represented by - * @np parameter and fills the read data into @trip data structure. - * - * Return: 0 on success, proper error code otherwise - */ -static int thermal_of_populate_trip(struct device_node *np, - struct thermal_trip *trip) -{ - int prop; - int ret; - - ret = of_property_read_u32(np, "temperature", &prop); - if (ret < 0) { - pr_err("missing temperature property\n"); - return ret; - } - trip->temperature = prop; - - ret = of_property_read_u32(np, "hysteresis", &prop); - if (ret < 0) { - pr_err("missing hysteresis property\n"); - return ret; - } - trip->hysteresis = prop; - - ret = thermal_of_get_trip_type(np, &trip->type); - if (ret < 0) { - pr_err("wrong trip type property\n"); - return ret; - } - - /* Required for cooling map matching */ - trip->np = np; - of_node_get(np); - - return 0; -} - -/** - * thermal_of_build_thermal_zone - parse and fill one thermal zone data - * @np: DT node containing a thermal zone node - * - * This function parses a thermal zone type of node represented by - * @np parameter and fills the read data into a __thermal_zone data structure - * and return this pointer. - * - * TODO: Missing properties to parse: thermal-sensor-names - * - * Return: On success returns a valid struct __thermal_zone, - * otherwise, it returns a corresponding ERR_PTR(). Caller must - * check the return value with help of IS_ERR() helper. - */ -static struct __thermal_zone -__init *thermal_of_build_thermal_zone(struct device_node *np) -{ - struct device_node *child = NULL, *gchild; - struct __thermal_zone *tz; - int ret, i; - u32 prop, coef[2]; - - if (!np) { - pr_err("no thermal zone np\n"); - return ERR_PTR(-EINVAL); - } - - tz = kzalloc(sizeof(*tz), GFP_KERNEL); - if (!tz) - return ERR_PTR(-ENOMEM); - - ret = of_property_read_u32(np, "polling-delay-passive", &prop); - if (ret < 0) { - pr_err("%pOFn: missing polling-delay-passive property\n", np); - goto free_tz; - } - tz->passive_delay = prop; - - ret = of_property_read_u32(np, "polling-delay", &prop); - if (ret < 0) { - pr_err("%pOFn: missing polling-delay property\n", np); - goto free_tz; - } - tz->polling_delay = prop; - - /* - * REVIST: for now, the thermal framework supports only - * one sensor per thermal zone. Thus, we are considering - * only the first two values as slope and offset. - */ - ret = of_property_read_u32_array(np, "coefficients", coef, 2); - if (ret == 0) { - tz->slope = coef[0]; - tz->offset = coef[1]; - } else { - tz->slope = 1; - tz->offset = 0; - } - - /* trips */ - child = of_get_child_by_name(np, "trips"); - - /* No trips provided */ - if (!child) - goto finish; - - tz->ntrips = of_get_child_count(child); - if (tz->ntrips == 0) /* must have at least one child */ - goto finish; - - tz->trips = kcalloc(tz->ntrips, sizeof(*tz->trips), GFP_KERNEL); - if (!tz->trips) { - ret = -ENOMEM; - goto free_tz; - } - - i = 0; - for_each_child_of_node(child, gchild) { - ret = thermal_of_populate_trip(gchild, &tz->trips[i++]); - if (ret) - goto free_trips; - } - - of_node_put(child); - - /* cooling-maps */ - child = of_get_child_by_name(np, "cooling-maps"); - - /* cooling-maps not provided */ - if (!child) - goto finish; - - tz->num_tbps = of_get_child_count(child); - if (tz->num_tbps == 0) - goto finish; - - tz->tbps = kcalloc(tz->num_tbps, sizeof(*tz->tbps), GFP_KERNEL); - if (!tz->tbps) { - ret = -ENOMEM; - goto free_trips; - } - - i = 0; - for_each_child_of_node(child, gchild) { - ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++], - tz->trips, tz->ntrips); - if (ret) - goto free_tbps; - } - -finish: - of_node_put(child); - tz->mode = THERMAL_DEVICE_DISABLED; - - return tz; - -free_tbps: - for (i = i - 1; i >= 0; i--) { - struct __thermal_bind_params *tbp = tz->tbps + i; - int j; - - for (j = 0; j < tbp->count; j++) - of_node_put(tbp->tcbp[j].cooling_device); - - kfree(tbp->tcbp); - } - - kfree(tz->tbps); -free_trips: - for (i = 0; i < tz->ntrips; i++) - of_node_put(tz->trips[i].np); - kfree(tz->trips); - of_node_put(gchild); -free_tz: - kfree(tz); - of_node_put(child); - - return ERR_PTR(ret); -} - -static __init void of_thermal_free_zone(struct __thermal_zone *tz) -{ - struct __thermal_bind_params *tbp; - int i, j; - - for (i = 0; i < tz->num_tbps; i++) { - tbp = tz->tbps + i; - - for (j = 0; j < tbp->count; j++) - of_node_put(tbp->tcbp[j].cooling_device); - - kfree(tbp->tcbp); - } - - kfree(tz->tbps); - for (i = 0; i < tz->ntrips; i++) - of_node_put(tz->trips[i].np); - kfree(tz->trips); - kfree(tz); -} - -/** - * of_thermal_destroy_zones - remove all zones parsed and allocated resources - * - * Finds all zones parsed and added to the thermal framework and remove them - * from the system, together with their resources. - * - */ -static __init void of_thermal_destroy_zones(void) -{ - struct device_node *np, *child; - - np = of_find_node_by_name(NULL, "thermal-zones"); - if (!np) { - pr_debug("unable to find thermal zones\n"); - return; - } - - for_each_available_child_of_node(np, child) { - struct thermal_zone_device *zone; - - zone = thermal_zone_get_zone_by_name(child->name); - if (IS_ERR(zone)) - continue; - - thermal_zone_device_unregister(zone); - kfree(zone->tzp); - kfree(zone->ops); - of_thermal_free_zone(zone->devdata); - } - of_node_put(np); -} - -/** - * of_parse_thermal_zones - parse device tree thermal data - * - * Initialization function that can be called by machine initialization - * code to parse thermal data and populate the thermal framework - * with hardware thermal zones info. This function only parses thermal zones. - * Cooling devices and sensor devices nodes are supposed to be parsed - * by their respective drivers. - * - * Return: 0 on success, proper error code otherwise - * - */ -int __init of_parse_thermal_zones(void) -{ - struct device_node *np, *child; - struct __thermal_zone *tz; - struct thermal_zone_device_ops *ops; - - np = of_find_node_by_name(NULL, "thermal-zones"); - if (!np) { - pr_debug("unable to find thermal zones\n"); - return 0; /* Run successfully on systems without thermal DT */ - } - - for_each_available_child_of_node(np, child) { - struct thermal_zone_device *zone; - struct thermal_zone_params *tzp; - int i, mask = 0; - u32 prop; - - tz = thermal_of_build_thermal_zone(child); - if (IS_ERR(tz)) { - pr_err("failed to build thermal zone %pOFn: %ld\n", - child, - PTR_ERR(tz)); - continue; - } - - ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL); - if (!ops) - goto exit_free; - - tzp = kzalloc(sizeof(*tzp), GFP_KERNEL); - if (!tzp) { - kfree(ops); - goto exit_free; - } - - /* No hwmon because there might be hwmon drivers registering */ - tzp->no_hwmon = true; - - if (!of_property_read_u32(child, "sustainable-power", &prop)) - tzp->sustainable_power = prop; - - for (i = 0; i < tz->ntrips; i++) - mask |= 1 << i; - - /* these two are left for temperature drivers to use */ - tzp->slope = tz->slope; - tzp->offset = tz->offset; - - zone = thermal_zone_device_register(child->name, tz->ntrips, - mask, tz, - ops, tzp, - tz->passive_delay, - tz->polling_delay); - if (IS_ERR(zone)) { - pr_err("Failed to build %pOFn zone %ld\n", child, - PTR_ERR(zone)); - kfree(tzp); - kfree(ops); - of_thermal_free_zone(tz); - /* attempting to build remaining zones still */ - } - } - of_node_put(np); - - return 0; - -exit_free: - of_node_put(child); - of_node_put(np); - of_thermal_free_zone(tz); - - /* no memory available, so free what we have built */ - of_thermal_destroy_zones(); - - return -ENOMEM; -} diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c new file mode 100644 index 000000000000..ddf88dbe7ba2 --- /dev/null +++ b/drivers/thermal/thermal_of.c @@ -0,0 +1,1151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * of-thermal.c - Generic Thermal Management device tree support. + * + * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Eduardo Valentin + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thermal_core.h" + +/*** Private data structures to represent thermal device tree data ***/ + +/** + * struct __thermal_cooling_bind_param - a cooling device for a trip point + * @cooling_device: a pointer to identify the referred cooling device + * @min: minimum cooling state used at this trip point + * @max: maximum cooling state used at this trip point + */ + +struct __thermal_cooling_bind_param { + struct device_node *cooling_device; + unsigned long min; + unsigned long max; +}; + +/** + * struct __thermal_bind_param - a match between trip and cooling device + * @tcbp: a pointer to an array of cooling devices + * @count: number of elements in array + * @trip_id: the trip point index + * @usage: the percentage (from 0 to 100) of cooling contribution + */ + +struct __thermal_bind_params { + struct __thermal_cooling_bind_param *tcbp; + unsigned int count; + unsigned int trip_id; + unsigned int usage; +}; + +/** + * struct __thermal_zone - internal representation of a thermal zone + * @mode: current thermal zone device mode (enabled/disabled) + * @passive_delay: polling interval while passive cooling is activated + * @polling_delay: zone polling interval + * @slope: slope of the temperature adjustment curve + * @offset: offset of the temperature adjustment curve + * @ntrips: number of trip points + * @trips: an array of trip points (0..ntrips - 1) + * @num_tbps: number of thermal bind params + * @tbps: an array of thermal bind params (0..num_tbps - 1) + * @sensor_data: sensor private data used while reading temperature and trend + * @ops: set of callbacks to handle the thermal zone based on DT + */ + +struct __thermal_zone { + enum thermal_device_mode mode; + int passive_delay; + int polling_delay; + int slope; + int offset; + + /* trip data */ + int ntrips; + struct thermal_trip *trips; + + /* cooling binding data */ + int num_tbps; + struct __thermal_bind_params *tbps; + + /* sensor interface */ + void *sensor_data; + const struct thermal_zone_of_device_ops *ops; +}; + +/*** DT thermal zone device callbacks ***/ + +static int of_thermal_get_temp(struct thermal_zone_device *tz, + int *temp) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data->ops->get_temp) + return -EINVAL; + + return data->ops->get_temp(data->sensor_data, temp); +} + +static int of_thermal_set_trips(struct thermal_zone_device *tz, + int low, int high) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data->ops || !data->ops->set_trips) + return -EINVAL; + + return data->ops->set_trips(data->sensor_data, low, high); +} + +/** + * of_thermal_get_ntrips - function to export number of available trip + * points. + * @tz: pointer to a thermal zone + * + * This function is a globally visible wrapper to get number of trip points + * stored in the local struct __thermal_zone + * + * Return: number of available trip points, -ENODEV when data not available + */ +int of_thermal_get_ntrips(struct thermal_zone_device *tz) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data || IS_ERR(data)) + return -ENODEV; + + return data->ntrips; +} +EXPORT_SYMBOL_GPL(of_thermal_get_ntrips); + +/** + * of_thermal_is_trip_valid - function to check if trip point is valid + * + * @tz: pointer to a thermal zone + * @trip: trip point to evaluate + * + * This function is responsible for checking if passed trip point is valid + * + * Return: true if trip point is valid, false otherwise + */ +bool of_thermal_is_trip_valid(struct thermal_zone_device *tz, int trip) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data || trip >= data->ntrips || trip < 0) + return false; + + return true; +} +EXPORT_SYMBOL_GPL(of_thermal_is_trip_valid); + +/** + * of_thermal_get_trip_points - function to get access to a globally exported + * trip points + * + * @tz: pointer to a thermal zone + * + * This function provides a pointer to trip points table + * + * Return: pointer to trip points table, NULL otherwise + */ +const struct thermal_trip * +of_thermal_get_trip_points(struct thermal_zone_device *tz) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data) + return NULL; + + return data->trips; +} +EXPORT_SYMBOL_GPL(of_thermal_get_trip_points); + +/** + * of_thermal_set_emul_temp - function to set emulated temperature + * + * @tz: pointer to a thermal zone + * @temp: temperature to set + * + * This function gives the ability to set emulated value of temperature, + * which is handy for debugging + * + * Return: zero on success, error code otherwise + */ +static int of_thermal_set_emul_temp(struct thermal_zone_device *tz, + int temp) +{ + struct __thermal_zone *data = tz->devdata; + + return data->ops->set_emul_temp(data->sensor_data, temp); +} + +static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip, + enum thermal_trend *trend) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data->ops->get_trend) + return -EINVAL; + + return data->ops->get_trend(data->sensor_data, trip, trend); +} + +static int of_thermal_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct __thermal_zone *data = thermal->devdata; + struct __thermal_bind_params *tbp; + struct __thermal_cooling_bind_param *tcbp; + int i, j; + + if (!data || IS_ERR(data)) + return -ENODEV; + + /* find where to bind */ + for (i = 0; i < data->num_tbps; i++) { + tbp = data->tbps + i; + + for (j = 0; j < tbp->count; j++) { + tcbp = tbp->tcbp + j; + + if (tcbp->cooling_device == cdev->np) { + int ret; + + ret = thermal_zone_bind_cooling_device(thermal, + tbp->trip_id, cdev, + tcbp->max, + tcbp->min, + tbp->usage); + if (ret) + return ret; + } + } + } + + return 0; +} + +static int of_thermal_unbind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct __thermal_zone *data = thermal->devdata; + struct __thermal_bind_params *tbp; + struct __thermal_cooling_bind_param *tcbp; + int i, j; + + if (!data || IS_ERR(data)) + return -ENODEV; + + /* find where to unbind */ + for (i = 0; i < data->num_tbps; i++) { + tbp = data->tbps + i; + + for (j = 0; j < tbp->count; j++) { + tcbp = tbp->tcbp + j; + + if (tcbp->cooling_device == cdev->np) { + int ret; + + ret = thermal_zone_unbind_cooling_device(thermal, + tbp->trip_id, cdev); + if (ret) + return ret; + } + } + } + + return 0; +} + +static int of_thermal_get_mode(struct thermal_zone_device *tz, + enum thermal_device_mode *mode) +{ + struct __thermal_zone *data = tz->devdata; + + *mode = data->mode; + + return 0; +} + +static int of_thermal_set_mode(struct thermal_zone_device *tz, + enum thermal_device_mode mode) +{ + struct __thermal_zone *data = tz->devdata; + + mutex_lock(&tz->lock); + + if (mode == THERMAL_DEVICE_ENABLED) { + tz->polling_delay = data->polling_delay; + tz->passive_delay = data->passive_delay; + } else { + tz->polling_delay = 0; + tz->passive_delay = 0; + } + + mutex_unlock(&tz->lock); + + data->mode = mode; + thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); + + return 0; +} + +static int of_thermal_get_trip_type(struct thermal_zone_device *tz, int trip, + enum thermal_trip_type *type) +{ + struct __thermal_zone *data = tz->devdata; + + if (trip >= data->ntrips || trip < 0) + return -EDOM; + + *type = data->trips[trip].type; + + return 0; +} + +static int of_thermal_get_trip_temp(struct thermal_zone_device *tz, int trip, + int *temp) +{ + struct __thermal_zone *data = tz->devdata; + + if (trip >= data->ntrips || trip < 0) + return -EDOM; + + *temp = data->trips[trip].temperature; + + return 0; +} + +static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip, + int temp) +{ + struct __thermal_zone *data = tz->devdata; + + if (trip >= data->ntrips || trip < 0) + return -EDOM; + + if (data->ops->set_trip_temp) { + int ret; + + ret = data->ops->set_trip_temp(data->sensor_data, trip, temp); + if (ret) + return ret; + } + + /* thermal framework should take care of data->mask & (1 << trip) */ + data->trips[trip].temperature = temp; + + return 0; +} + +static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip, + int *hyst) +{ + struct __thermal_zone *data = tz->devdata; + + if (trip >= data->ntrips || trip < 0) + return -EDOM; + + *hyst = data->trips[trip].hysteresis; + + return 0; +} + +static int of_thermal_set_trip_hyst(struct thermal_zone_device *tz, int trip, + int hyst) +{ + struct __thermal_zone *data = tz->devdata; + + if (trip >= data->ntrips || trip < 0) + return -EDOM; + + /* thermal framework should take care of data->mask & (1 << trip) */ + data->trips[trip].hysteresis = hyst; + + return 0; +} + +static int of_thermal_get_crit_temp(struct thermal_zone_device *tz, + int *temp) +{ + struct __thermal_zone *data = tz->devdata; + int i; + + for (i = 0; i < data->ntrips; i++) + if (data->trips[i].type == THERMAL_TRIP_CRITICAL) { + *temp = data->trips[i].temperature; + return 0; + } + + return -EINVAL; +} + +static struct thermal_zone_device_ops of_thermal_ops = { + .get_mode = of_thermal_get_mode, + .set_mode = of_thermal_set_mode, + + .get_trip_type = of_thermal_get_trip_type, + .get_trip_temp = of_thermal_get_trip_temp, + .set_trip_temp = of_thermal_set_trip_temp, + .get_trip_hyst = of_thermal_get_trip_hyst, + .set_trip_hyst = of_thermal_set_trip_hyst, + .get_crit_temp = of_thermal_get_crit_temp, + + .bind = of_thermal_bind, + .unbind = of_thermal_unbind, +}; + +/*** sensor API ***/ + +static struct thermal_zone_device * +thermal_zone_of_add_sensor(struct device_node *zone, + struct device_node *sensor, void *data, + const struct thermal_zone_of_device_ops *ops) +{ + struct thermal_zone_device *tzd; + struct __thermal_zone *tz; + + tzd = thermal_zone_get_zone_by_name(zone->name); + if (IS_ERR(tzd)) + return ERR_PTR(-EPROBE_DEFER); + + tz = tzd->devdata; + + if (!ops) + return ERR_PTR(-EINVAL); + + mutex_lock(&tzd->lock); + tz->ops = ops; + tz->sensor_data = data; + + tzd->ops->get_temp = of_thermal_get_temp; + tzd->ops->get_trend = of_thermal_get_trend; + + /* + * The thermal zone core will calculate the window if they have set the + * optional set_trips pointer. + */ + if (ops->set_trips) + tzd->ops->set_trips = of_thermal_set_trips; + + if (ops->set_emul_temp) + tzd->ops->set_emul_temp = of_thermal_set_emul_temp; + + mutex_unlock(&tzd->lock); + + return tzd; +} + +/** + * thermal_zone_of_get_sensor_id - get sensor ID from a DT thermal zone + * @tz_np: a valid thermal zone device node. + * @sensor_np: a sensor node of a valid sensor device. + * @id: the sensor ID returned if success. + * + * This function will get sensor ID from a given thermal zone node and + * the sensor node must match the temperature provider @sensor_np. + * + * Return: 0 on success, proper error code otherwise. + */ + +int thermal_zone_of_get_sensor_id(struct device_node *tz_np, + struct device_node *sensor_np, + u32 *id) +{ + struct of_phandle_args sensor_specs; + int ret; + + ret = of_parse_phandle_with_args(tz_np, + "thermal-sensors", + "#thermal-sensor-cells", + 0, + &sensor_specs); + if (ret) + return ret; + + if (sensor_specs.np != sensor_np) { + of_node_put(sensor_specs.np); + return -ENODEV; + } + + if (sensor_specs.args_count > 1) + pr_warn("%pOFn: too many cells in sensor specifier %d\n", + sensor_specs.np, sensor_specs.args_count); + + *id = sensor_specs.args_count ? sensor_specs.args[0] : 0; + + of_node_put(sensor_specs.np); + + return 0; +} +EXPORT_SYMBOL_GPL(thermal_zone_of_get_sensor_id); + +/** + * thermal_zone_of_sensor_register - registers a sensor to a DT thermal zone + * @dev: a valid struct device pointer of a sensor device. Must contain + * a valid .of_node, for the sensor node. + * @sensor_id: a sensor identifier, in case the sensor IP has more + * than one sensors + * @data: a private pointer (owned by the caller) that will be passed + * back, when a temperature reading is needed. + * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp. + * + * This function will search the list of thermal zones described in device + * tree and look for the zone that refer to the sensor device pointed by + * @dev->of_node as temperature providers. For the zone pointing to the + * sensor node, the sensor will be added to the DT thermal zone device. + * + * The thermal zone temperature is provided by the @get_temp function + * pointer. When called, it will have the private pointer @data back. + * + * The thermal zone temperature trend is provided by the @get_trend function + * pointer. When called, it will have the private pointer @data back. + * + * TODO: + * 01 - This function must enqueue the new sensor instead of using + * it as the only source of temperature values. + * + * 02 - There must be a way to match the sensor with all thermal zones + * that refer to it. + * + * Return: On success returns a valid struct thermal_zone_device, + * otherwise, it returns a corresponding ERR_PTR(). Caller must + * check the return value with help of IS_ERR() helper. + */ +struct thermal_zone_device * +thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data, + const struct thermal_zone_of_device_ops *ops) +{ + struct device_node *np, *child, *sensor_np; + struct thermal_zone_device *tzd = ERR_PTR(-ENODEV); + + np = of_find_node_by_name(NULL, "thermal-zones"); + if (!np) + return ERR_PTR(-ENODEV); + + if (!dev || !dev->of_node) { + of_node_put(np); + return ERR_PTR(-ENODEV); + } + + sensor_np = of_node_get(dev->of_node); + + for_each_available_child_of_node(np, child) { + int ret, id; + + /* For now, thermal framework supports only 1 sensor per zone */ + ret = thermal_zone_of_get_sensor_id(child, sensor_np, &id); + if (ret) + continue; + + if (id == sensor_id) { + tzd = thermal_zone_of_add_sensor(child, sensor_np, + data, ops); + if (!IS_ERR(tzd)) + tzd->ops->set_mode(tzd, THERMAL_DEVICE_ENABLED); + + of_node_put(child); + goto exit; + } + } +exit: + of_node_put(sensor_np); + of_node_put(np); + + return tzd; +} +EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_register); + +/** + * thermal_zone_of_sensor_unregister - unregisters a sensor from a DT thermal zone + * @dev: a valid struct device pointer of a sensor device. Must contain + * a valid .of_node, for the sensor node. + * @tzd: a pointer to struct thermal_zone_device where the sensor is registered. + * + * This function removes the sensor callbacks and private data from the + * thermal zone device registered with thermal_zone_of_sensor_register() + * API. It will also silent the zone by remove the .get_temp() and .get_trend() + * thermal zone device callbacks. + * + * TODO: When the support to several sensors per zone is added, this + * function must search the sensor list based on @dev parameter. + * + */ +void thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tzd) +{ + struct __thermal_zone *tz; + + if (!dev || !tzd || !tzd->devdata) + return; + + tz = tzd->devdata; + + /* no __thermal_zone, nothing to be done */ + if (!tz) + return; + + mutex_lock(&tzd->lock); + tzd->ops->get_temp = NULL; + tzd->ops->get_trend = NULL; + tzd->ops->set_emul_temp = NULL; + + tz->ops = NULL; + tz->sensor_data = NULL; + mutex_unlock(&tzd->lock); +} +EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister); + +static void devm_thermal_zone_of_sensor_release(struct device *dev, void *res) +{ + thermal_zone_of_sensor_unregister(dev, + *(struct thermal_zone_device **)res); +} + +static int devm_thermal_zone_of_sensor_match(struct device *dev, void *res, + void *data) +{ + struct thermal_zone_device **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +/** + * devm_thermal_zone_of_sensor_register - Resource managed version of + * thermal_zone_of_sensor_register() + * @dev: a valid struct device pointer of a sensor device. Must contain + * a valid .of_node, for the sensor node. + * @sensor_id: a sensor identifier, in case the sensor IP has more + * than one sensors + * @data: a private pointer (owned by the caller) that will be passed + * back, when a temperature reading is needed. + * @ops: struct thermal_zone_of_device_ops *. Must contain at least .get_temp. + * + * Refer thermal_zone_of_sensor_register() for more details. + * + * Return: On success returns a valid struct thermal_zone_device, + * otherwise, it returns a corresponding ERR_PTR(). Caller must + * check the return value with help of IS_ERR() helper. + * Registered thermal_zone_device device will automatically be + * released when device is unbounded. + */ +struct thermal_zone_device *devm_thermal_zone_of_sensor_register( + struct device *dev, int sensor_id, + void *data, const struct thermal_zone_of_device_ops *ops) +{ + struct thermal_zone_device **ptr, *tzd; + + ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + tzd = thermal_zone_of_sensor_register(dev, sensor_id, data, ops); + if (IS_ERR(tzd)) { + devres_free(ptr); + return tzd; + } + + *ptr = tzd; + devres_add(dev, ptr); + + return tzd; +} +EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_register); + +/** + * devm_thermal_zone_of_sensor_unregister - Resource managed version of + * thermal_zone_of_sensor_unregister(). + * @dev: Device for which which resource was allocated. + * @tzd: a pointer to struct thermal_zone_device where the sensor is registered. + * + * This function removes the sensor callbacks and private data from the + * thermal zone device registered with devm_thermal_zone_of_sensor_register() + * API. It will also silent the zone by remove the .get_temp() and .get_trend() + * thermal zone device callbacks. + * Normally this function will not need to be called and the resource + * management code will ensure that the resource is freed. + */ +void devm_thermal_zone_of_sensor_unregister(struct device *dev, + struct thermal_zone_device *tzd) +{ + WARN_ON(devres_release(dev, devm_thermal_zone_of_sensor_release, + devm_thermal_zone_of_sensor_match, tzd)); +} +EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_unregister); + +/*** functions parsing device tree nodes ***/ + +/** + * thermal_of_populate_bind_params - parse and fill cooling map data + * @np: DT node containing a cooling-map node + * @__tbp: data structure to be filled with cooling map info + * @trips: array of thermal zone trip points + * @ntrips: number of trip points inside trips. + * + * This function parses a cooling-map type of node represented by + * @np parameter and fills the read data into @__tbp data structure. + * It needs the already parsed array of trip points of the thermal zone + * in consideration. + * + * Return: 0 on success, proper error code otherwise + */ +static int thermal_of_populate_bind_params(struct device_node *np, + struct __thermal_bind_params *__tbp, + struct thermal_trip *trips, + int ntrips) +{ + struct of_phandle_args cooling_spec; + struct __thermal_cooling_bind_param *__tcbp; + struct device_node *trip; + int ret, i, count; + u32 prop; + + /* Default weight. Usage is optional */ + __tbp->usage = THERMAL_WEIGHT_DEFAULT; + ret = of_property_read_u32(np, "contribution", &prop); + if (ret == 0) + __tbp->usage = prop; + + trip = of_parse_phandle(np, "trip", 0); + if (!trip) { + pr_err("missing trip property\n"); + return -ENODEV; + } + + /* match using device_node */ + for (i = 0; i < ntrips; i++) + if (trip == trips[i].np) { + __tbp->trip_id = i; + break; + } + + if (i == ntrips) { + ret = -ENODEV; + goto end; + } + + count = of_count_phandle_with_args(np, "cooling-device", + "#cooling-cells"); + if (!count) { + pr_err("Add a cooling_device property with at least one device\n"); + goto end; + } + + __tcbp = kcalloc(count, sizeof(*__tcbp), GFP_KERNEL); + if (!__tcbp) + goto end; + + for (i = 0; i < count; i++) { + ret = of_parse_phandle_with_args(np, "cooling-device", + "#cooling-cells", i, &cooling_spec); + if (ret < 0) { + pr_err("Invalid cooling-device entry\n"); + goto free_tcbp; + } + + __tcbp[i].cooling_device = cooling_spec.np; + + if (cooling_spec.args_count >= 2) { /* at least min and max */ + __tcbp[i].min = cooling_spec.args[0]; + __tcbp[i].max = cooling_spec.args[1]; + } else { + pr_err("wrong reference to cooling device, missing limits\n"); + } + } + + __tbp->tcbp = __tcbp; + __tbp->count = count; + + goto end; + +free_tcbp: + for (i = i - 1; i >= 0; i--) + of_node_put(__tcbp[i].cooling_device); + kfree(__tcbp); +end: + of_node_put(trip); + + return ret; +} + +/* + * It maps 'enum thermal_trip_type' found in include/linux/thermal.h + * into the device tree binding of 'trip', property type. + */ +static const char * const trip_types[] = { + [THERMAL_TRIP_ACTIVE] = "active", + [THERMAL_TRIP_PASSIVE] = "passive", + [THERMAL_TRIP_HOT] = "hot", + [THERMAL_TRIP_CRITICAL] = "critical", +}; + +/** + * thermal_of_get_trip_type - Get phy mode for given device_node + * @np: Pointer to the given device_node + * @type: Pointer to resulting trip type + * + * The function gets trip type string from property 'type', + * and store its index in trip_types table in @type, + * + * Return: 0 on success, or errno in error case. + */ +static int thermal_of_get_trip_type(struct device_node *np, + enum thermal_trip_type *type) +{ + const char *t; + int err, i; + + err = of_property_read_string(np, "type", &t); + if (err < 0) + return err; + + for (i = 0; i < ARRAY_SIZE(trip_types); i++) + if (!strcasecmp(t, trip_types[i])) { + *type = i; + return 0; + } + + return -ENODEV; +} + +/** + * thermal_of_populate_trip - parse and fill one trip point data + * @np: DT node containing a trip point node + * @trip: trip point data structure to be filled up + * + * This function parses a trip point type of node represented by + * @np parameter and fills the read data into @trip data structure. + * + * Return: 0 on success, proper error code otherwise + */ +static int thermal_of_populate_trip(struct device_node *np, + struct thermal_trip *trip) +{ + int prop; + int ret; + + ret = of_property_read_u32(np, "temperature", &prop); + if (ret < 0) { + pr_err("missing temperature property\n"); + return ret; + } + trip->temperature = prop; + + ret = of_property_read_u32(np, "hysteresis", &prop); + if (ret < 0) { + pr_err("missing hysteresis property\n"); + return ret; + } + trip->hysteresis = prop; + + ret = thermal_of_get_trip_type(np, &trip->type); + if (ret < 0) { + pr_err("wrong trip type property\n"); + return ret; + } + + /* Required for cooling map matching */ + trip->np = np; + of_node_get(np); + + return 0; +} + +/** + * thermal_of_build_thermal_zone - parse and fill one thermal zone data + * @np: DT node containing a thermal zone node + * + * This function parses a thermal zone type of node represented by + * @np parameter and fills the read data into a __thermal_zone data structure + * and return this pointer. + * + * TODO: Missing properties to parse: thermal-sensor-names + * + * Return: On success returns a valid struct __thermal_zone, + * otherwise, it returns a corresponding ERR_PTR(). Caller must + * check the return value with help of IS_ERR() helper. + */ +static struct __thermal_zone +__init *thermal_of_build_thermal_zone(struct device_node *np) +{ + struct device_node *child = NULL, *gchild; + struct __thermal_zone *tz; + int ret, i; + u32 prop, coef[2]; + + if (!np) { + pr_err("no thermal zone np\n"); + return ERR_PTR(-EINVAL); + } + + tz = kzalloc(sizeof(*tz), GFP_KERNEL); + if (!tz) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32(np, "polling-delay-passive", &prop); + if (ret < 0) { + pr_err("%pOFn: missing polling-delay-passive property\n", np); + goto free_tz; + } + tz->passive_delay = prop; + + ret = of_property_read_u32(np, "polling-delay", &prop); + if (ret < 0) { + pr_err("%pOFn: missing polling-delay property\n", np); + goto free_tz; + } + tz->polling_delay = prop; + + /* + * REVIST: for now, the thermal framework supports only + * one sensor per thermal zone. Thus, we are considering + * only the first two values as slope and offset. + */ + ret = of_property_read_u32_array(np, "coefficients", coef, 2); + if (ret == 0) { + tz->slope = coef[0]; + tz->offset = coef[1]; + } else { + tz->slope = 1; + tz->offset = 0; + } + + /* trips */ + child = of_get_child_by_name(np, "trips"); + + /* No trips provided */ + if (!child) + goto finish; + + tz->ntrips = of_get_child_count(child); + if (tz->ntrips == 0) /* must have at least one child */ + goto finish; + + tz->trips = kcalloc(tz->ntrips, sizeof(*tz->trips), GFP_KERNEL); + if (!tz->trips) { + ret = -ENOMEM; + goto free_tz; + } + + i = 0; + for_each_child_of_node(child, gchild) { + ret = thermal_of_populate_trip(gchild, &tz->trips[i++]); + if (ret) + goto free_trips; + } + + of_node_put(child); + + /* cooling-maps */ + child = of_get_child_by_name(np, "cooling-maps"); + + /* cooling-maps not provided */ + if (!child) + goto finish; + + tz->num_tbps = of_get_child_count(child); + if (tz->num_tbps == 0) + goto finish; + + tz->tbps = kcalloc(tz->num_tbps, sizeof(*tz->tbps), GFP_KERNEL); + if (!tz->tbps) { + ret = -ENOMEM; + goto free_trips; + } + + i = 0; + for_each_child_of_node(child, gchild) { + ret = thermal_of_populate_bind_params(gchild, &tz->tbps[i++], + tz->trips, tz->ntrips); + if (ret) + goto free_tbps; + } + +finish: + of_node_put(child); + tz->mode = THERMAL_DEVICE_DISABLED; + + return tz; + +free_tbps: + for (i = i - 1; i >= 0; i--) { + struct __thermal_bind_params *tbp = tz->tbps + i; + int j; + + for (j = 0; j < tbp->count; j++) + of_node_put(tbp->tcbp[j].cooling_device); + + kfree(tbp->tcbp); + } + + kfree(tz->tbps); +free_trips: + for (i = 0; i < tz->ntrips; i++) + of_node_put(tz->trips[i].np); + kfree(tz->trips); + of_node_put(gchild); +free_tz: + kfree(tz); + of_node_put(child); + + return ERR_PTR(ret); +} + +static __init void of_thermal_free_zone(struct __thermal_zone *tz) +{ + struct __thermal_bind_params *tbp; + int i, j; + + for (i = 0; i < tz->num_tbps; i++) { + tbp = tz->tbps + i; + + for (j = 0; j < tbp->count; j++) + of_node_put(tbp->tcbp[j].cooling_device); + + kfree(tbp->tcbp); + } + + kfree(tz->tbps); + for (i = 0; i < tz->ntrips; i++) + of_node_put(tz->trips[i].np); + kfree(tz->trips); + kfree(tz); +} + +/** + * of_thermal_destroy_zones - remove all zones parsed and allocated resources + * + * Finds all zones parsed and added to the thermal framework and remove them + * from the system, together with their resources. + * + */ +static __init void of_thermal_destroy_zones(void) +{ + struct device_node *np, *child; + + np = of_find_node_by_name(NULL, "thermal-zones"); + if (!np) { + pr_debug("unable to find thermal zones\n"); + return; + } + + for_each_available_child_of_node(np, child) { + struct thermal_zone_device *zone; + + zone = thermal_zone_get_zone_by_name(child->name); + if (IS_ERR(zone)) + continue; + + thermal_zone_device_unregister(zone); + kfree(zone->tzp); + kfree(zone->ops); + of_thermal_free_zone(zone->devdata); + } + of_node_put(np); +} + +/** + * of_parse_thermal_zones - parse device tree thermal data + * + * Initialization function that can be called by machine initialization + * code to parse thermal data and populate the thermal framework + * with hardware thermal zones info. This function only parses thermal zones. + * Cooling devices and sensor devices nodes are supposed to be parsed + * by their respective drivers. + * + * Return: 0 on success, proper error code otherwise + * + */ +int __init of_parse_thermal_zones(void) +{ + struct device_node *np, *child; + struct __thermal_zone *tz; + struct thermal_zone_device_ops *ops; + + np = of_find_node_by_name(NULL, "thermal-zones"); + if (!np) { + pr_debug("unable to find thermal zones\n"); + return 0; /* Run successfully on systems without thermal DT */ + } + + for_each_available_child_of_node(np, child) { + struct thermal_zone_device *zone; + struct thermal_zone_params *tzp; + int i, mask = 0; + u32 prop; + + tz = thermal_of_build_thermal_zone(child); + if (IS_ERR(tz)) { + pr_err("failed to build thermal zone %pOFn: %ld\n", + child, + PTR_ERR(tz)); + continue; + } + + ops = kmemdup(&of_thermal_ops, sizeof(*ops), GFP_KERNEL); + if (!ops) + goto exit_free; + + tzp = kzalloc(sizeof(*tzp), GFP_KERNEL); + if (!tzp) { + kfree(ops); + goto exit_free; + } + + /* No hwmon because there might be hwmon drivers registering */ + tzp->no_hwmon = true; + + if (!of_property_read_u32(child, "sustainable-power", &prop)) + tzp->sustainable_power = prop; + + for (i = 0; i < tz->ntrips; i++) + mask |= 1 << i; + + /* these two are left for temperature drivers to use */ + tzp->slope = tz->slope; + tzp->offset = tz->offset; + + zone = thermal_zone_device_register(child->name, tz->ntrips, + mask, tz, + ops, tzp, + tz->passive_delay, + tz->polling_delay); + if (IS_ERR(zone)) { + pr_err("Failed to build %pOFn zone %ld\n", child, + PTR_ERR(zone)); + kfree(tzp); + kfree(ops); + of_thermal_free_zone(tz); + /* attempting to build remaining zones still */ + } + } + of_node_put(np); + + return 0; + +exit_free: + of_node_put(child); + of_node_put(np); + of_thermal_free_zone(tz); + + /* no memory available, so free what we have built */ + of_thermal_destroy_zones(); + + return -ENOMEM; +} -- cgit v1.2.3 From a7ff82976122eb6d1fd286dc34f09b6ecd756b60 Mon Sep 17 00:00:00 2001 From: Amit Kucheria Date: Wed, 29 Apr 2020 23:44:17 +0530 Subject: drivers: thermal: tsens: Merge tsens-common.c into tsens.c tsens-common.c has outlived its usefuless. It was created expecting lots of custom routines per version of the TSENS IP. We haven't needed those, there is now only data in the version-specific files. Merge the code for tsens-common.c into tsens.c. As a result, - Remove any unnecessary forward declarations in tsens.h. - Add a Linaro copyright to tsens.c. - Fixup the Makefile to remove tsens-common.c. - Where it made sense, fix some 80-column alignments in the tsens-common.c code being copied over. There is no functional change with this patch. Signed-off-by: Amit Kucheria Reviewed-by: Bjorn Andersson Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/e30e2ba6fa5c007983afd4d7d4e0311c0b57917a.1588183879.git.amit.kucheria@linaro.org --- drivers/thermal/qcom/Makefile | 4 +- drivers/thermal/qcom/tsens-common.c | 843 ------------------------------------ drivers/thermal/qcom/tsens.c | 838 +++++++++++++++++++++++++++++++++++ drivers/thermal/qcom/tsens.h | 5 - 4 files changed, 840 insertions(+), 850 deletions(-) delete mode 100644 drivers/thermal/qcom/tsens-common.c diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 7c8dc6e36693..ec86eef7f6a6 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o -qcom_tsens-y += tsens.o tsens-common.o tsens-v0_1.o \ - tsens-8960.o tsens-v2.o tsens-v1.o +qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \ + tsens-8960.o obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o diff --git a/drivers/thermal/qcom/tsens-common.c b/drivers/thermal/qcom/tsens-common.c deleted file mode 100644 index 172545366636..000000000000 --- a/drivers/thermal/qcom/tsens-common.c +++ /dev/null @@ -1,843 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2015, The Linux Foundation. All rights reserved. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "tsens.h" - -/** - * struct tsens_irq_data - IRQ status and temperature violations - * @up_viol: upper threshold violated - * @up_thresh: upper threshold temperature value - * @up_irq_mask: mask register for upper threshold irqs - * @up_irq_clear: clear register for uppper threshold irqs - * @low_viol: lower threshold violated - * @low_thresh: lower threshold temperature value - * @low_irq_mask: mask register for lower threshold irqs - * @low_irq_clear: clear register for lower threshold irqs - * @crit_viol: critical threshold violated - * @crit_thresh: critical threshold temperature value - * @crit_irq_mask: mask register for critical threshold irqs - * @crit_irq_clear: clear register for critical threshold irqs - * - * Structure containing data about temperature threshold settings and - * irq status if they were violated. - */ -struct tsens_irq_data { - u32 up_viol; - int up_thresh; - u32 up_irq_mask; - u32 up_irq_clear; - u32 low_viol; - int low_thresh; - u32 low_irq_mask; - u32 low_irq_clear; - u32 crit_viol; - u32 crit_thresh; - u32 crit_irq_mask; - u32 crit_irq_clear; -}; - -char *qfprom_read(struct device *dev, const char *cname) -{ - struct nvmem_cell *cell; - ssize_t data; - char *ret; - - cell = nvmem_cell_get(dev, cname); - if (IS_ERR(cell)) - return ERR_CAST(cell); - - ret = nvmem_cell_read(cell, &data); - nvmem_cell_put(cell); - - return ret; -} - -/* - * Use this function on devices where slope and offset calculations - * depend on calibration data read from qfprom. On others the slope - * and offset values are derived from tz->tzp->slope and tz->tzp->offset - * resp. - */ -void compute_intercept_slope(struct tsens_priv *priv, u32 *p1, - u32 *p2, u32 mode) -{ - int i; - int num, den; - - for (i = 0; i < priv->num_sensors; i++) { - dev_dbg(priv->dev, - "%s: sensor%d - data_point1:%#x data_point2:%#x\n", - __func__, i, p1[i], p2[i]); - - priv->sensor[i].slope = SLOPE_DEFAULT; - if (mode == TWO_PT_CALIB) { - /* - * slope (m) = adc_code2 - adc_code1 (y2 - y1)/ - * temp_120_degc - temp_30_degc (x2 - x1) - */ - num = p2[i] - p1[i]; - num *= SLOPE_FACTOR; - den = CAL_DEGC_PT2 - CAL_DEGC_PT1; - priv->sensor[i].slope = num / den; - } - - priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) - - (CAL_DEGC_PT1 * - priv->sensor[i].slope); - dev_dbg(priv->dev, "%s: offset:%d\n", __func__, priv->sensor[i].offset); - } -} - -static inline u32 degc_to_code(int degc, const struct tsens_sensor *s) -{ - u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR); - - pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc); - return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE); -} - -static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) -{ - int degc, num, den; - - num = (adc_code * SLOPE_FACTOR) - s->offset; - den = s->slope; - - if (num > 0) - degc = num + (den / 2); - else if (num < 0) - degc = num - (den / 2); - else - degc = num; - - degc /= den; - - return degc; -} - -/** - * tsens_hw_to_mC - Return sign-extended temperature in mCelsius. - * @s: Pointer to sensor struct - * @field: Index into regmap_field array pointing to temperature data - * - * This function handles temperature returned in ADC code or deciCelsius - * depending on IP version. - * - * Return: Temperature in milliCelsius on success, a negative errno will - * be returned in error cases - */ -static int tsens_hw_to_mC(const struct tsens_sensor *s, int field) -{ - struct tsens_priv *priv = s->priv; - u32 resolution; - u32 temp = 0; - int ret; - - resolution = priv->fields[LAST_TEMP_0].msb - - priv->fields[LAST_TEMP_0].lsb; - - ret = regmap_field_read(priv->rf[field], &temp); - if (ret) - return ret; - - /* Convert temperature from ADC code to milliCelsius */ - if (priv->feat->adc) - return code_to_degc(temp, s) * 1000; - - /* deciCelsius -> milliCelsius along with sign extension */ - return sign_extend32(temp, resolution) * 100; -} - -/** - * tsens_mC_to_hw - Convert temperature to hardware register value - * @s: Pointer to sensor struct - * @temp: temperature in milliCelsius to be programmed to hardware - * - * This function outputs the value to be written to hardware in ADC code - * or deciCelsius depending on IP version. - * - * Return: ADC code or temperature in deciCelsius. - */ -static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp) -{ - struct tsens_priv *priv = s->priv; - - /* milliC to adc code */ - if (priv->feat->adc) - return degc_to_code(temp / 1000, s); - - /* milliC to deciC */ - return temp / 100; -} - -static inline enum tsens_ver tsens_version(struct tsens_priv *priv) -{ - return priv->feat->ver_major; -} - -static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id, - enum tsens_irq_type irq_type, bool enable) -{ - u32 index = 0; - - switch (irq_type) { - case UPPER: - index = UP_INT_CLEAR_0 + hw_id; - break; - case LOWER: - index = LOW_INT_CLEAR_0 + hw_id; - break; - case CRITICAL: - /* No critical interrupts before v2 */ - return; - } - regmap_field_write(priv->rf[index], enable ? 0 : 1); -} - -static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id, - enum tsens_irq_type irq_type, bool enable) -{ - u32 index_mask = 0, index_clear = 0; - - /* - * To enable the interrupt flag for a sensor: - * - clear the mask bit - * To disable the interrupt flag for a sensor: - * - Mask further interrupts for this sensor - * - Write 1 followed by 0 to clear the interrupt - */ - switch (irq_type) { - case UPPER: - index_mask = UP_INT_MASK_0 + hw_id; - index_clear = UP_INT_CLEAR_0 + hw_id; - break; - case LOWER: - index_mask = LOW_INT_MASK_0 + hw_id; - index_clear = LOW_INT_CLEAR_0 + hw_id; - break; - case CRITICAL: - index_mask = CRIT_INT_MASK_0 + hw_id; - index_clear = CRIT_INT_CLEAR_0 + hw_id; - break; - } - - if (enable) { - regmap_field_write(priv->rf[index_mask], 0); - } else { - regmap_field_write(priv->rf[index_mask], 1); - regmap_field_write(priv->rf[index_clear], 1); - regmap_field_write(priv->rf[index_clear], 0); - } -} - -/** - * tsens_set_interrupt - Set state of an interrupt - * @priv: Pointer to tsens controller private data - * @hw_id: Hardware ID aka. sensor number - * @irq_type: irq_type from enum tsens_irq_type - * @enable: false = disable, true = enable - * - * Call IP-specific function to set state of an interrupt - * - * Return: void - */ -static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id, - enum tsens_irq_type irq_type, bool enable) -{ - dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__, - irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW", - enable ? "en" : "dis"); - if (tsens_version(priv) > VER_1_X) - tsens_set_interrupt_v2(priv, hw_id, irq_type, enable); - else - tsens_set_interrupt_v1(priv, hw_id, irq_type, enable); -} - -/** - * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold - * @priv: Pointer to tsens controller private data - * @hw_id: Hardware ID aka. sensor number - * @d: Pointer to irq state data - * - * Return: 0 if threshold was not violated, 1 if it was violated and negative - * errno in case of errors - */ -static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id, - struct tsens_irq_data *d) -{ - int ret; - - ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol); - if (ret) - return ret; - - if (priv->feat->crit_int) { - ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id], - &d->crit_viol); - if (ret) - return ret; - } - - if (d->up_viol || d->low_viol || d->crit_viol) - return 1; - - return 0; -} - -static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, - const struct tsens_sensor *s, - struct tsens_irq_data *d) -{ - int ret; - - ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear); - if (ret) - return ret; - if (tsens_version(priv) > VER_1_X) { - ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id], - &d->crit_irq_clear); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id], - &d->crit_irq_mask); - if (ret) - return ret; - - d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id); - } else { - /* No mask register on older TSENS */ - d->up_irq_mask = 0; - d->low_irq_mask = 0; - d->crit_irq_clear = 0; - d->crit_irq_mask = 0; - d->crit_thresh = 0; - } - - d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id); - d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id); - - dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n", - hw_id, __func__, - (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "", - d->low_viol, d->up_viol, d->crit_viol, - d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear, - d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask); - dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__, - (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "", - d->low_thresh, d->up_thresh, d->crit_thresh); - - return 0; -} - -static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver) -{ - if (ver > VER_1_X) - return mask & (1 << hw_id); - - /* v1, v0.1 don't have a irq mask register */ - return 0; -} - -/** - * tsens_critical_irq_thread() - Threaded handler for critical interrupts - * @irq: irq number - * @data: tsens controller private data - * - * Check FSM watchdog bark status and clear if needed. - * Check all sensors to find ones that violated their critical threshold limits. - * Clear and then re-enable the interrupt. - * - * The level-triggered interrupt might deassert if the temperature returned to - * within the threshold limits by the time the handler got scheduled. We - * consider the irq to have been handled in that case. - * - * Return: IRQ_HANDLED - */ -irqreturn_t tsens_critical_irq_thread(int irq, void *data) -{ - struct tsens_priv *priv = data; - struct tsens_irq_data d; - int temp, ret, i; - u32 wdog_status, wdog_count; - - if (priv->feat->has_watchdog) { - ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS], - &wdog_status); - if (ret) - return ret; - - if (wdog_status) { - /* Clear WDOG interrupt */ - regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1); - regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0); - ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT], - &wdog_count); - if (ret) - return ret; - if (wdog_count) - dev_dbg(priv->dev, "%s: watchdog count: %d\n", - __func__, wdog_count); - - /* Fall through to handle critical interrupts if any */ - } - } - - for (i = 0; i < priv->num_sensors; i++) { - const struct tsens_sensor *s = &priv->sensor[i]; - u32 hw_id = s->hw_id; - - if (IS_ERR(s->tzd)) - continue; - if (!tsens_threshold_violated(priv, hw_id, &d)) - continue; - ret = get_temp_tsens_valid(s, &temp); - if (ret) { - dev_err(priv->dev, "[%u] %s: error reading sensor\n", - hw_id, __func__); - continue; - } - - tsens_read_irq_state(priv, hw_id, s, &d); - if (d.crit_viol && - !masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) { - /* Mask critical interrupts, unused on Linux */ - tsens_set_interrupt(priv, hw_id, CRITICAL, false); - } - } - - return IRQ_HANDLED; -} - -/** - * tsens_irq_thread - Threaded interrupt handler for uplow interrupts - * @irq: irq number - * @data: tsens controller private data - * - * Check all sensors to find ones that violated their threshold limits. If the - * temperature is still outside the limits, call thermal_zone_device_update() to - * update the thresholds, else re-enable the interrupts. - * - * The level-triggered interrupt might deassert if the temperature returned to - * within the threshold limits by the time the handler got scheduled. We - * consider the irq to have been handled in that case. - * - * Return: IRQ_HANDLED - */ -irqreturn_t tsens_irq_thread(int irq, void *data) -{ - struct tsens_priv *priv = data; - struct tsens_irq_data d; - bool enable = true, disable = false; - unsigned long flags; - int temp, ret, i; - - for (i = 0; i < priv->num_sensors; i++) { - bool trigger = false; - const struct tsens_sensor *s = &priv->sensor[i]; - u32 hw_id = s->hw_id; - - if (IS_ERR(s->tzd)) - continue; - if (!tsens_threshold_violated(priv, hw_id, &d)) - continue; - ret = get_temp_tsens_valid(s, &temp); - if (ret) { - dev_err(priv->dev, "[%u] %s: error reading sensor\n", hw_id, __func__); - continue; - } - - spin_lock_irqsave(&priv->ul_lock, flags); - - tsens_read_irq_state(priv, hw_id, s, &d); - - if (d.up_viol && - !masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) { - tsens_set_interrupt(priv, hw_id, UPPER, disable); - if (d.up_thresh > temp) { - dev_dbg(priv->dev, "[%u] %s: re-arm upper\n", - hw_id, __func__); - tsens_set_interrupt(priv, hw_id, UPPER, enable); - } else { - trigger = true; - /* Keep irq masked */ - } - } else if (d.low_viol && - !masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) { - tsens_set_interrupt(priv, hw_id, LOWER, disable); - if (d.low_thresh < temp) { - dev_dbg(priv->dev, "[%u] %s: re-arm low\n", - hw_id, __func__); - tsens_set_interrupt(priv, hw_id, LOWER, enable); - } else { - trigger = true; - /* Keep irq masked */ - } - } - - spin_unlock_irqrestore(&priv->ul_lock, flags); - - if (trigger) { - dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n", - hw_id, __func__, temp); - thermal_zone_device_update(s->tzd, - THERMAL_EVENT_UNSPECIFIED); - } else { - dev_dbg(priv->dev, "[%u] %s: no violation: %d\n", - hw_id, __func__, temp); - } - } - - return IRQ_HANDLED; -} - -int tsens_set_trips(void *_sensor, int low, int high) -{ - struct tsens_sensor *s = _sensor; - struct tsens_priv *priv = s->priv; - struct device *dev = priv->dev; - struct tsens_irq_data d; - unsigned long flags; - int high_val, low_val, cl_high, cl_low; - u32 hw_id = s->hw_id; - - dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n", - hw_id, __func__, low, high); - - cl_high = clamp_val(high, -40000, 120000); - cl_low = clamp_val(low, -40000, 120000); - - high_val = tsens_mC_to_hw(s, cl_high); - low_val = tsens_mC_to_hw(s, cl_low); - - spin_lock_irqsave(&priv->ul_lock, flags); - - tsens_read_irq_state(priv, hw_id, s, &d); - - /* Write the new thresholds and clear the status */ - regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val); - regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val); - tsens_set_interrupt(priv, hw_id, LOWER, true); - tsens_set_interrupt(priv, hw_id, UPPER, true); - - spin_unlock_irqrestore(&priv->ul_lock, flags); - - dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n", - hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high); - - return 0; -} - -int tsens_enable_irq(struct tsens_priv *priv) -{ - int ret; - int val = tsens_version(priv) > VER_1_X ? 7 : 1; - - ret = regmap_field_write(priv->rf[INT_EN], val); - if (ret < 0) - dev_err(priv->dev, "%s: failed to enable interrupts\n", __func__); - - return ret; -} - -void tsens_disable_irq(struct tsens_priv *priv) -{ - regmap_field_write(priv->rf[INT_EN], 0); -} - -int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) -{ - struct tsens_priv *priv = s->priv; - int hw_id = s->hw_id; - u32 temp_idx = LAST_TEMP_0 + hw_id; - u32 valid_idx = VALID_0 + hw_id; - u32 valid; - int ret; - - ret = regmap_field_read(priv->rf[valid_idx], &valid); - if (ret) - return ret; - while (!valid) { - /* Valid bit is 0 for 6 AHB clock cycles. - * At 19.2MHz, 1 AHB clock is ~60ns. - * We should enter this loop very, very rarely. - */ - ndelay(400); - ret = regmap_field_read(priv->rf[valid_idx], &valid); - if (ret) - return ret; - } - - /* Valid bit is set, OK to read the temperature */ - *temp = tsens_hw_to_mC(s, temp_idx); - - return 0; -} - -int get_temp_common(const struct tsens_sensor *s, int *temp) -{ - struct tsens_priv *priv = s->priv; - int hw_id = s->hw_id; - int last_temp = 0, ret; - - ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp); - if (ret) - return ret; - - *temp = code_to_degc(last_temp, s) * 1000; - - return 0; -} - -#ifdef CONFIG_DEBUG_FS -static int dbg_sensors_show(struct seq_file *s, void *data) -{ - struct platform_device *pdev = s->private; - struct tsens_priv *priv = platform_get_drvdata(pdev); - int i; - - seq_printf(s, "max: %2d\nnum: %2d\n\n", - priv->feat->max_sensors, priv->num_sensors); - - seq_puts(s, " id slope offset\n--------------------------\n"); - for (i = 0; i < priv->num_sensors; i++) { - seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id, - priv->sensor[i].slope, priv->sensor[i].offset); - } - - return 0; -} - -static int dbg_version_show(struct seq_file *s, void *data) -{ - struct platform_device *pdev = s->private; - struct tsens_priv *priv = platform_get_drvdata(pdev); - u32 maj_ver, min_ver, step_ver; - int ret; - - if (tsens_version(priv) > VER_0_1) { - ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver); - if (ret) - return ret; - ret = regmap_field_read(priv->rf[VER_STEP], &step_ver); - if (ret) - return ret; - seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver); - } else { - seq_puts(s, "0.1.0\n"); - } - - return 0; -} - -DEFINE_SHOW_ATTRIBUTE(dbg_version); -DEFINE_SHOW_ATTRIBUTE(dbg_sensors); - -static void tsens_debug_init(struct platform_device *pdev) -{ - struct tsens_priv *priv = platform_get_drvdata(pdev); - struct dentry *root, *file; - - root = debugfs_lookup("tsens", NULL); - if (!root) - priv->debug_root = debugfs_create_dir("tsens", NULL); - else - priv->debug_root = root; - - file = debugfs_lookup("version", priv->debug_root); - if (!file) - debugfs_create_file("version", 0444, priv->debug_root, - pdev, &dbg_version_fops); - - /* A directory for each instance of the TSENS IP */ - priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root); - debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops); -} -#else -static inline void tsens_debug_init(struct platform_device *pdev) {} -#endif - -static const struct regmap_config tsens_config = { - .name = "tm", - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, -}; - -static const struct regmap_config tsens_srot_config = { - .name = "srot", - .reg_bits = 32, - .val_bits = 32, - .reg_stride = 4, -}; - -int __init init_common(struct tsens_priv *priv) -{ - void __iomem *tm_base, *srot_base; - struct device *dev = priv->dev; - u32 ver_minor; - struct resource *res; - u32 enabled; - int ret, i, j; - struct platform_device *op = of_find_device_by_node(priv->dev->of_node); - - if (!op) - return -EINVAL; - - if (op->num_resources > 1) { - /* DT with separate SROT and TM address space */ - priv->tm_offset = 0; - res = platform_get_resource(op, IORESOURCE_MEM, 1); - srot_base = devm_ioremap_resource(dev, res); - if (IS_ERR(srot_base)) { - ret = PTR_ERR(srot_base); - goto err_put_device; - } - - priv->srot_map = devm_regmap_init_mmio(dev, srot_base, - &tsens_srot_config); - if (IS_ERR(priv->srot_map)) { - ret = PTR_ERR(priv->srot_map); - goto err_put_device; - } - } else { - /* old DTs where SROT and TM were in a contiguous 2K block */ - priv->tm_offset = 0x1000; - } - - res = platform_get_resource(op, IORESOURCE_MEM, 0); - tm_base = devm_ioremap_resource(dev, res); - if (IS_ERR(tm_base)) { - ret = PTR_ERR(tm_base); - goto err_put_device; - } - - priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config); - if (IS_ERR(priv->tm_map)) { - ret = PTR_ERR(priv->tm_map); - goto err_put_device; - } - - if (tsens_version(priv) > VER_0_1) { - for (i = VER_MAJOR; i <= VER_STEP; i++) { - priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map, - priv->fields[i]); - if (IS_ERR(priv->rf[i])) - return PTR_ERR(priv->rf[i]); - } - ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor); - if (ret) - goto err_put_device; - } - - priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map, - priv->fields[TSENS_EN]); - if (IS_ERR(priv->rf[TSENS_EN])) { - ret = PTR_ERR(priv->rf[TSENS_EN]); - goto err_put_device; - } - ret = regmap_field_read(priv->rf[TSENS_EN], &enabled); - if (ret) - goto err_put_device; - if (!enabled) { - dev_err(dev, "%s: device not enabled\n", __func__); - ret = -ENODEV; - goto err_put_device; - } - - priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map, - priv->fields[SENSOR_EN]); - if (IS_ERR(priv->rf[SENSOR_EN])) { - ret = PTR_ERR(priv->rf[SENSOR_EN]); - goto err_put_device; - } - priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map, - priv->fields[INT_EN]); - if (IS_ERR(priv->rf[INT_EN])) { - ret = PTR_ERR(priv->rf[INT_EN]); - goto err_put_device; - } - - /* This loop might need changes if enum regfield_ids is reordered */ - for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) { - for (i = 0; i < priv->feat->max_sensors; i++) { - int idx = j + i; - - priv->rf[idx] = devm_regmap_field_alloc(dev, priv->tm_map, - priv->fields[idx]); - if (IS_ERR(priv->rf[idx])) { - ret = PTR_ERR(priv->rf[idx]); - goto err_put_device; - } - } - } - - if (priv->feat->crit_int) { - /* Loop might need changes if enum regfield_ids is reordered */ - for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) { - for (i = 0; i < priv->feat->max_sensors; i++) { - int idx = j + i; - - priv->rf[idx] = - devm_regmap_field_alloc(dev, - priv->tm_map, - priv->fields[idx]); - if (IS_ERR(priv->rf[idx])) { - ret = PTR_ERR(priv->rf[idx]); - goto err_put_device; - } - } - } - } - - if (tsens_version(priv) > VER_1_X && ver_minor > 2) { - /* Watchdog is present only on v2.3+ */ - priv->feat->has_watchdog = 1; - for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) { - priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map, - priv->fields[i]); - if (IS_ERR(priv->rf[i])) { - ret = PTR_ERR(priv->rf[i]); - goto err_put_device; - } - } - /* - * Watchdog is already enabled, unmask the bark. - * Disable cycle completion monitoring - */ - regmap_field_write(priv->rf[WDOG_BARK_MASK], 0); - regmap_field_write(priv->rf[CC_MON_MASK], 1); - } - - spin_lock_init(&priv->ul_lock); - tsens_enable_irq(priv); - tsens_debug_init(op); - -err_put_device: - put_device(&op->dev); - return ret; -} diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index 2f77d235cf73..8d3e94d2a9ed 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1,19 +1,857 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2019, 2020, Linaro Ltd. */ #include #include +#include #include +#include #include +#include #include #include #include +#include #include #include #include "tsens.h" +/** + * struct tsens_irq_data - IRQ status and temperature violations + * @up_viol: upper threshold violated + * @up_thresh: upper threshold temperature value + * @up_irq_mask: mask register for upper threshold irqs + * @up_irq_clear: clear register for uppper threshold irqs + * @low_viol: lower threshold violated + * @low_thresh: lower threshold temperature value + * @low_irq_mask: mask register for lower threshold irqs + * @low_irq_clear: clear register for lower threshold irqs + * @crit_viol: critical threshold violated + * @crit_thresh: critical threshold temperature value + * @crit_irq_mask: mask register for critical threshold irqs + * @crit_irq_clear: clear register for critical threshold irqs + * + * Structure containing data about temperature threshold settings and + * irq status if they were violated. + */ +struct tsens_irq_data { + u32 up_viol; + int up_thresh; + u32 up_irq_mask; + u32 up_irq_clear; + u32 low_viol; + int low_thresh; + u32 low_irq_mask; + u32 low_irq_clear; + u32 crit_viol; + u32 crit_thresh; + u32 crit_irq_mask; + u32 crit_irq_clear; +}; + +char *qfprom_read(struct device *dev, const char *cname) +{ + struct nvmem_cell *cell; + ssize_t data; + char *ret; + + cell = nvmem_cell_get(dev, cname); + if (IS_ERR(cell)) + return ERR_CAST(cell); + + ret = nvmem_cell_read(cell, &data); + nvmem_cell_put(cell); + + return ret; +} + +/* + * Use this function on devices where slope and offset calculations + * depend on calibration data read from qfprom. On others the slope + * and offset values are derived from tz->tzp->slope and tz->tzp->offset + * resp. + */ +void compute_intercept_slope(struct tsens_priv *priv, u32 *p1, + u32 *p2, u32 mode) +{ + int i; + int num, den; + + for (i = 0; i < priv->num_sensors; i++) { + dev_dbg(priv->dev, + "%s: sensor%d - data_point1:%#x data_point2:%#x\n", + __func__, i, p1[i], p2[i]); + + priv->sensor[i].slope = SLOPE_DEFAULT; + if (mode == TWO_PT_CALIB) { + /* + * slope (m) = adc_code2 - adc_code1 (y2 - y1)/ + * temp_120_degc - temp_30_degc (x2 - x1) + */ + num = p2[i] - p1[i]; + num *= SLOPE_FACTOR; + den = CAL_DEGC_PT2 - CAL_DEGC_PT1; + priv->sensor[i].slope = num / den; + } + + priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) - + (CAL_DEGC_PT1 * + priv->sensor[i].slope); + dev_dbg(priv->dev, "%s: offset:%d\n", __func__, + priv->sensor[i].offset); + } +} + +static inline u32 degc_to_code(int degc, const struct tsens_sensor *s) +{ + u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR); + + pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc); + return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE); +} + +static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s) +{ + int degc, num, den; + + num = (adc_code * SLOPE_FACTOR) - s->offset; + den = s->slope; + + if (num > 0) + degc = num + (den / 2); + else if (num < 0) + degc = num - (den / 2); + else + degc = num; + + degc /= den; + + return degc; +} + +/** + * tsens_hw_to_mC - Return sign-extended temperature in mCelsius. + * @s: Pointer to sensor struct + * @field: Index into regmap_field array pointing to temperature data + * + * This function handles temperature returned in ADC code or deciCelsius + * depending on IP version. + * + * Return: Temperature in milliCelsius on success, a negative errno will + * be returned in error cases + */ +static int tsens_hw_to_mC(const struct tsens_sensor *s, int field) +{ + struct tsens_priv *priv = s->priv; + u32 resolution; + u32 temp = 0; + int ret; + + resolution = priv->fields[LAST_TEMP_0].msb - + priv->fields[LAST_TEMP_0].lsb; + + ret = regmap_field_read(priv->rf[field], &temp); + if (ret) + return ret; + + /* Convert temperature from ADC code to milliCelsius */ + if (priv->feat->adc) + return code_to_degc(temp, s) * 1000; + + /* deciCelsius -> milliCelsius along with sign extension */ + return sign_extend32(temp, resolution) * 100; +} + +/** + * tsens_mC_to_hw - Convert temperature to hardware register value + * @s: Pointer to sensor struct + * @temp: temperature in milliCelsius to be programmed to hardware + * + * This function outputs the value to be written to hardware in ADC code + * or deciCelsius depending on IP version. + * + * Return: ADC code or temperature in deciCelsius. + */ +static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp) +{ + struct tsens_priv *priv = s->priv; + + /* milliC to adc code */ + if (priv->feat->adc) + return degc_to_code(temp / 1000, s); + + /* milliC to deciC */ + return temp / 100; +} + +static inline enum tsens_ver tsens_version(struct tsens_priv *priv) +{ + return priv->feat->ver_major; +} + +static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id, + enum tsens_irq_type irq_type, bool enable) +{ + u32 index = 0; + + switch (irq_type) { + case UPPER: + index = UP_INT_CLEAR_0 + hw_id; + break; + case LOWER: + index = LOW_INT_CLEAR_0 + hw_id; + break; + case CRITICAL: + /* No critical interrupts before v2 */ + return; + } + regmap_field_write(priv->rf[index], enable ? 0 : 1); +} + +static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id, + enum tsens_irq_type irq_type, bool enable) +{ + u32 index_mask = 0, index_clear = 0; + + /* + * To enable the interrupt flag for a sensor: + * - clear the mask bit + * To disable the interrupt flag for a sensor: + * - Mask further interrupts for this sensor + * - Write 1 followed by 0 to clear the interrupt + */ + switch (irq_type) { + case UPPER: + index_mask = UP_INT_MASK_0 + hw_id; + index_clear = UP_INT_CLEAR_0 + hw_id; + break; + case LOWER: + index_mask = LOW_INT_MASK_0 + hw_id; + index_clear = LOW_INT_CLEAR_0 + hw_id; + break; + case CRITICAL: + index_mask = CRIT_INT_MASK_0 + hw_id; + index_clear = CRIT_INT_CLEAR_0 + hw_id; + break; + } + + if (enable) { + regmap_field_write(priv->rf[index_mask], 0); + } else { + regmap_field_write(priv->rf[index_mask], 1); + regmap_field_write(priv->rf[index_clear], 1); + regmap_field_write(priv->rf[index_clear], 0); + } +} + +/** + * tsens_set_interrupt - Set state of an interrupt + * @priv: Pointer to tsens controller private data + * @hw_id: Hardware ID aka. sensor number + * @irq_type: irq_type from enum tsens_irq_type + * @enable: false = disable, true = enable + * + * Call IP-specific function to set state of an interrupt + * + * Return: void + */ +static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id, + enum tsens_irq_type irq_type, bool enable) +{ + dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__, + irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW", + enable ? "en" : "dis"); + if (tsens_version(priv) > VER_1_X) + tsens_set_interrupt_v2(priv, hw_id, irq_type, enable); + else + tsens_set_interrupt_v1(priv, hw_id, irq_type, enable); +} + +/** + * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold + * @priv: Pointer to tsens controller private data + * @hw_id: Hardware ID aka. sensor number + * @d: Pointer to irq state data + * + * Return: 0 if threshold was not violated, 1 if it was violated and negative + * errno in case of errors + */ +static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id, + struct tsens_irq_data *d) +{ + int ret; + + ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol); + if (ret) + return ret; + + if (priv->feat->crit_int) { + ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id], + &d->crit_viol); + if (ret) + return ret; + } + + if (d->up_viol || d->low_viol || d->crit_viol) + return 1; + + return 0; +} + +static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id, + const struct tsens_sensor *s, + struct tsens_irq_data *d) +{ + int ret; + + ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear); + if (ret) + return ret; + if (tsens_version(priv) > VER_1_X) { + ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id], + &d->crit_irq_clear); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id], + &d->crit_irq_mask); + if (ret) + return ret; + + d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id); + } else { + /* No mask register on older TSENS */ + d->up_irq_mask = 0; + d->low_irq_mask = 0; + d->crit_irq_clear = 0; + d->crit_irq_mask = 0; + d->crit_thresh = 0; + } + + d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id); + d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id); + + dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n", + hw_id, __func__, + (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "", + d->low_viol, d->up_viol, d->crit_viol, + d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear, + d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask); + dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__, + (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "", + d->low_thresh, d->up_thresh, d->crit_thresh); + + return 0; +} + +static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver) +{ + if (ver > VER_1_X) + return mask & (1 << hw_id); + + /* v1, v0.1 don't have a irq mask register */ + return 0; +} + +/** + * tsens_critical_irq_thread() - Threaded handler for critical interrupts + * @irq: irq number + * @data: tsens controller private data + * + * Check FSM watchdog bark status and clear if needed. + * Check all sensors to find ones that violated their critical threshold limits. + * Clear and then re-enable the interrupt. + * + * The level-triggered interrupt might deassert if the temperature returned to + * within the threshold limits by the time the handler got scheduled. We + * consider the irq to have been handled in that case. + * + * Return: IRQ_HANDLED + */ +irqreturn_t tsens_critical_irq_thread(int irq, void *data) +{ + struct tsens_priv *priv = data; + struct tsens_irq_data d; + int temp, ret, i; + u32 wdog_status, wdog_count; + + if (priv->feat->has_watchdog) { + ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS], + &wdog_status); + if (ret) + return ret; + + if (wdog_status) { + /* Clear WDOG interrupt */ + regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1); + regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0); + ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT], + &wdog_count); + if (ret) + return ret; + if (wdog_count) + dev_dbg(priv->dev, "%s: watchdog count: %d\n", + __func__, wdog_count); + + /* Fall through to handle critical interrupts if any */ + } + } + + for (i = 0; i < priv->num_sensors; i++) { + const struct tsens_sensor *s = &priv->sensor[i]; + u32 hw_id = s->hw_id; + + if (IS_ERR(s->tzd)) + continue; + if (!tsens_threshold_violated(priv, hw_id, &d)) + continue; + ret = get_temp_tsens_valid(s, &temp); + if (ret) { + dev_err(priv->dev, "[%u] %s: error reading sensor\n", + hw_id, __func__); + continue; + } + + tsens_read_irq_state(priv, hw_id, s, &d); + if (d.crit_viol && + !masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) { + /* Mask critical interrupts, unused on Linux */ + tsens_set_interrupt(priv, hw_id, CRITICAL, false); + } + } + + return IRQ_HANDLED; +} + +/** + * tsens_irq_thread - Threaded interrupt handler for uplow interrupts + * @irq: irq number + * @data: tsens controller private data + * + * Check all sensors to find ones that violated their threshold limits. If the + * temperature is still outside the limits, call thermal_zone_device_update() to + * update the thresholds, else re-enable the interrupts. + * + * The level-triggered interrupt might deassert if the temperature returned to + * within the threshold limits by the time the handler got scheduled. We + * consider the irq to have been handled in that case. + * + * Return: IRQ_HANDLED + */ +irqreturn_t tsens_irq_thread(int irq, void *data) +{ + struct tsens_priv *priv = data; + struct tsens_irq_data d; + bool enable = true, disable = false; + unsigned long flags; + int temp, ret, i; + + for (i = 0; i < priv->num_sensors; i++) { + bool trigger = false; + const struct tsens_sensor *s = &priv->sensor[i]; + u32 hw_id = s->hw_id; + + if (IS_ERR(s->tzd)) + continue; + if (!tsens_threshold_violated(priv, hw_id, &d)) + continue; + ret = get_temp_tsens_valid(s, &temp); + if (ret) { + dev_err(priv->dev, "[%u] %s: error reading sensor\n", + hw_id, __func__); + continue; + } + + spin_lock_irqsave(&priv->ul_lock, flags); + + tsens_read_irq_state(priv, hw_id, s, &d); + + if (d.up_viol && + !masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) { + tsens_set_interrupt(priv, hw_id, UPPER, disable); + if (d.up_thresh > temp) { + dev_dbg(priv->dev, "[%u] %s: re-arm upper\n", + hw_id, __func__); + tsens_set_interrupt(priv, hw_id, UPPER, enable); + } else { + trigger = true; + /* Keep irq masked */ + } + } else if (d.low_viol && + !masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) { + tsens_set_interrupt(priv, hw_id, LOWER, disable); + if (d.low_thresh < temp) { + dev_dbg(priv->dev, "[%u] %s: re-arm low\n", + hw_id, __func__); + tsens_set_interrupt(priv, hw_id, LOWER, enable); + } else { + trigger = true; + /* Keep irq masked */ + } + } + + spin_unlock_irqrestore(&priv->ul_lock, flags); + + if (trigger) { + dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n", + hw_id, __func__, temp); + thermal_zone_device_update(s->tzd, + THERMAL_EVENT_UNSPECIFIED); + } else { + dev_dbg(priv->dev, "[%u] %s: no violation: %d\n", + hw_id, __func__, temp); + } + } + + return IRQ_HANDLED; +} + +int tsens_set_trips(void *_sensor, int low, int high) +{ + struct tsens_sensor *s = _sensor; + struct tsens_priv *priv = s->priv; + struct device *dev = priv->dev; + struct tsens_irq_data d; + unsigned long flags; + int high_val, low_val, cl_high, cl_low; + u32 hw_id = s->hw_id; + + dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n", + hw_id, __func__, low, high); + + cl_high = clamp_val(high, -40000, 120000); + cl_low = clamp_val(low, -40000, 120000); + + high_val = tsens_mC_to_hw(s, cl_high); + low_val = tsens_mC_to_hw(s, cl_low); + + spin_lock_irqsave(&priv->ul_lock, flags); + + tsens_read_irq_state(priv, hw_id, s, &d); + + /* Write the new thresholds and clear the status */ + regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val); + regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val); + tsens_set_interrupt(priv, hw_id, LOWER, true); + tsens_set_interrupt(priv, hw_id, UPPER, true); + + spin_unlock_irqrestore(&priv->ul_lock, flags); + + dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n", + hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high); + + return 0; +} + +int tsens_enable_irq(struct tsens_priv *priv) +{ + int ret; + int val = tsens_version(priv) > VER_1_X ? 7 : 1; + + ret = regmap_field_write(priv->rf[INT_EN], val); + if (ret < 0) + dev_err(priv->dev, "%s: failed to enable interrupts\n", + __func__); + + return ret; +} + +void tsens_disable_irq(struct tsens_priv *priv) +{ + regmap_field_write(priv->rf[INT_EN], 0); +} + +int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp) +{ + struct tsens_priv *priv = s->priv; + int hw_id = s->hw_id; + u32 temp_idx = LAST_TEMP_0 + hw_id; + u32 valid_idx = VALID_0 + hw_id; + u32 valid; + int ret; + + ret = regmap_field_read(priv->rf[valid_idx], &valid); + if (ret) + return ret; + while (!valid) { + /* Valid bit is 0 for 6 AHB clock cycles. + * At 19.2MHz, 1 AHB clock is ~60ns. + * We should enter this loop very, very rarely. + */ + ndelay(400); + ret = regmap_field_read(priv->rf[valid_idx], &valid); + if (ret) + return ret; + } + + /* Valid bit is set, OK to read the temperature */ + *temp = tsens_hw_to_mC(s, temp_idx); + + return 0; +} + +int get_temp_common(const struct tsens_sensor *s, int *temp) +{ + struct tsens_priv *priv = s->priv; + int hw_id = s->hw_id; + int last_temp = 0, ret; + + ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp); + if (ret) + return ret; + + *temp = code_to_degc(last_temp, s) * 1000; + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int dbg_sensors_show(struct seq_file *s, void *data) +{ + struct platform_device *pdev = s->private; + struct tsens_priv *priv = platform_get_drvdata(pdev); + int i; + + seq_printf(s, "max: %2d\nnum: %2d\n\n", + priv->feat->max_sensors, priv->num_sensors); + + seq_puts(s, " id slope offset\n--------------------------\n"); + for (i = 0; i < priv->num_sensors; i++) { + seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id, + priv->sensor[i].slope, priv->sensor[i].offset); + } + + return 0; +} + +static int dbg_version_show(struct seq_file *s, void *data) +{ + struct platform_device *pdev = s->private; + struct tsens_priv *priv = platform_get_drvdata(pdev); + u32 maj_ver, min_ver, step_ver; + int ret; + + if (tsens_version(priv) > VER_0_1) { + ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver); + if (ret) + return ret; + ret = regmap_field_read(priv->rf[VER_STEP], &step_ver); + if (ret) + return ret; + seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver); + } else { + seq_puts(s, "0.1.0\n"); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(dbg_version); +DEFINE_SHOW_ATTRIBUTE(dbg_sensors); + +static void tsens_debug_init(struct platform_device *pdev) +{ + struct tsens_priv *priv = platform_get_drvdata(pdev); + struct dentry *root, *file; + + root = debugfs_lookup("tsens", NULL); + if (!root) + priv->debug_root = debugfs_create_dir("tsens", NULL); + else + priv->debug_root = root; + + file = debugfs_lookup("version", priv->debug_root); + if (!file) + debugfs_create_file("version", 0444, priv->debug_root, + pdev, &dbg_version_fops); + + /* A directory for each instance of the TSENS IP */ + priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root); + debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops); +} +#else +static inline void tsens_debug_init(struct platform_device *pdev) {} +#endif + +static const struct regmap_config tsens_config = { + .name = "tm", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static const struct regmap_config tsens_srot_config = { + .name = "srot", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +int __init init_common(struct tsens_priv *priv) +{ + void __iomem *tm_base, *srot_base; + struct device *dev = priv->dev; + u32 ver_minor; + struct resource *res; + u32 enabled; + int ret, i, j; + struct platform_device *op = of_find_device_by_node(priv->dev->of_node); + + if (!op) + return -EINVAL; + + if (op->num_resources > 1) { + /* DT with separate SROT and TM address space */ + priv->tm_offset = 0; + res = platform_get_resource(op, IORESOURCE_MEM, 1); + srot_base = devm_ioremap_resource(dev, res); + if (IS_ERR(srot_base)) { + ret = PTR_ERR(srot_base); + goto err_put_device; + } + + priv->srot_map = devm_regmap_init_mmio(dev, srot_base, + &tsens_srot_config); + if (IS_ERR(priv->srot_map)) { + ret = PTR_ERR(priv->srot_map); + goto err_put_device; + } + } else { + /* old DTs where SROT and TM were in a contiguous 2K block */ + priv->tm_offset = 0x1000; + } + + res = platform_get_resource(op, IORESOURCE_MEM, 0); + tm_base = devm_ioremap_resource(dev, res); + if (IS_ERR(tm_base)) { + ret = PTR_ERR(tm_base); + goto err_put_device; + } + + priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config); + if (IS_ERR(priv->tm_map)) { + ret = PTR_ERR(priv->tm_map); + goto err_put_device; + } + + if (tsens_version(priv) > VER_0_1) { + for (i = VER_MAJOR; i <= VER_STEP; i++) { + priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map, + priv->fields[i]); + if (IS_ERR(priv->rf[i])) + return PTR_ERR(priv->rf[i]); + } + ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor); + if (ret) + goto err_put_device; + } + + priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map, + priv->fields[TSENS_EN]); + if (IS_ERR(priv->rf[TSENS_EN])) { + ret = PTR_ERR(priv->rf[TSENS_EN]); + goto err_put_device; + } + ret = regmap_field_read(priv->rf[TSENS_EN], &enabled); + if (ret) + goto err_put_device; + if (!enabled) { + dev_err(dev, "%s: device not enabled\n", __func__); + ret = -ENODEV; + goto err_put_device; + } + + priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map, + priv->fields[SENSOR_EN]); + if (IS_ERR(priv->rf[SENSOR_EN])) { + ret = PTR_ERR(priv->rf[SENSOR_EN]); + goto err_put_device; + } + priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map, + priv->fields[INT_EN]); + if (IS_ERR(priv->rf[INT_EN])) { + ret = PTR_ERR(priv->rf[INT_EN]); + goto err_put_device; + } + + /* This loop might need changes if enum regfield_ids is reordered */ + for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) { + for (i = 0; i < priv->feat->max_sensors; i++) { + int idx = j + i; + + priv->rf[idx] = devm_regmap_field_alloc(dev, + priv->tm_map, + priv->fields[idx]); + if (IS_ERR(priv->rf[idx])) { + ret = PTR_ERR(priv->rf[idx]); + goto err_put_device; + } + } + } + + if (priv->feat->crit_int) { + /* Loop might need changes if enum regfield_ids is reordered */ + for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) { + for (i = 0; i < priv->feat->max_sensors; i++) { + int idx = j + i; + + priv->rf[idx] = + devm_regmap_field_alloc(dev, + priv->tm_map, + priv->fields[idx]); + if (IS_ERR(priv->rf[idx])) { + ret = PTR_ERR(priv->rf[idx]); + goto err_put_device; + } + } + } + } + + if (tsens_version(priv) > VER_1_X && ver_minor > 2) { + /* Watchdog is present only on v2.3+ */ + priv->feat->has_watchdog = 1; + for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) { + priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map, + priv->fields[i]); + if (IS_ERR(priv->rf[i])) { + ret = PTR_ERR(priv->rf[i]); + goto err_put_device; + } + } + /* + * Watchdog is already enabled, unmask the bark. + * Disable cycle completion monitoring + */ + regmap_field_write(priv->rf[WDOG_BARK_MASK], 0); + regmap_field_write(priv->rf[CC_MON_MASK], 1); + } + + spin_lock_init(&priv->ul_lock); + tsens_enable_irq(priv); + tsens_debug_init(op); + +err_put_device: + put_device(&op->dev); + return ret; +} + static int tsens_get_temp(void *data, int *temp) { struct tsens_sensor *s = data; diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index 502acf0e6828..59d01162c66a 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -580,11 +580,6 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mo int init_common(struct tsens_priv *priv); int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp); int get_temp_common(const struct tsens_sensor *s, int *temp); -int tsens_enable_irq(struct tsens_priv *priv); -void tsens_disable_irq(struct tsens_priv *priv); -int tsens_set_trips(void *_sensor, int low, int high); -irqreturn_t tsens_irq_thread(int irq, void *data); -irqreturn_t tsens_critical_irq_thread(int irq, void *data); /* TSENS target */ extern struct tsens_plat_data data_8960; -- cgit v1.2.3 From 1ab20c0e53fa2167357bd90b7f7f7019cad9daaa Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 7 May 2020 13:29:55 +0200 Subject: thermal: qoriq: Add platform dependencies The QorIQ Thermal Monitoring Unit is only present on Freescale E500MC and Layerscape SoCs, and on NXP i.MX8 SoCs. Add platform dependencies to the QORIQ_THERMAL config symbol, to avoid asking the user about it when configuring a kernel without support for any of the aforementioned SoCs. Signed-off-by: Geert Uytterhoeven Acked-by: Arnd Bergmann Acked-by: Li Yang Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200507112955.23520-5-geert+renesas@glider.be --- drivers/thermal/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index e53314ea9e25..3eb2348e5242 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -295,8 +295,8 @@ config MAX77620_THERMAL config QORIQ_THERMAL tristate "QorIQ Thermal Monitoring Unit" - depends on THERMAL_OF - depends on HAS_IOMEM + depends on THERMAL_OF && HAS_IOMEM + depends on PPC_E500MC || SOC_LS1021A || ARCH_LAYERSCAPE || (ARCH_MXC && ARM64) || COMPILE_TEST select REGMAP_MMIO help Support for Thermal Monitoring Unit (TMU) found on QorIQ platforms. -- cgit v1.2.3 From b03628b73564cf54e05b7611e22d9886a8822877 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 14 May 2020 17:25:05 +0200 Subject: thermal: rcar_thermal: Clean up rcar_thermal_update_temp() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moving the ctemp variable out of the private data structure made it possible to clean up rcar_thermal_update_temp(). Initialize the local ctemp to the error code to return if the reading fails and just return it at the end of the function. It's OK to change the datatype of old, new and ctemp to int as all values are ANDed with CTEMP (0x3f) before being stored. While at it change the datatype of the loop variable 'i' to to unsigned int. Suggested-by: Geert Uytterhoeven Signed-off-by: Niklas Söderlund Reviewed-by: Geert Uytterhoeven Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200514152505.1927634-1-niklas.soderlund+renesas@ragnatech.se --- drivers/thermal/rcar_thermal.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index e0c1f2409035..46aeb28b4e90 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -198,8 +198,8 @@ static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) { struct device *dev = rcar_priv_to_dev(priv); - int i; - u32 ctemp, old, new; + int old, new, ctemp = -EINVAL; + unsigned int i; mutex_lock(&priv->lock); @@ -209,7 +209,6 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) */ rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL); - ctemp = 0; old = ~0; for (i = 0; i < 128; i++) { /* @@ -227,7 +226,7 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) old = new; } - if (!ctemp) { + if (ctemp < 0) { dev_err(dev, "thermal sensor was broken\n"); goto err_out_unlock; } @@ -248,7 +247,7 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv) err_out_unlock: mutex_unlock(&priv->lock); - return ctemp ? ctemp : -EINVAL; + return ctemp; } static int rcar_thermal_get_current_temp(struct rcar_thermal_priv *priv, -- cgit v1.2.3 From 47fa116e5faec894f6238f87322c01dd41d21e23 Mon Sep 17 00:00:00 2001 From: Yuantian Tang Date: Tue, 26 May 2020 14:02:12 +0800 Subject: thermal: qoriq: Update the settings for TMUv2 For TMU v2, TMSAR registers need to be set properly to get the accurate temperature values. Also the temperature read needs to be converted to degree Celsius since it is in degrees Kelvin. Signed-off-by: Yuantian Tang Signed-off-by: Daniel Lezcano Link: https://lore.kernel.org/r/20200526060212.4118-1-andy.tang@nxp.com --- drivers/thermal/qoriq_thermal.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/qoriq_thermal.c b/drivers/thermal/qoriq_thermal.c index 028a6bbf75dc..73049f9bea25 100644 --- a/drivers/thermal/qoriq_thermal.c +++ b/drivers/thermal/qoriq_thermal.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "thermal_core.h" #include "thermal_hwmon.h" @@ -23,6 +24,7 @@ #define TMTMIR_DEFAULT 0x0000000f #define TIER_DISABLE 0x0 #define TEUMR0_V2 0x51009c00 +#define TMSARA_V2 0xe #define TMU_VER1 0x1 #define TMU_VER2 0x2 @@ -50,6 +52,9 @@ * Site Register */ #define TRITSR_V BIT(31) +#define REGS_V2_TMSAR(n) (0x304 + 16 * (n)) /* TMU monitoring + * site adjustment register + */ #define REGS_TTRnCR(n) (0xf10 + 4 * (n)) /* Temperature Range n * Control Register */ @@ -85,12 +90,21 @@ static int tmu_get_temp(void *p, int *temp) /* * REGS_TRITSR(id) has the following layout: * + * For TMU Rev1: * 31 ... 7 6 5 4 3 2 1 0 * V TEMP * * Where V bit signifies if the measurement is ready and is * within sensor range. TEMP is an 8 bit value representing - * temperature in C. + * temperature in Celsius. + + * For TMU Rev2: + * 31 ... 8 7 6 5 4 3 2 1 0 + * V TEMP + * + * Where V bit signifies if the measurement is ready and is + * within sensor range. TEMP is an 9 bit value representing + * temperature in KelVin. */ if (regmap_read_poll_timeout(qdata->regmap, REGS_TRITSR(qsensor->id), @@ -100,7 +114,10 @@ static int tmu_get_temp(void *p, int *temp) 10 * USEC_PER_MSEC)) return -ENODATA; - *temp = (val & 0xff) * 1000; + if (qdata->ver == TMU_VER1) + *temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE; + else + *temp = kelvin_to_millicelsius(val & GENMASK(8, 0)); return 0; } @@ -192,6 +209,8 @@ static int qoriq_tmu_calibration(struct device *dev, static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) { + int i; + /* Disable interrupt, using polling instead */ regmap_write(data->regmap, REGS_TIER, TIER_DISABLE); @@ -202,6 +221,8 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) } else { regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT); regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2); + for (i = 0; i < SITES_MAX; i++) + regmap_write(data->regmap, REGS_V2_TMSAR(i), TMSARA_V2); } /* Disable monitoring */ @@ -212,6 +233,7 @@ static const struct regmap_range qoriq_yes_ranges[] = { regmap_reg_range(REGS_TMR, REGS_TSCFGR), regmap_reg_range(REGS_TTRnCR(0), REGS_TTRnCR(3)), regmap_reg_range(REGS_V2_TEUMR(0), REGS_V2_TEUMR(2)), + regmap_reg_range(REGS_V2_TMSAR(0), REGS_V2_TMSAR(15)), regmap_reg_range(REGS_IPBRR(0), REGS_IPBRR(1)), /* Read only registers below */ regmap_reg_range(REGS_TRITSR(0), REGS_TRITSR(15)), -- cgit v1.2.3 From 0ba13c763aacb27ab32bde5d559bf40e88465921 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Mon, 13 Apr 2020 19:09:51 -0700 Subject: thermal/int340x_thermal: Export GDDV Implementing DPTF properly requires making use of firmware-provided information associated with the INT3400 device. Calling GDDV provides a buffer of information which userland can then interpret to determine appropriate DPTF policy. Conflicts: drivers/thermal/intel/int340x_thermal/int3400_thermal.c Signed-off-by: Matthew Garrett Tested-by: Pandruvada, Srinivas Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20200414020953.255364-1-matthewgarrett@google.com --- .../intel/int340x_thermal/int3400_thermal.c | 60 ++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index e802922a13cf..0ad0469e91ba 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -52,6 +52,25 @@ struct int3400_thermal_priv { u8 uuid_bitmap; int rel_misc_dev_res; int current_uuid_index; + char *data_vault; +}; + +static ssize_t data_vault_read(struct file *file, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, size_t count) +{ + memcpy(buf, attr->private + off, count); + return count; +} + +static BIN_ATTR_RO(data_vault, 0); + +static struct bin_attribute *data_attributes[] = { + &bin_attr_data_vault, + NULL, +}; + +static const struct attribute_group data_attribute_group = { + .bin_attrs = data_attributes, }; static ssize_t available_uuids_show(struct device *dev, @@ -280,6 +299,32 @@ static struct thermal_zone_params int3400_thermal_params = { .no_hwmon = true, }; +static void int3400_setup_gddv(struct int3400_thermal_priv *priv) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL, + &buffer); + if (ACPI_FAILURE(status) || !buffer.length) + return; + + obj = buffer.pointer; + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1 + || obj->package.elements[0].type != ACPI_TYPE_BUFFER) { + kfree(buffer.pointer); + return; + } + + priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer, + obj->package.elements[0].buffer.length, + GFP_KERNEL); + bin_attr_data_vault.private = priv->data_vault; + bin_attr_data_vault.size = obj->package.elements[0].buffer.length; + kfree(buffer.pointer); +} + static int int3400_thermal_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); @@ -311,6 +356,8 @@ static int int3400_thermal_probe(struct platform_device *pdev) platform_set_drvdata(pdev, priv); + int3400_setup_gddv(priv); + priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, priv, &int3400_thermal_ops, &int3400_thermal_params, 0, 0); @@ -326,6 +373,13 @@ static int int3400_thermal_probe(struct platform_device *pdev) if (result) goto free_rel_misc; + if (priv->data_vault) { + result = sysfs_create_group(&pdev->dev.kobj, + &data_attribute_group); + if (result) + goto free_uuid; + } + result = acpi_install_notify_handler( priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify, (void *)priv); @@ -335,6 +389,9 @@ static int int3400_thermal_probe(struct platform_device *pdev) return 0; free_sysfs: + if (priv->data_vault) + sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group); +free_uuid: sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); free_rel_misc: if (!priv->rel_misc_dev_res) @@ -359,8 +416,11 @@ static int int3400_thermal_remove(struct platform_device *pdev) if (!priv->rel_misc_dev_res) acpi_thermal_rel_misc_device_remove(priv->adev->handle); + if (priv->data_vault) + sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group); sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); thermal_zone_device_unregister(priv->thermal); + kfree(priv->data_vault); kfree(priv->trts); kfree(priv->arts); kfree(priv); -- cgit v1.2.3 From 006f006f1e5c48e5ddd6515eab2c9a7b27ce1144 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Mon, 13 Apr 2020 19:09:52 -0700 Subject: thermal/int340x_thermal: Export OEM vendor variables The platform vendor may expose an array of OEM-specific values to be used in determining DPTF policy. These are obtained via the ODVP method, and then simply exposed in sysfs. In addition, they are updated when a notification is received or when the DPTF policy is changed by userland. Conflicts: drivers/thermal/intel/int340x_thermal/int3400_thermal.c Signed-off-by: Matthew Garrett Tested-by: Pandruvada, Srinivas Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20200414020953.255364-2-matthewgarrett@google.com --- .../intel/int340x_thermal/int3400_thermal.c | 132 ++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index 0ad0469e91ba..87d9b075363c 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -13,6 +13,7 @@ #include "acpi_thermal_rel.h" #define INT3400_THERMAL_TABLE_CHANGED 0x83 +#define INT3400_ODVP_CHANGED 0x88 enum int3400_thermal_uuid { INT3400_THERMAL_PASSIVE_1, @@ -41,8 +42,11 @@ static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { "BE84BABF-C4D4-403D-B495-3128FD44dAC1", }; +struct odvp_attr; + struct int3400_thermal_priv { struct acpi_device *adev; + struct platform_device *pdev; struct thermal_zone_device *thermal; int mode; int art_count; @@ -53,6 +57,17 @@ struct int3400_thermal_priv { int rel_misc_dev_res; int current_uuid_index; char *data_vault; + int odvp_count; + int *odvp; + struct odvp_attr *odvp_attrs; +}; + +static int evaluate_odvp(struct int3400_thermal_priv *priv); + +struct odvp_attr { + int odvp; + struct int3400_thermal_priv *priv; + struct kobj_attribute attr; }; static ssize_t data_vault_read(struct file *file, struct kobject *kobj, @@ -210,9 +225,110 @@ static int int3400_thermal_run_osc(acpi_handle handle, result = -EPERM; kfree(context.ret.pointer); + return result; } +static ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct odvp_attr *odvp_attr; + + odvp_attr = container_of(attr, struct odvp_attr, attr); + + return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]); +} + +static void cleanup_odvp(struct int3400_thermal_priv *priv) +{ + int i; + + if (priv->odvp_attrs) { + for (i = 0; i < priv->odvp_count; i++) { + sysfs_remove_file(&priv->pdev->dev.kobj, + &priv->odvp_attrs[i].attr.attr); + kfree(priv->odvp_attrs[i].attr.attr.name); + } + kfree(priv->odvp_attrs); + } + kfree(priv->odvp); + priv->odvp_count = 0; +} + +static int evaluate_odvp(struct int3400_thermal_priv *priv) +{ + struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj = NULL; + acpi_status status; + int i, ret; + + status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp); + if (ACPI_FAILURE(status)) { + ret = -EINVAL; + goto out_err; + } + + obj = odvp.pointer; + if (obj->type != ACPI_TYPE_PACKAGE) { + ret = -EINVAL; + goto out_err; + } + + if (priv->odvp == NULL) { + priv->odvp_count = obj->package.count; + priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int), + GFP_KERNEL); + if (!priv->odvp) { + ret = -ENOMEM; + goto out_err; + } + } + + if (priv->odvp_attrs == NULL) { + priv->odvp_attrs = kcalloc(priv->odvp_count, + sizeof(struct odvp_attr), + GFP_KERNEL); + if (!priv->odvp_attrs) { + ret = -ENOMEM; + goto out_err; + } + for (i = 0; i < priv->odvp_count; i++) { + struct odvp_attr *odvp = &priv->odvp_attrs[i]; + + sysfs_attr_init(&odvp->attr.attr); + odvp->priv = priv; + odvp->odvp = i; + odvp->attr.attr.name = kasprintf(GFP_KERNEL, + "odvp%d", i); + + if (!odvp->attr.attr.name) { + ret = -ENOMEM; + goto out_err; + } + odvp->attr.attr.mode = 0444; + odvp->attr.show = odvp_show; + odvp->attr.store = NULL; + ret = sysfs_create_file(&priv->pdev->dev.kobj, + &odvp->attr.attr); + if (ret) + goto out_err; + } + } + + for (i = 0; i < obj->package.count; i++) { + if (obj->package.elements[i].type == ACPI_TYPE_INTEGER) + priv->odvp[i] = obj->package.elements[i].integer.value; + } + + kfree(obj); + return 0; + +out_err: + cleanup_odvp(priv); + kfree(obj); + return ret; +} + static void int3400_notify(acpi_handle handle, u32 event, void *data) @@ -236,6 +352,9 @@ static void int3400_notify(acpi_handle handle, kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, thermal_prop); break; + case INT3400_ODVP_CHANGED: + evaluate_odvp(priv); + break; default: /* Ignore unknown notification codes sent to INT3400 device */ break; @@ -285,6 +404,9 @@ static int int3400_thermal_set_mode(struct thermal_zone_device *thermal, priv->current_uuid_index, enable); } + + evaluate_odvp(priv); + return result; } @@ -338,6 +460,7 @@ static int int3400_thermal_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; + priv->pdev = pdev; priv->adev = adev; result = int3400_thermal_get_uuids(priv); @@ -358,6 +481,8 @@ static int int3400_thermal_probe(struct platform_device *pdev) int3400_setup_gddv(priv); + evaluate_odvp(priv); + priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, priv, &int3400_thermal_ops, &int3400_thermal_params, 0, 0); @@ -389,8 +514,11 @@ static int int3400_thermal_probe(struct platform_device *pdev) return 0; free_sysfs: - if (priv->data_vault) + cleanup_odvp(priv); + if (priv->data_vault) { sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group); + kfree(priv->data_vault); + } free_uuid: sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); free_rel_misc: @@ -413,6 +541,8 @@ static int int3400_thermal_remove(struct platform_device *pdev) priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify); + cleanup_odvp(priv); + if (!priv->rel_misc_dev_res) acpi_thermal_rel_misc_device_remove(priv->adev->handle); -- cgit v1.2.3 From 8d485da0ddee79d0e6713405694253d401e41b93 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Mon, 13 Apr 2020 19:09:53 -0700 Subject: thermal/int340x_thermal: Don't require IDSP to exist The IDSP method doesn't appear to exist on the most recent Intel platforms: instead, the IDSP data is included in the GDDV blob. Since we probably don't want to decompress and parse that in-kernel, just allow any UUID to be written if IDSP is missing. Signed-off-by: Matthew Garrett Tested-by: Pandruvada, Srinivas [ rzhang: fix checkpatch warning in changelog ] Signed-off-by: Zhang Rui Link: https://lore.kernel.org/r/20200414020953.255364-3-matthewgarrett@google.com --- .../intel/int340x_thermal/int3400_thermal.c | 30 ++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c index 87d9b075363c..0b3a62655843 100644 --- a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -96,6 +96,9 @@ static ssize_t available_uuids_show(struct device *dev, int i; int length = 0; + if (!priv->uuid_bitmap) + return sprintf(buf, "UNKNOWN\n"); + for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) { if (priv->uuid_bitmap & (1 << i)) if (PAGE_SIZE - length > 0) @@ -113,11 +116,11 @@ static ssize_t current_uuid_show(struct device *dev, { struct int3400_thermal_priv *priv = dev_get_drvdata(dev); - if (priv->uuid_bitmap & (1 << priv->current_uuid_index)) - return sprintf(buf, "%s\n", - int3400_thermal_uuids[priv->current_uuid_index]); - else + if (priv->current_uuid_index == -1) return sprintf(buf, "INVALID\n"); + + return sprintf(buf, "%s\n", + int3400_thermal_uuids[priv->current_uuid_index]); } static ssize_t current_uuid_store(struct device *dev, @@ -128,9 +131,16 @@ static ssize_t current_uuid_store(struct device *dev, int i; for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) { - if ((priv->uuid_bitmap & (1 << i)) && - !(strncmp(buf, int3400_thermal_uuids[i], - sizeof(int3400_thermal_uuids[i]) - 1))) { + if (!strncmp(buf, int3400_thermal_uuids[i], + sizeof(int3400_thermal_uuids[i]) - 1)) { + /* + * If we have a list of supported UUIDs, make sure + * this one is supported. + */ + if (priv->uuid_bitmap && + !(priv->uuid_bitmap & (1 << i))) + return -EINVAL; + priv->current_uuid_index = i; return count; } @@ -464,9 +474,13 @@ static int int3400_thermal_probe(struct platform_device *pdev) priv->adev = adev; result = int3400_thermal_get_uuids(priv); - if (result) + + /* Missing IDSP isn't fatal */ + if (result && result != -ENODEV) goto free_priv; + priv->current_uuid_index = -1; + result = acpi_parse_art(priv->adev->handle, &priv->art_count, &priv->arts, true); if (result) -- cgit v1.2.3