Message ID | 20240119110842.772606-3-abailon@baylibre.com (mailing list archive) |
---|---|
State | Superseded, archived |
Headers | show |
Series | thermal: Add support of multiple sensors | expand |
Hi Alexandre, On Thu, Apr 11, 2024 at 4:34 PM Alexandre Bailon <abailon@baylibre.com> wrote: > > This adds support of multi sensors to thermal. > Currently, this only support the get_temp operation. > This returns an average temperature of all the sensors. > If defined, a coefficient is applied to the value read from the sensor > before computing the average. > > Signed-off-by: Alexandre Bailon <abailon@baylibre.com> > --- > drivers/thermal/Makefile | 1 + > drivers/thermal/thermal_core.h | 7 ++ > drivers/thermal/thermal_multi.c | 178 ++++++++++++++++++++++++++++++++ > 3 files changed, 186 insertions(+) > create mode 100644 drivers/thermal/thermal_multi.c > > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile > index c934cab309ae..757289a406f7 100644 > --- a/drivers/thermal/Makefile > +++ b/drivers/thermal/Makefile > @@ -6,6 +6,7 @@ CFLAGS_thermal_core.o := -I$(src) > obj-$(CONFIG_THERMAL) += thermal_sys.o > thermal_sys-y += thermal_core.o thermal_sysfs.o > thermal_sys-y += thermal_trip.o thermal_helpers.o > +thermal_sys-y += thermal_multi.o > > # netlink interface to manage the thermal framework > thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o > diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h > index 0a3b3ec5120b..26e83a5c8298 100644 > --- a/drivers/thermal/thermal_core.h > +++ b/drivers/thermal/thermal_core.h > @@ -138,6 +138,13 @@ ssize_t weight_show(struct device *, struct device_attribute *, char *); > ssize_t weight_store(struct device *, struct device_attribute *, const char *, > size_t); > > +/* Multi sensors */ > +int thermal_multi_sensor_validate_coeff(int *coeff, int count, int offset); > +int thermal_multi_sensor_register(const char *name, > + struct thermal_zone_device *sensor_tz, int coeff); > +void thermal_multi_sensor_unregister(struct thermal_zone_device *sensor_tz); > + > + > #ifdef CONFIG_THERMAL_STATISTICS > void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, > unsigned long new_state); > diff --git a/drivers/thermal/thermal_multi.c b/drivers/thermal/thermal_multi.c > new file mode 100644 > index 000000000000..a5a4f1f2d594 > --- /dev/null > +++ b/drivers/thermal/thermal_multi.c > @@ -0,0 +1,178 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <linux/err.h> > +#include <linux/export.h> > +#include <linux/of.h> > +#include <linux/slab.h> > +#include <linux/thermal.h> > +#include <linux/types.h> > +#include <linux/string.h> > + > +#include "thermal_core.h" > + > +struct sensor_interface { > + struct thermal_zone_device *tz; > + int coeff; > + > + struct list_head node; > +}; > + > +struct multi_sensor_thermal_zone { > + struct thermal_zone_device *tz; > + struct mutex sensors_lock; > + struct list_head sensors; > + > + struct list_head node; > +}; > + > +static DEFINE_MUTEX(multi_tz_mutex); > +static LIST_HEAD(multi_tz_list); > + > +#define TJ_MAX 120000 > + > +static int multi_sensor_get_temp(struct thermal_zone_device *tz, int *temp) > +{ > + struct multi_sensor_thermal_zone *multi_tz = tz->devdata; > + struct sensor_interface *sensor; > + int accumulated_temp = 0; > + u32 accumulated_coeff; Should we initialize accumulated_coeff to 0 as well? > + int ret; > + > + mutex_lock(&multi_tz->sensors_lock); > + > + if (list_empty(&multi_tz->sensors)) { > + mutex_unlock(&multi_tz->sensors_lock); > + return -ENODEV; > + } > + > + list_for_each_entry(sensor, &multi_tz->sensors, node) { > + ret = thermal_zone_get_temp(sensor->tz, temp); > + if (ret) { > + mutex_unlock(&multi_tz->sensors_lock); > + return ret; > + } > + > + accumulated_temp += *temp * sensor->coeff; > + accumulated_coeff += sensor->coeff; > + } > + > + mutex_unlock(&multi_tz->sensors_lock); > + > + *temp = accumulated_temp / accumulated_coeff; > + return ret; > +} > + > +struct thermal_zone_device_ops multi_sensor_ops = { > + .get_temp = multi_sensor_get_temp, > +}; > + > +int thermal_multi_sensor_validate_coeff(int *coeff, int count, int offset) > +{ > + int max_accumulated_temp = 0; > + int i; > + > + for (i = 0; i < count; i++) { > + max_accumulated_temp += TJ_MAX * coeff[i]; > + if (max_accumulated_temp < 0) > + return -EOVERFLOW; > + } > + > + max_accumulated_temp += offset; > + return max_accumulated_temp < 0 ? -EOVERFLOW : 0; > +} > + > +static struct thermal_zone_device *multi_sensor_tz_alloc(const char *name) > +{ > + struct thermal_zone_device *tz; > + struct thermal_zone_params tzp = {}; > + struct multi_sensor_thermal_zone *multi_tz; > + > + tz = thermal_zone_get_zone_by_name(name); > + if (!IS_ERR(tz)) { > + mutex_unlock(&multi_tz_mutex); > + return tz; > + } > + > + multi_tz = kzalloc(sizeof(*multi_tz), GFP_KERNEL); > + if (!multi_tz) > + return ERR_PTR(-ENOMEM); > + mutex_init(&multi_tz->sensors_lock); > + INIT_LIST_HEAD(&multi_tz->sensors); > + > + tzp.no_hwmon = true; > + tzp.slope = 1; > + tzp.offset = 0; > + > + tz = thermal_tripless_zone_device_register(name, multi_tz, > + &multi_sensor_ops, &tzp); > + if (IS_ERR(tz)) { > + kfree(multi_tz); > + } else { > + multi_tz->tz = tz; > + list_add(&multi_tz->node, &multi_tz_list); > + } > + > + return tz; > +} > + > +int thermal_multi_sensor_register(const char *name, > + struct thermal_zone_device *sensor_tz, int coeff) > +{ > + struct thermal_zone_device *tz; > + struct multi_sensor_thermal_zone *multi_tz; > + struct sensor_interface *sensor; > + > + mutex_lock(&multi_tz_mutex); > + > + tz = multi_sensor_tz_alloc(name); > + if (IS_ERR(tz)) { > + mutex_unlock(&multi_tz_mutex); > + return PTR_ERR(tz); > + } > + multi_tz = tz->devdata; > + > + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); > + if (!sensor) { > + mutex_unlock(&multi_tz_mutex); > + return -ENOMEM; > + } > + > + sensor->tz = sensor_tz; > + sensor->coeff = coeff; > + mutex_lock(&multi_tz->sensors_lock); > + list_add(&sensor->node, &multi_tz->sensors); > + mutex_unlock(&multi_tz->sensors_lock); > + > + thermal_zone_device_enable(tz); > + > + mutex_unlock(&multi_tz_mutex); > + > + return 0; > +} > + > +void thermal_multi_sensor_unregister(struct thermal_zone_device *sensor_tz) > +{ > + struct multi_sensor_thermal_zone *multi_tz; > + struct sensor_interface *sensor, *tmp; > + > + mutex_lock(&multi_tz_mutex); > + list_for_each_entry(multi_tz, &multi_tz_list, node) { > + mutex_lock(&multi_tz->sensors_lock); > + list_for_each_entry_safe(sensor, tmp, &multi_tz->sensors, node) { > + if (sensor->tz == sensor_tz) { > + list_del(&sensor->node); > + kfree(sensor); > + break; > + } > + } > + > + if (list_empty(&multi_tz->sensors)) { > + thermal_zone_device_unregister(multi_tz->tz); > + mutex_unlock(&multi_tz->sensors_lock); > + kfree(multi_tz); > + } else { > + mutex_unlock(&multi_tz->sensors_lock); > + } > + } > + mutex_unlock(&multi_tz_mutex); > +} > -- > 2.41.0 > By the way, may I know why min/max aggregation is dropped in this version? I thought that checking max temperature is the most direct approach to protect the hardware and the users from high temperature. Best regards, Pin-yen
Hi Pin-yen, > Hi Alexandre, > > On Thu, Apr 11, 2024 at 4:34 PM Alexandre Bailon <abailon@baylibre.com> wrote: >> >> This adds support of multi sensors to thermal. >> Currently, this only support the get_temp operation. >> This returns an average temperature of all the sensors. >> If defined, a coefficient is applied to the value read from the sensor >> before computing the average. >> >> Signed-off-by: Alexandre Bailon <abailon@baylibre.com> >> --- >> drivers/thermal/Makefile | 1 + >> drivers/thermal/thermal_core.h | 7 ++ >> drivers/thermal/thermal_multi.c | 178 ++++++++++++++++++++++++++++++++ >> 3 files changed, 186 insertions(+) >> create mode 100644 drivers/thermal/thermal_multi.c >> >> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile >> index c934cab309ae..757289a406f7 100644 >> --- a/drivers/thermal/Makefile >> +++ b/drivers/thermal/Makefile >> @@ -6,6 +6,7 @@ CFLAGS_thermal_core.o := -I$(src) >> obj-$(CONFIG_THERMAL) += thermal_sys.o >> thermal_sys-y += thermal_core.o thermal_sysfs.o >> thermal_sys-y += thermal_trip.o thermal_helpers.o >> +thermal_sys-y += thermal_multi.o >> >> # netlink interface to manage the thermal framework >> thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o >> diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h >> index 0a3b3ec5120b..26e83a5c8298 100644 >> --- a/drivers/thermal/thermal_core.h >> +++ b/drivers/thermal/thermal_core.h >> @@ -138,6 +138,13 @@ ssize_t weight_show(struct device *, struct device_attribute *, char *); >> ssize_t weight_store(struct device *, struct device_attribute *, const char *, >> size_t); >> >> +/* Multi sensors */ >> +int thermal_multi_sensor_validate_coeff(int *coeff, int count, int offset); >> +int thermal_multi_sensor_register(const char *name, >> + struct thermal_zone_device *sensor_tz, int coeff); >> +void thermal_multi_sensor_unregister(struct thermal_zone_device *sensor_tz); >> + >> + >> #ifdef CONFIG_THERMAL_STATISTICS >> void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, >> unsigned long new_state); >> diff --git a/drivers/thermal/thermal_multi.c b/drivers/thermal/thermal_multi.c >> new file mode 100644 >> index 000000000000..a5a4f1f2d594 >> --- /dev/null >> +++ b/drivers/thermal/thermal_multi.c >> @@ -0,0 +1,178 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include <linux/err.h> >> +#include <linux/export.h> >> +#include <linux/of.h> >> +#include <linux/slab.h> >> +#include <linux/thermal.h> >> +#include <linux/types.h> >> +#include <linux/string.h> >> + >> +#include "thermal_core.h" >> + >> +struct sensor_interface { >> + struct thermal_zone_device *tz; >> + int coeff; >> + >> + struct list_head node; >> +}; >> + >> +struct multi_sensor_thermal_zone { >> + struct thermal_zone_device *tz; >> + struct mutex sensors_lock; >> + struct list_head sensors; >> + >> + struct list_head node; >> +}; >> + >> +static DEFINE_MUTEX(multi_tz_mutex); >> +static LIST_HEAD(multi_tz_list); >> + >> +#define TJ_MAX 120000 >> + >> +static int multi_sensor_get_temp(struct thermal_zone_device *tz, int *temp) >> +{ >> + struct multi_sensor_thermal_zone *multi_tz = tz->devdata; >> + struct sensor_interface *sensor; >> + int accumulated_temp = 0; >> + u32 accumulated_coeff; > > Should we initialize accumulated_coeff to 0 as well? Yes, indeed, I will fix it. > >> + int ret; >> + >> + mutex_lock(&multi_tz->sensors_lock); >> + >> + if (list_empty(&multi_tz->sensors)) { >> + mutex_unlock(&multi_tz->sensors_lock); >> + return -ENODEV; >> + } >> + >> + list_for_each_entry(sensor, &multi_tz->sensors, node) { >> + ret = thermal_zone_get_temp(sensor->tz, temp); >> + if (ret) { >> + mutex_unlock(&multi_tz->sensors_lock); >> + return ret; >> + } >> + >> + accumulated_temp += *temp * sensor->coeff; >> + accumulated_coeff += sensor->coeff; >> + } >> + >> + mutex_unlock(&multi_tz->sensors_lock); >> + >> + *temp = accumulated_temp / accumulated_coeff; >> + return ret; >> +} >> + >> +struct thermal_zone_device_ops multi_sensor_ops = { >> + .get_temp = multi_sensor_get_temp, >> +}; >> + >> +int thermal_multi_sensor_validate_coeff(int *coeff, int count, int offset) >> +{ >> + int max_accumulated_temp = 0; >> + int i; >> + >> + for (i = 0; i < count; i++) { >> + max_accumulated_temp += TJ_MAX * coeff[i]; >> + if (max_accumulated_temp < 0) >> + return -EOVERFLOW; >> + } >> + >> + max_accumulated_temp += offset; >> + return max_accumulated_temp < 0 ? -EOVERFLOW : 0; >> +} >> + >> +static struct thermal_zone_device *multi_sensor_tz_alloc(const char *name) >> +{ >> + struct thermal_zone_device *tz; >> + struct thermal_zone_params tzp = {}; >> + struct multi_sensor_thermal_zone *multi_tz; >> + >> + tz = thermal_zone_get_zone_by_name(name); >> + if (!IS_ERR(tz)) { >> + mutex_unlock(&multi_tz_mutex); >> + return tz; >> + } >> + >> + multi_tz = kzalloc(sizeof(*multi_tz), GFP_KERNEL); >> + if (!multi_tz) >> + return ERR_PTR(-ENOMEM); >> + mutex_init(&multi_tz->sensors_lock); >> + INIT_LIST_HEAD(&multi_tz->sensors); >> + >> + tzp.no_hwmon = true; >> + tzp.slope = 1; >> + tzp.offset = 0; >> + >> + tz = thermal_tripless_zone_device_register(name, multi_tz, >> + &multi_sensor_ops, &tzp); >> + if (IS_ERR(tz)) { >> + kfree(multi_tz); >> + } else { >> + multi_tz->tz = tz; >> + list_add(&multi_tz->node, &multi_tz_list); >> + } >> + >> + return tz; >> +} >> + >> +int thermal_multi_sensor_register(const char *name, >> + struct thermal_zone_device *sensor_tz, int coeff) >> +{ >> + struct thermal_zone_device *tz; >> + struct multi_sensor_thermal_zone *multi_tz; >> + struct sensor_interface *sensor; >> + >> + mutex_lock(&multi_tz_mutex); >> + >> + tz = multi_sensor_tz_alloc(name); >> + if (IS_ERR(tz)) { >> + mutex_unlock(&multi_tz_mutex); >> + return PTR_ERR(tz); >> + } >> + multi_tz = tz->devdata; >> + >> + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); >> + if (!sensor) { >> + mutex_unlock(&multi_tz_mutex); >> + return -ENOMEM; >> + } >> + >> + sensor->tz = sensor_tz; >> + sensor->coeff = coeff; >> + mutex_lock(&multi_tz->sensors_lock); >> + list_add(&sensor->node, &multi_tz->sensors); >> + mutex_unlock(&multi_tz->sensors_lock); >> + >> + thermal_zone_device_enable(tz); >> + >> + mutex_unlock(&multi_tz_mutex); >> + >> + return 0; >> +} >> + >> +void thermal_multi_sensor_unregister(struct thermal_zone_device *sensor_tz) >> +{ >> + struct multi_sensor_thermal_zone *multi_tz; >> + struct sensor_interface *sensor, *tmp; >> + >> + mutex_lock(&multi_tz_mutex); >> + list_for_each_entry(multi_tz, &multi_tz_list, node) { >> + mutex_lock(&multi_tz->sensors_lock); >> + list_for_each_entry_safe(sensor, tmp, &multi_tz->sensors, node) { >> + if (sensor->tz == sensor_tz) { >> + list_del(&sensor->node); >> + kfree(sensor); >> + break; >> + } >> + } >> + >> + if (list_empty(&multi_tz->sensors)) { >> + thermal_zone_device_unregister(multi_tz->tz); >> + mutex_unlock(&multi_tz->sensors_lock); >> + kfree(multi_tz); >> + } else { >> + mutex_unlock(&multi_tz->sensors_lock); >> + } >> + } >> + mutex_unlock(&multi_tz_mutex); >> +} >> -- >> 2.41.0 >> > > By the way, may I know why min/max aggregation is dropped in this > version? I thought that checking max temperature is the most direct > approach to protect the hardware and the users from high temperature. You are right. The main reason was device tree bindings. I was expecting getting this accepted first before adding max and min. Thanks, Alexandre > > Best regards, > Pin-yen
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index c934cab309ae..757289a406f7 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -6,6 +6,7 @@ CFLAGS_thermal_core.o := -I$(src) obj-$(CONFIG_THERMAL) += thermal_sys.o thermal_sys-y += thermal_core.o thermal_sysfs.o thermal_sys-y += thermal_trip.o thermal_helpers.o +thermal_sys-y += thermal_multi.o # netlink interface to manage the thermal framework thermal_sys-$(CONFIG_THERMAL_NETLINK) += thermal_netlink.o diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 0a3b3ec5120b..26e83a5c8298 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -138,6 +138,13 @@ ssize_t weight_show(struct device *, struct device_attribute *, char *); ssize_t weight_store(struct device *, struct device_attribute *, const char *, size_t); +/* Multi sensors */ +int thermal_multi_sensor_validate_coeff(int *coeff, int count, int offset); +int thermal_multi_sensor_register(const char *name, + struct thermal_zone_device *sensor_tz, int coeff); +void thermal_multi_sensor_unregister(struct thermal_zone_device *sensor_tz); + + #ifdef CONFIG_THERMAL_STATISTICS void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, unsigned long new_state); diff --git a/drivers/thermal/thermal_multi.c b/drivers/thermal/thermal_multi.c new file mode 100644 index 000000000000..a5a4f1f2d594 --- /dev/null +++ b/drivers/thermal/thermal_multi.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/err.h> +#include <linux/export.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/thermal.h> +#include <linux/types.h> +#include <linux/string.h> + +#include "thermal_core.h" + +struct sensor_interface { + struct thermal_zone_device *tz; + int coeff; + + struct list_head node; +}; + +struct multi_sensor_thermal_zone { + struct thermal_zone_device *tz; + struct mutex sensors_lock; + struct list_head sensors; + + struct list_head node; +}; + +static DEFINE_MUTEX(multi_tz_mutex); +static LIST_HEAD(multi_tz_list); + +#define TJ_MAX 120000 + +static int multi_sensor_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct multi_sensor_thermal_zone *multi_tz = tz->devdata; + struct sensor_interface *sensor; + int accumulated_temp = 0; + u32 accumulated_coeff; + int ret; + + mutex_lock(&multi_tz->sensors_lock); + + if (list_empty(&multi_tz->sensors)) { + mutex_unlock(&multi_tz->sensors_lock); + return -ENODEV; + } + + list_for_each_entry(sensor, &multi_tz->sensors, node) { + ret = thermal_zone_get_temp(sensor->tz, temp); + if (ret) { + mutex_unlock(&multi_tz->sensors_lock); + return ret; + } + + accumulated_temp += *temp * sensor->coeff; + accumulated_coeff += sensor->coeff; + } + + mutex_unlock(&multi_tz->sensors_lock); + + *temp = accumulated_temp / accumulated_coeff; + return ret; +} + +struct thermal_zone_device_ops multi_sensor_ops = { + .get_temp = multi_sensor_get_temp, +}; + +int thermal_multi_sensor_validate_coeff(int *coeff, int count, int offset) +{ + int max_accumulated_temp = 0; + int i; + + for (i = 0; i < count; i++) { + max_accumulated_temp += TJ_MAX * coeff[i]; + if (max_accumulated_temp < 0) + return -EOVERFLOW; + } + + max_accumulated_temp += offset; + return max_accumulated_temp < 0 ? -EOVERFLOW : 0; +} + +static struct thermal_zone_device *multi_sensor_tz_alloc(const char *name) +{ + struct thermal_zone_device *tz; + struct thermal_zone_params tzp = {}; + struct multi_sensor_thermal_zone *multi_tz; + + tz = thermal_zone_get_zone_by_name(name); + if (!IS_ERR(tz)) { + mutex_unlock(&multi_tz_mutex); + return tz; + } + + multi_tz = kzalloc(sizeof(*multi_tz), GFP_KERNEL); + if (!multi_tz) + return ERR_PTR(-ENOMEM); + mutex_init(&multi_tz->sensors_lock); + INIT_LIST_HEAD(&multi_tz->sensors); + + tzp.no_hwmon = true; + tzp.slope = 1; + tzp.offset = 0; + + tz = thermal_tripless_zone_device_register(name, multi_tz, + &multi_sensor_ops, &tzp); + if (IS_ERR(tz)) { + kfree(multi_tz); + } else { + multi_tz->tz = tz; + list_add(&multi_tz->node, &multi_tz_list); + } + + return tz; +} + +int thermal_multi_sensor_register(const char *name, + struct thermal_zone_device *sensor_tz, int coeff) +{ + struct thermal_zone_device *tz; + struct multi_sensor_thermal_zone *multi_tz; + struct sensor_interface *sensor; + + mutex_lock(&multi_tz_mutex); + + tz = multi_sensor_tz_alloc(name); + if (IS_ERR(tz)) { + mutex_unlock(&multi_tz_mutex); + return PTR_ERR(tz); + } + multi_tz = tz->devdata; + + sensor = kzalloc(sizeof(*sensor), GFP_KERNEL); + if (!sensor) { + mutex_unlock(&multi_tz_mutex); + return -ENOMEM; + } + + sensor->tz = sensor_tz; + sensor->coeff = coeff; + mutex_lock(&multi_tz->sensors_lock); + list_add(&sensor->node, &multi_tz->sensors); + mutex_unlock(&multi_tz->sensors_lock); + + thermal_zone_device_enable(tz); + + mutex_unlock(&multi_tz_mutex); + + return 0; +} + +void thermal_multi_sensor_unregister(struct thermal_zone_device *sensor_tz) +{ + struct multi_sensor_thermal_zone *multi_tz; + struct sensor_interface *sensor, *tmp; + + mutex_lock(&multi_tz_mutex); + list_for_each_entry(multi_tz, &multi_tz_list, node) { + mutex_lock(&multi_tz->sensors_lock); + list_for_each_entry_safe(sensor, tmp, &multi_tz->sensors, node) { + if (sensor->tz == sensor_tz) { + list_del(&sensor->node); + kfree(sensor); + break; + } + } + + if (list_empty(&multi_tz->sensors)) { + thermal_zone_device_unregister(multi_tz->tz); + mutex_unlock(&multi_tz->sensors_lock); + kfree(multi_tz); + } else { + mutex_unlock(&multi_tz->sensors_lock); + } + } + mutex_unlock(&multi_tz_mutex); +}
This adds support of multi sensors to thermal. Currently, this only support the get_temp operation. This returns an average temperature of all the sensors. If defined, a coefficient is applied to the value read from the sensor before computing the average. Signed-off-by: Alexandre Bailon <abailon@baylibre.com> --- drivers/thermal/Makefile | 1 + drivers/thermal/thermal_core.h | 7 ++ drivers/thermal/thermal_multi.c | 178 ++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 drivers/thermal/thermal_multi.c