diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 3353ab7b3990..cbb4ab4d7d40 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -13,6 +13,10 @@ #include #include #include +#include + +#define CREATE_TRACE_POINTS +#include #include "thermal_core.h" @@ -35,6 +39,23 @@ struct __thermal_bind_params { unsigned long max; }; +/** + * struct __sensor_param - Holds individual sensor data + * @sensor_data: sensor driver private data passed as input argument + * @ops: sensor driver ops + * @trip_high: last trip high value programmed in the sensor driver + * @trip_low: last trip low value programmed in the sensor driver + * @lock: mutex lock acquired before updating the trip temperatures + * @first_tz: list head pointing the first thermal zone + */ +struct __sensor_param { + void *sensor_data; + const struct thermal_zone_of_device_ops *ops; + int trip_high, trip_low; + struct mutex lock; + struct list_head first_tz; +}; + /** * struct __thermal_zone - internal representation of a thermal zone * @mode: current thermal zone device mode (enabled/disabled) @@ -42,12 +63,14 @@ struct __thermal_bind_params { * @polling_delay: zone polling interval * @slope: slope of the temperature adjustment curve * @offset: offset of the temperature adjustment curve + * @default_disable: Keep the thermal zone disabled by default + * @tzd: thermal zone device pointer for this sensor * @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 + * @senps: sensor related parameters + * @list: sibling thermal zone */ struct __thermal_zone { @@ -56,6 +79,8 @@ struct __thermal_zone { int polling_delay; int slope; int offset; + struct thermal_zone_device *tzd; + bool default_disable; /* trip data */ int ntrips; @@ -65,22 +90,101 @@ struct __thermal_zone { int num_tbps; struct __thermal_bind_params *tbps; + struct list_head list; /* sensor interface */ - void *sensor_data; - const struct thermal_zone_of_device_ops *ops; + struct __sensor_param *senps; }; +/** + * struct virtual_sensor - internal representation of a virtual thermal zone + * @num_sensors - number of sensors this virtual sensor will reference to + * estimate temperature + * @tz - Array of thermal zones of the sensors this virtual sensor will use + * to estimate temperature + * @virt_tz - Virtual thermal zone pointer + * @logic - aggregation logic to be used to estimate the temperature + * @last_reading - last estimated temperature + * @coefficients - array of coefficients to be used for weighted aggregation + * logic + * @avg_offset - offset value to be used for the weighted aggregation logic + * @avg_denominator - denominator value to be used for the weighted aggregation + * logic + */ +struct virtual_sensor { + int num_sensors; + struct thermal_zone_device *tz[THERMAL_MAX_VIRT_SENSORS]; + struct thermal_zone_device *virt_tz; + enum aggregation_logic logic; + int last_reading; + int coefficients[THERMAL_MAX_VIRT_SENSORS]; + int avg_offset; + int avg_denominator; +}; + + /*** DT thermal zone device callbacks ***/ +static int virt_sensor_read_temp(void *data, int *val) +{ + struct virtual_sensor *sens = data; + int idx, temp = 0, ret = 0; + + for (idx = 0; idx < sens->num_sensors; idx++) { + int sens_temp = 0; + + ret = thermal_zone_get_temp(sens->tz[idx], &sens_temp); + if (ret) { + pr_err("virt zone: sensor[%s] read error:%d\n", + sens->tz[idx]->type, ret); + return ret; + } + switch (sens->logic) { + case VIRT_COUNT_THRESHOLD: + if ((sens->coefficients[idx] < 0 && + sens_temp < -sens->coefficients[idx]) || + (sens->coefficients[idx] > 0 && + sens_temp >= sens->coefficients[idx])) + temp += 1; + break; + case VIRT_WEIGHTED_AVG: + temp += sens_temp * sens->coefficients[idx]; + if (idx == (sens->num_sensors - 1)) + temp = (temp + sens->avg_offset) + / sens->avg_denominator; + break; + case VIRT_MAXIMUM: + if (idx == 0) + temp = INT_MIN; + if (sens_temp > temp) + temp = sens_temp; + break; + case VIRT_MINIMUM: + if (idx == 0) + temp = INT_MAX; + if (sens_temp < temp) + temp = sens_temp; + break; + default: + break; + } + trace_virtual_temperature(sens->virt_tz, sens->tz[idx], + sens_temp, temp); + } + + sens->last_reading = *val = temp; + + return 0; +} + static int of_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct __thermal_zone *data = tz->devdata; - if (!data->ops->get_temp) + if (!data->senps || !data->senps->ops->get_temp) return -EINVAL; - return data->ops->get_temp(data->sensor_data, temp); + return data->senps->ops->get_temp(data->senps->sensor_data, temp); } static int of_thermal_set_trips(struct thermal_zone_device *tz, @@ -88,10 +192,10 @@ static int of_thermal_set_trips(struct thermal_zone_device *tz, { struct __thermal_zone *data = tz->devdata; - if (!data->ops || !data->ops->set_trips) + if (!data->senps || !data->senps->ops->set_trips) return -EINVAL; - return data->ops->set_trips(data->sensor_data, low, high); + return data->senps->ops->set_trips(data->senps->sensor_data, low, high); } /** @@ -174,7 +278,10 @@ static int of_thermal_set_emul_temp(struct thermal_zone_device *tz, { struct __thermal_zone *data = tz->devdata; - return data->ops->set_emul_temp(data->sensor_data, temp); + if (!data->senps || !data->senps->ops->set_emul_temp) + return -EINVAL; + + return data->senps->ops->set_emul_temp(data->senps->sensor_data, temp); } static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip, @@ -182,10 +289,11 @@ static int of_thermal_get_trend(struct thermal_zone_device *tz, int trip, { struct __thermal_zone *data = tz->devdata; - if (!data->ops->get_trend) + if (!data->senps || !data->senps->ops->get_trend) return -EINVAL; - return data->ops->get_trend(data->sensor_data, trip, trend); + return data->senps->ops->get_trend(data->senps->sensor_data, + trip, trend); } static int of_thermal_bind(struct thermal_zone_device *thermal, @@ -310,10 +418,11 @@ static int of_thermal_set_trip_temp(struct thermal_zone_device *tz, int trip, if (trip >= data->ntrips || trip < 0) return -EDOM; - if (data->ops->set_trip_temp) { + if (data->senps && data->senps->ops->set_trip_temp) { int ret; - ret = data->ops->set_trip_temp(data->sensor_data, trip, temp); + ret = data->senps->ops->set_trip_temp(data->senps->sensor_data, + trip, temp); if (ret) return ret; } @@ -381,12 +490,16 @@ static struct thermal_zone_device_ops of_thermal_ops = { .unbind = of_thermal_unbind, }; +static struct thermal_zone_of_device_ops of_virt_ops = { + .get_temp = virt_sensor_read_temp, +}; + /*** 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 device_node *sensor, + struct __sensor_param *sens_param) { struct thermal_zone_device *tzd; struct __thermal_zone *tz; @@ -397,12 +510,11 @@ thermal_zone_of_add_sensor(struct device_node *zone, tz = tzd->devdata; - if (!ops) + if (!sens_param->ops) return ERR_PTR(-EINVAL); mutex_lock(&tzd->lock); - tz->ops = ops; - tz->sensor_data = data; + tz->senps = sens_param; tzd->ops->get_temp = of_thermal_get_temp; tzd->ops->get_trend = of_thermal_get_trend; @@ -411,10 +523,10 @@ thermal_zone_of_add_sensor(struct device_node *zone, * The thermal zone core will calculate the window if they have set the * optional set_trips pointer. */ - if (ops->set_trips) + if (sens_param->ops->set_trips) tzd->ops->set_trips = of_thermal_set_trips; - if (ops->set_emul_temp) + if (sens_param->ops->set_emul_temp) tzd->ops->set_emul_temp = of_thermal_set_emul_temp; mutex_unlock(&tzd->lock); @@ -460,6 +572,7 @@ thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data, { struct device_node *np, *child, *sensor_np; struct thermal_zone_device *tzd = ERR_PTR(-ENODEV); + struct __sensor_param *sens_param = NULL; np = of_find_node_by_name(NULL, "thermal-zones"); if (!np) @@ -470,6 +583,13 @@ thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data, return ERR_PTR(-EINVAL); } + sens_param = kzalloc(sizeof(*sens_param), GFP_KERNEL); + if (!sens_param) { + of_node_put(np); + return ERR_PTR(-ENOMEM); + } + sens_param->sensor_data = data; + sens_param->ops = ops; sensor_np = of_node_get(dev->of_node); for_each_available_child_of_node(np, child) { @@ -494,7 +614,7 @@ thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data, if (sensor_specs.np == sensor_np && id == sensor_id) { tzd = thermal_zone_of_add_sensor(child, sensor_np, - data, ops); + sens_param); if (!IS_ERR(tzd)) tzd->ops->set_mode(tzd, THERMAL_DEVICE_ENABLED); @@ -508,6 +628,8 @@ exit: of_node_put(sensor_np); of_node_put(np); + if (tzd == ERR_PTR(-ENODEV)) + kfree(sens_param); return tzd; } EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_register); @@ -546,8 +668,8 @@ void thermal_zone_of_sensor_unregister(struct device *dev, tzd->ops->get_trend = NULL; tzd->ops->set_emul_temp = NULL; - tz->ops = NULL; - tz->sensor_data = NULL; + kfree(tz->senps); + tz->senps = NULL; mutex_unlock(&tzd->lock); } EXPORT_SYMBOL_GPL(thermal_zone_of_sensor_unregister); @@ -633,6 +755,138 @@ void devm_thermal_zone_of_sensor_unregister(struct device *dev, } EXPORT_SYMBOL_GPL(devm_thermal_zone_of_sensor_unregister); +/** + * devm_thermal_of_virtual_sensor_register - Register a virtual sensor. + * Three types of virtual sensors are supported. + * 1. Weighted aggregation type: + * Virtual sensor of this type calculates the weighted aggregation + * of sensor temperatures using the below formula, + * temp = (sensor_1_temp * coeff_1 + ... + sensor_n_temp * coeff_n) + * + avg_offset / avg_denominator + * So the sensor drivers has to specify n+2 coefficients. + * 2. Maximum type: + * Virtual sensors of this type will report the maximum of all + * sensor temperatures. + * 3. Minimum type: + * Virtual sensors of this type will report the minimum of all + * sensor temperatures. + * + * @input arguments: + * @dev: Virtual sensor driver device pointer. + * @sensor_data: Virtual sensor data supported for the device. + * + * @return: Returns a virtual thermal zone pointer. Returns error if thermal + * zone is not created. Returns -EAGAIN, if the sensor that is required for + * this virtual sensor temperature estimation is not registered yet. The + * sensor driver can try again later. + */ +struct thermal_zone_device *devm_thermal_of_virtual_sensor_register( + struct device *dev, + const struct virtual_sensor_data *sensor_data) +{ + int sens_idx = 0; + struct virtual_sensor *sens; + struct __thermal_zone *tz; + struct thermal_zone_device **ptr; + struct thermal_zone_device *tzd; + struct __sensor_param *sens_param = NULL; + enum thermal_device_mode mode; + + if (!dev || !sensor_data) + return ERR_PTR(-EINVAL); + + tzd = thermal_zone_get_zone_by_name( + sensor_data->virt_zone_name); + if (IS_ERR(tzd)) { + dev_dbg(dev, "sens:%s not available err: %ld\n", + sensor_data->virt_zone_name, + PTR_ERR(tzd)); + return tzd; + } + + mutex_lock(&tzd->lock); + /* + * Check if the virtual zone is registered and enabled. + * If so return the registered thermal zone. + */ + tzd->ops->get_mode(tzd, &mode); + mutex_unlock(&tzd->lock); + if (mode == THERMAL_DEVICE_ENABLED) + return tzd; + + sens = devm_kzalloc(dev, sizeof(*sens), GFP_KERNEL); + if (!sens) + return ERR_PTR(-ENOMEM); + + sens->virt_tz = tzd; + sens->logic = sensor_data->logic; + sens->num_sensors = sensor_data->num_sensors; + if ((sens->logic == VIRT_WEIGHTED_AVG) || + (sens->logic == VIRT_COUNT_THRESHOLD)) { + int coeff_ct = sensor_data->coefficient_ct; + + /* + * For weighted aggregation, sensor drivers has to specify + * n+2 coefficients. + */ + if (coeff_ct != sens->num_sensors) { + dev_err(dev, "sens:%s invalid coefficient\n", + sensor_data->virt_zone_name); + return ERR_PTR(-EINVAL); + } + memcpy(sens->coefficients, sensor_data->coefficients, + coeff_ct * sizeof(*sens->coefficients)); + sens->avg_offset = sensor_data->avg_offset; + sens->avg_denominator = sensor_data->avg_denominator; + } + + for (sens_idx = 0; sens_idx < sens->num_sensors; sens_idx++) { + sens->tz[sens_idx] = thermal_zone_get_zone_by_name( + sensor_data->sensor_names[sens_idx]); + if (IS_ERR(sens->tz[sens_idx])) { + dev_err(dev, "sens:%s sensor[%s] fetch err:%ld\n", + sensor_data->virt_zone_name, + sensor_data->sensor_names[sens_idx], + PTR_ERR(sens->tz[sens_idx])); + break; + } + } + if (sens->num_sensors != sens_idx) + return ERR_PTR(-EAGAIN); + + sens_param = kzalloc(sizeof(*sens_param), GFP_KERNEL); + if (!sens_param) + return ERR_PTR(-ENOMEM); + sens_param->sensor_data = sens; + sens_param->ops = &of_virt_ops; + INIT_LIST_HEAD(&sens_param->first_tz); + sens_param->trip_high = INT_MAX; + sens_param->trip_low = INT_MIN; + mutex_init(&sens_param->lock); + + mutex_lock(&tzd->lock); + tz = tzd->devdata; + tz->senps = sens_param; + tzd->ops->get_temp = of_thermal_get_temp; + list_add_tail(&tz->list, &sens_param->first_tz); + mutex_unlock(&tzd->lock); + + ptr = devres_alloc(devm_thermal_zone_of_sensor_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + *ptr = tzd; + devres_add(dev, ptr); + + if (!tz->default_disable) + tzd->ops->set_mode(tzd, THERMAL_DEVICE_ENABLED); + + return tzd; +} +EXPORT_SYMBOL(devm_thermal_of_virtual_sensor_register); + + /*** functions parsing device tree nodes ***/ /** diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 90a486c562e6..a532dba27e15 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -29,6 +29,9 @@ /* Default weight of a bound cooling device */ #define THERMAL_WEIGHT_DEFAULT 0 +/* Max sensors that can be used for a single virtual thermalzone */ +#define THERMAL_MAX_VIRT_SENSORS 10 + /* use value, which < 0K, to indicate an invalid/uninitialized temperature */ #define THERMAL_TEMP_INVALID -274000 @@ -381,6 +384,40 @@ struct thermal_trip { enum thermal_trip_type type; }; +/* Different aggregation logic supported for virtual sensors */ +enum aggregation_logic { + VIRT_WEIGHTED_AVG, + VIRT_MAXIMUM, + VIRT_MINIMUM, + VIRT_COUNT_THRESHOLD, + VIRT_AGGREGATION_NR, +}; + +/* + * struct virtual_sensor_data - Data structure used to provide + * information about the virtual zone. + * @virt_zone_name - Virtual thermal zone name + * @num_sensors - Number of sensors this virtual zone uses to compute + * temperature + * @sensor_names - Array of sensor names + * @logic - Temperature aggregation logic to be used + * @coefficients - Coefficients to be used for weighted average logic + * @coefficient_ct - number of coefficients provided as input + * @avg_offset - offset value to be used for the weighted aggregation logic + * @avg_denominator - denominator value to be used for the weighted aggregation + * logic + */ +struct virtual_sensor_data { + int num_sensors; + char virt_zone_name[THERMAL_NAME_LENGTH]; + char *sensor_names[THERMAL_MAX_VIRT_SENSORS]; + enum aggregation_logic logic; + int coefficients[THERMAL_MAX_VIRT_SENSORS]; + int coefficient_ct; + int avg_offset; + int avg_denominator; +}; + /* Function declarations */ #ifdef CONFIG_THERMAL_OF struct thermal_zone_device * @@ -393,6 +430,9 @@ struct thermal_zone_device *devm_thermal_zone_of_sensor_register( const struct thermal_zone_of_device_ops *ops); void devm_thermal_zone_of_sensor_unregister(struct device *dev, struct thermal_zone_device *tz); +struct thermal_zone_device *devm_thermal_of_virtual_sensor_register( + struct device *dev, + const struct virtual_sensor_data *sensor_data); #else static inline struct thermal_zone_device * thermal_zone_of_sensor_register(struct device *dev, int id, void *data, @@ -420,6 +460,14 @@ void devm_thermal_zone_of_sensor_unregister(struct device *dev, { } +static inline +struct thermal_zone_device *devm_thermal_of_virtual_sensor_register( + struct device *dev, + const struct virtual_sensor_data *sensor_data) +{ + return ERR_PTR(-ENODEV); +} + #endif #if IS_ENABLED(CONFIG_THERMAL) diff --git a/include/trace/events/thermal_virtual.h b/include/trace/events/thermal_virtual.h new file mode 100644 index 000000000000..76173eac001f --- /dev/null +++ b/include/trace/events/thermal_virtual.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM thermal_virtual + +#if !defined(_TRACE_VIRTUAL_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_VIRTUAL_H + +#include +#include + +TRACE_EVENT(virtual_temperature, + + TP_PROTO(struct thermal_zone_device *virt_tz, + struct thermal_zone_device *tz, int sens_temp, + int est_temp), + + TP_ARGS(virt_tz, tz, sens_temp, est_temp), + + TP_STRUCT__entry( + __string(virt_zone, virt_tz->type) + __string(therm_zone, tz->type) + __field(int, sens_temp) + __field(int, est_temp) + ), + + TP_fast_assign( + __assign_str(virt_zone, virt_tz->type); + __assign_str(therm_zone, tz->type); + __entry->sens_temp = sens_temp; + __entry->est_temp = est_temp; + ), + + TP_printk("virt_zone=%s zone=%s temp=%d virtual zone estimated temp=%d", + __get_str(virt_zone), __get_str(therm_zone), + __entry->sens_temp, + __entry->est_temp) +); + +#endif /* _TRACE_VIRTUAL_H */ + +/* This part must be outside protection */ +#include