diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/cpufreq/Kconfig | 2 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq-cpu0.c | 16 | ||||
-rw-r--r-- | drivers/hwmon/lm75.c | 35 | ||||
-rw-r--r-- | drivers/hwmon/tmp102.c | 19 | ||||
-rw-r--r-- | drivers/thermal/Kconfig | 14 | ||||
-rw-r--r-- | drivers/thermal/Makefile | 1 | ||||
-rw-r--r-- | drivers/thermal/cpu_cooling.c | 56 | ||||
-rw-r--r-- | drivers/thermal/of-thermal.c | 849 | ||||
-rw-r--r-- | drivers/thermal/thermal_core.c | 77 | ||||
-rw-r--r-- | drivers/thermal/thermal_core.h | 9 | ||||
-rw-r--r-- | drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 77 |
11 files changed, 1121 insertions, 34 deletions
diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 38093e27237..6b8cde5f98a 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -181,7 +181,7 @@ config CPU_FREQ_GOV_CONSERVATIVE config GENERIC_CPUFREQ_CPU0 tristate "Generic CPU0 cpufreq driver" - depends on HAVE_CLK && REGULATOR && PM_OPP && OF + depends on HAVE_CLK && REGULATOR && PM_OPP && OF && THERMAL && CPU_THERMAL help This adds a generic cpufreq driver for CPU0 frequency management. It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) diff --git a/drivers/cpufreq/cpufreq-cpu0.c b/drivers/cpufreq/cpufreq-cpu0.c index d4585ce2346..91c7bb67bc7 100644 --- a/drivers/cpufreq/cpufreq-cpu0.c +++ b/drivers/cpufreq/cpufreq-cpu0.c @@ -13,7 +13,9 @@ #include <linux/clk.h> #include <linux/cpu.h> +#include <linux/cpu_cooling.h> #include <linux/cpufreq.h> +#include <linux/cpumask.h> #include <linux/err.h> #include <linux/module.h> #include <linux/of.h> @@ -21,6 +23,7 @@ #include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <linux/slab.h> +#include <linux/thermal.h> static unsigned int transition_latency; static unsigned int voltage_tolerance; /* in percentage */ @@ -29,6 +32,7 @@ static struct device *cpu_dev; static struct clk *cpu_clk; static struct regulator *cpu_reg; static struct cpufreq_frequency_table *freq_table; +static struct thermal_cooling_device *cdev; static unsigned int cpu0_get_speed(unsigned int cpu) { @@ -201,6 +205,17 @@ static int cpu0_cpufreq_probe(struct platform_device *pdev) goto out_free_table; } + /* + * For now, just loading the cooling device; + * thermal DT code takes care of matching them. + */ + if (of_find_property(np, "#cooling-cells", NULL)) { + cdev = of_cpufreq_cooling_register(np, cpu_present_mask); + if (IS_ERR(cdev)) + pr_err("running cpufreq without cooling device: %ld\n", + PTR_ERR(cdev)); + } + of_node_put(np); return 0; @@ -213,6 +228,7 @@ out_put_node: static int cpu0_cpufreq_remove(struct platform_device *pdev) { + cpufreq_cooling_unregister(cdev); cpufreq_unregister_driver(&cpu0_cpufreq_driver); dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 7e3ef134f1d..84a55eacd90 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -27,6 +27,8 @@ #include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/mutex.h> +#include <linux/of.h> +#include <linux/thermal.h> #include "lm75.h" @@ -71,6 +73,7 @@ static const u8 LM75_REG_TEMP[3] = { /* Each client has this additional data */ struct lm75_data { struct device *hwmon_dev; + struct thermal_zone_device *tz; struct mutex update_lock; u8 orig_conf; u8 resolution; /* In bits, between 9 and 12 */ @@ -91,22 +94,36 @@ static struct lm75_data *lm75_update_device(struct device *dev); /*-----------------------------------------------------------------------*/ +static inline long lm75_reg_to_mc(s16 temp, u8 resolution) +{ + return ((temp >> (16 - resolution)) * 1000) >> (resolution - 8); +} + /* sysfs attributes for hwmon */ +static int lm75_read_temp(void *dev, long *temp) +{ + struct lm75_data *data = lm75_update_device(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + *temp = lm75_reg_to_mc(data->temp[0], data->resolution); + + return 0; +} + static ssize_t show_temp(struct device *dev, struct device_attribute *da, char *buf) { struct sensor_device_attribute *attr = to_sensor_dev_attr(da); struct lm75_data *data = lm75_update_device(dev); - long temp; if (IS_ERR(data)) return PTR_ERR(data); - temp = ((data->temp[attr->index] >> (16 - data->resolution)) * 1000) - >> (data->resolution - 8); - - return sprintf(buf, "%ld\n", temp); + return sprintf(buf, "%ld\n", lm75_reg_to_mc(data->temp[attr->index], + data->resolution)); } static ssize_t set_temp(struct device *dev, struct device_attribute *da, @@ -273,6 +290,13 @@ lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) goto exit_remove; } + data->tz = thermal_zone_of_sensor_register(&client->dev, + 0, + &client->dev, + lm75_read_temp, NULL); + if (IS_ERR(data->tz)) + data->tz = NULL; + dev_info(&client->dev, "%s: sensor '%s'\n", dev_name(data->hwmon_dev), client->name); @@ -287,6 +311,7 @@ static int lm75_remove(struct i2c_client *client) { struct lm75_data *data = i2c_get_clientdata(client); + thermal_zone_of_sensor_unregister(&client->dev, data->tz); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&client->dev.kobj, &lm75_group); lm75_write_value(client, LM75_REG_CONF, data->orig_conf); diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index d7b47abf37f..6748b4583e7 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -27,6 +27,8 @@ #include <linux/mutex.h> #include <linux/device.h> #include <linux/jiffies.h> +#include <linux/thermal.h> +#include <linux/of.h> #define DRIVER_NAME "tmp102" @@ -50,6 +52,7 @@ struct tmp102 { struct device *hwmon_dev; + struct thermal_zone_device *tz; struct mutex lock; u16 config_orig; unsigned long last_update; @@ -93,6 +96,15 @@ static struct tmp102 *tmp102_update_device(struct i2c_client *client) return tmp102; } +static int tmp102_read_temp(void *dev, long *temp) +{ + struct tmp102 *tmp102 = tmp102_update_device(to_i2c_client(dev)); + + *temp = tmp102->temp[0]; + + return 0; +} + static ssize_t tmp102_show_temp(struct device *dev, struct device_attribute *attr, char *buf) @@ -204,6 +216,12 @@ static int tmp102_probe(struct i2c_client *client, goto fail_remove_sysfs; } + tmp102->tz = thermal_zone_of_sensor_register(&client->dev, 0, + &client->dev, + tmp102_read_temp, NULL); + if (IS_ERR(tmp102->tz)) + tmp102->tz = NULL; + dev_info(&client->dev, "initialized\n"); return 0; @@ -220,6 +238,7 @@ static int tmp102_remove(struct i2c_client *client) { struct tmp102 *tmp102 = i2c_get_clientdata(client); + thermal_zone_of_sensor_unregister(&client->dev, tmp102->tz); hwmon_device_unregister(tmp102->hwmon_dev); sysfs_remove_group(&client->dev.kobj, &tmp102_attr_group); diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index f35a1f75b15..3feb5377fbf 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -29,6 +29,19 @@ config THERMAL_HWMON Say 'Y' here if you want all thermal sensors to have hwmon sysfs interface too. +config THERMAL_OF + bool + prompt "APIs to parse thermal data out of device tree" + depends on OF + default y + help + This options provides helpers to add the support to + read and parse thermal data definitions out of the + device tree blob. + + Say 'Y' here if you need to build thermal infrastructure + based on device tree. + choice prompt "Default Thermal governor" default THERMAL_DEFAULT_GOV_STEP_WISE @@ -79,6 +92,7 @@ config THERMAL_GOV_USER_SPACE config CPU_THERMAL bool "generic cpu cooling support" depends on CPU_FREQ + depends on THERMAL_OF help This implements the generic cpu cooling mechanism through frequency reduction. An ACPI version of this already exists diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 584b36319d5..4b03956b929 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -7,6 +7,7 @@ thermal_sys-y += thermal_core.o # interface to/from other layers providing sensors 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 diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c index 02a46f23d14..a6cb5531403 100644 --- a/drivers/thermal/cpu_cooling.c +++ b/drivers/thermal/cpu_cooling.c @@ -417,18 +417,21 @@ static struct notifier_block thermal_cpufreq_notifier_block = { }; /** - * cpufreq_cooling_register - function to create cpufreq cooling device. + * __cpufreq_cooling_register - helper function to create cpufreq cooling device + * @np: a valid struct device_node to the cooling device device tree node * @clip_cpus: cpumask of cpus where the frequency constraints will happen. * * This interface function registers the cpufreq cooling device with the name * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq - * cooling devices. + * cooling devices. It also gives the opportunity to link the cooling device + * with a device tree node, in order to bind it via the thermal DT code. * * Return: a valid struct thermal_cooling_device pointer on success, * on failure, it returns a corresponding ERR_PTR(). */ -struct thermal_cooling_device * -cpufreq_cooling_register(const struct cpumask *clip_cpus) +static struct thermal_cooling_device * +__cpufreq_cooling_register(struct device_node *np, + const struct cpumask *clip_cpus) { struct thermal_cooling_device *cool_dev; struct cpufreq_cooling_device *cpufreq_dev = NULL; @@ -467,8 +470,8 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus) snprintf(dev_name, sizeof(dev_name), "thermal-cpufreq-%d", cpufreq_dev->id); - cool_dev = thermal_cooling_device_register(dev_name, cpufreq_dev, - &cpufreq_cooling_ops); + cool_dev = thermal_of_cooling_device_register(np, dev_name, cpufreq_dev, + &cpufreq_cooling_ops); if (IS_ERR(cool_dev)) { release_idr(&cpufreq_idr, cpufreq_dev->id); kfree(cpufreq_dev); @@ -488,9 +491,50 @@ cpufreq_cooling_register(const struct cpumask *clip_cpus) return cool_dev; } + +/** + * cpufreq_cooling_register - function to create cpufreq cooling device. + * @clip_cpus: cpumask of cpus where the frequency constraints will happen. + * + * This interface function registers the cpufreq cooling device with the name + * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq + * cooling devices. + * + * Return: a valid struct thermal_cooling_device pointer on success, + * on failure, it returns a corresponding ERR_PTR(). + */ +struct thermal_cooling_device * +cpufreq_cooling_register(const struct cpumask *clip_cpus) +{ + return __cpufreq_cooling_register(NULL, clip_cpus); +} EXPORT_SYMBOL_GPL(cpufreq_cooling_register); /** + * of_cpufreq_cooling_register - function to create cpufreq cooling device. + * @np: a valid struct device_node to the cooling device device tree node + * @clip_cpus: cpumask of cpus where the frequency constraints will happen. + * + * This interface function registers the cpufreq cooling device with the name + * "thermal-cpufreq-%x". This api can support multiple instances of cpufreq + * cooling devices. Using this API, the cpufreq cooling device will be + * linked to the device tree node provided. + * + * Return: a valid struct thermal_cooling_device pointer on success, + * on failure, it returns a corresponding ERR_PTR(). + */ +struct thermal_cooling_device * +of_cpufreq_cooling_register(struct device_node *np, + const struct cpumask *clip_cpus) +{ + if (!np) + return ERR_PTR(-EINVAL); + + return __cpufreq_cooling_register(np, clip_cpus); +} +EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register); + +/** * cpufreq_cooling_unregister - function to remove cpufreq cooling device. * @cdev: thermal cooling device pointer. * diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c new file mode 100644 index 00000000000..04b1be7fa01 --- /dev/null +++ b/drivers/thermal/of-thermal.c @@ -0,0 +1,849 @@ +/* + * of-thermal.c - Generic Thermal Management device tree support. + * + * Copyright (C) 2013 Texas Instruments + * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> + * + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/thermal.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/err.h> +#include <linux/export.h> +#include <linux/string.h> + +#include "thermal_core.h" + +/*** Private data structures to represent thermal device tree data ***/ + +/** + * 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; + unsigned long int temperature; + unsigned long int hysteresis; + enum thermal_trip_type type; +}; + +/** + * struct __thermal_bind_param - a match between trip and cooling device + * @cooling_device: a pointer to identify the referred cooling device + * @trip_id: the trip point index + * @usage: the percentage (from 0 to 100) of cooling contribution + * @min: minimum cooling state used at this trip point + * @max: maximum cooling state used at this trip point + */ + +struct __thermal_bind_params { + struct device_node *cooling_device; + unsigned int trip_id; + unsigned int usage; + unsigned long min; + unsigned long max; +}; + +/** + * 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 + * @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 + * @get_temp: sensor callback to read temperature + * @get_trend: sensor callback to read temperature trend + */ + +struct __thermal_zone { + enum thermal_device_mode mode; + int passive_delay; + int polling_delay; + + /* trip data */ + int ntrips; + struct __thermal_trip *trips; + + /* cooling binding data */ + int num_tbps; + struct __thermal_bind_params *tbps; + + /* sensor interface */ + void *sensor_data; + int (*get_temp)(void *, long *); + int (*get_trend)(void *, long *); +}; + +/*** DT thermal zone device callbacks ***/ + +static int of_thermal_get_temp(struct thermal_zone_device *tz, + unsigned long *temp) +{ + struct __thermal_zone *data = tz->devdata; + + if (!data->get_temp) + return -EINVAL; + + return data->get_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; + long dev_trend; + int r; + + if (!data->get_trend) + return -EINVAL; + + r = data->get_trend(data->sensor_data, &dev_trend); + if (r) + return r; + + /* TODO: These intervals might have some thresholds, but in core code */ + if (dev_trend > 0) + *trend = THERMAL_TREND_RAISING; + else if (dev_trend < 0) + *trend = THERMAL_TREND_DROPPING; + else + *trend = THERMAL_TREND_STABLE; + + return 0; +} + +static int of_thermal_bind(struct thermal_zone_device *thermal, + struct thermal_cooling_device *cdev) +{ + struct __thermal_zone *data = thermal->devdata; + int i; + + if (!data || IS_ERR(data)) + return -ENODEV; + + /* find where to bind */ + for (i = 0; i < data->num_tbps; i++) { + struct __thermal_bind_params *tbp = data->tbps + i; + + if (tbp->cooling_device == cdev->np) { + int ret; + + ret = thermal_zone_bind_cooling_device(thermal, + tbp->trip_id, cdev, + tbp->min, + tbp->max); + 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; + int i; + + if (!data || IS_ERR(data)) + return -ENODEV; + + /* find where to unbind */ + for (i = 0; i < data->num_tbps; i++) { + struct __thermal_bind_params *tbp = data->tbps + i; + + if (tbp->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; + else + tz->polling_delay = 0; + + mutex_unlock(&tz->lock); + + data->mode = mode; + thermal_zone_device_update(tz); + + 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, + unsigned long *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, + unsigned long temp) +{ + 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].temperature = temp; + + return 0; +} + +static int of_thermal_get_trip_hyst(struct thermal_zone_device *tz, int trip, + unsigned long *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, + unsigned long 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, + unsigned long *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, + int (*get_temp)(void *, long *), + int (*get_trend)(void *, long *)) +{ + 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; + + mutex_lock(&tzd->lock); + tz->get_temp = get_temp; + tz->get_trend = get_trend; + tz->sensor_data = data; + + tzd->ops->get_temp = of_thermal_get_temp; + tzd->ops->get_trend = of_thermal_get_trend; + mutex_unlock(&tzd->lock); + + return tzd; +} + +/** + * 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. + * @get_temp: a pointer to a function that reads the sensor temperature. + * @get_trend: a pointer to a function that reads the sensor temperature trend. + * + * 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, int (*get_temp)(void *, long *), + int (*get_trend)(void *, long *)) +{ + struct device_node *np, *child, *sensor_np; + + np = of_find_node_by_name(NULL, "thermal-zones"); + if (!np) + return ERR_PTR(-ENODEV); + + if (!dev || !dev->of_node) + return ERR_PTR(-EINVAL); + + sensor_np = dev->of_node; + + for_each_child_of_node(np, child) { + struct of_phandle_args sensor_specs; + int ret, id; + + /* For now, thermal framework supports only 1 sensor per zone */ + ret = of_parse_phandle_with_args(child, "thermal-sensors", + "#thermal-sensor-cells", + 0, &sensor_specs); + if (ret) + continue; + + if (sensor_specs.args_count >= 1) { + id = sensor_specs.args[0]; + WARN(sensor_specs.args_count > 1, + "%s: too many cells in sensor specifier %d\n", + sensor_specs.np->name, sensor_specs.args_count); + } else { + id = 0; + } + + if (sensor_specs.np == sensor_np && id == sensor_id) { + of_node_put(np); + return thermal_zone_of_add_sensor(child, sensor_np, + data, + get_temp, + get_trend); + } + } + of_node_put(np); + + return ERR_PTR(-ENODEV); +} +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; + + tz->get_temp = NULL; + tz->get_trend = NULL; + tz->sensor_data = NULL; + mutex_unlock(&tzd->lock); +} +EXPORT_SYMBOL_GPL(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 device_node *trip; + int ret, i; + u32 prop; + + /* Default weight. Usage is optional */ + __tbp->usage = 0; + 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; + } + + ret = of_parse_phandle_with_args(np, "cooling-device", "#cooling-cells", + 0, &cooling_spec); + if (ret < 0) { + pr_err("missing cooling_device property\n"); + goto end; + } + __tbp->cooling_device = cooling_spec.np; + if (cooling_spec.args_count >= 2) { /* at least min and max */ + __tbp->min = cooling_spec.args[0]; + __tbp->max = cooling_spec.args[1]; + } else { + pr_err("wrong reference to cooling device, missing limits\n"); + } + +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; + + 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 and coefficients + * + * 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 * +thermal_of_build_thermal_zone(struct device_node *np) +{ + struct device_node *child = NULL, *gchild; + struct __thermal_zone *tz; + int ret, i; + u32 prop; + + 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("missing polling-delay-passive property\n"); + goto free_tz; + } + tz->passive_delay = prop; + + ret = of_property_read_u32(np, "polling-delay", &prop); + if (ret < 0) { + pr_err("missing polling-delay property\n"); + goto free_tz; + } + tz->polling_delay = prop; + + /* 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 = kzalloc(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 */ |