Re: [PATCHv8 03/18] thermal: introduce device tree parser

From: Mark Rutland
Date: Mon Oct 07 2013 - 16:51:39 EST


Hi all,

Given that this is a complex new class of binding, I'd appreciate if at least
one other device tree maintainer could also take a look over this and voice any
concerns or suggestions. I've left the entire patch context for this.

Apologies for the munged whitespace -- my mail server appears to have helpfully
and irreparably replaced all tabs with spaces.

On Tue, Oct 01, 2013 at 03:39:26AM +0100, Eduardo Valentin wrote:
> This patch introduces a device tree bindings for
> describing the hardware thermal behavior and limits.
> Also a parser to read and interpret the data and feed
> it in the thermal framework is presented.
>
> This patch introduces a thermal data parser for device
> tree. The parsed data is used to build thermal zones
> and thermal binding parameters. The output data
> can then be used to deploy thermal policies.
>
> This patch adds also documentation regarding this
> API and how to define tree nodes to use
> this infrastructure.
>
> Note that, in order to be able to have control
> on the sensor registration on the DT thermal zone,
> it was required to allow changing the thermal zone
> .get_temp callback. For this reason, this patch
> also removes the 'const' modifier from the .ops
> field of thermal zone devices.
>
> Cc: Zhang Rui <rui.zhang@xxxxxxxxx>
> Cc: linux-pm@xxxxxxxxxxxxxxx
> Cc: linux-kernel@xxxxxxxxxxxxxxx
> Signed-off-by: Eduardo Valentin <eduardo.valentin@xxxxxx>
> ---
> Hello Mark, folks,
>
> So, here is v8. Pretty small changes. Most are better
> English phrasing in the binding document. It follows changelog:
> - Several rephrasing in the binding document, including
> spelling, grammar and better phrasing.
> - Removed WiP warning from binding document.
> - Several spacing and formatting changes in the binding document
> - Used the '-specifier' nomenclature properly in the binding doc
> - Removed the optional property 'thermal-sensors-names', because
> it does not provide useful runtime information
> - Fixed description of 'polling-delay-passive'
> - Improved examples by adding comments and better explanation
> - s/fw/framework/g
> - Added a WARN when sensors specifiers are greater and 1.
>
> All best,
>
> Eduardo
>
> ---
> .../devicetree/bindings/thermal/thermal.txt | 587 ++++++++++++++
> drivers/thermal/Kconfig | 13 +
> drivers/thermal/Makefile | 1 +
> drivers/thermal/of-thermal.c | 849 +++++++++++++++++++++
> drivers/thermal/thermal_core.c | 9 +-
> drivers/thermal/thermal_core.h | 9 +
> include/dt-bindings/thermal/thermal.h | 27 +
> include/linux/thermal.h | 28 +-
> 8 files changed, 1520 insertions(+), 3 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/thermal/thermal.txt
> create mode 100644 drivers/thermal/of-thermal.c
> create mode 100644 include/dt-bindings/thermal/thermal.h
>
> diff --git a/Documentation/devicetree/bindings/thermal/thermal.txt b/Documentation/devicetree/bindings/thermal/thermal.txt
> new file mode 100644
> index 0000000..ad06a8d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/thermal.txt
> @@ -0,0 +1,587 @@
> +* Thermal Framework Device Tree descriptor
> +
> +This file describes a generic binding to provide a way of
> +defining hardware thermal structure using device tree.

s/defining/describing/

> +A thermal structure includes thermal zones and their components,
> +such as trip points, polling intervals, sensors and cooling devices
> +binding descriptors.
> +
> +The target of device tree thermal descriptors is to describe only
> +the hardware thermal aspects. The thermal device tree bindings are
> +not about how the system must control or which algorithm or policy
> +must be taken in place.

I'm not quite sure what this is saying -- it seems to contradict the
design of the binding where trip points get linked to cooling device
configuration, and thus we're defining a policy along the lines of "when
sensor $W hits temperature $X, configure cooling device $Y with value
$Z". I don't see how that can't be considered policy.

At best, we can say that policy is being defined because describing the
physical properties of a device and dealing with this at runtime is not
feasible.

> +
> +There are five types of nodes involved to describe thermal bindings:
> +- sensors: used to describe the device source of temperature sensing;

- thermal sensors: devices which may be used to take temperature
measurements.

> +- cooling devices: used to describe devices source of power dissipation control;

- cooling devices: devices which may be used to dissipate heat.

> +- trip points: used to describe points in temperature domain defined to
> +make the system aware of hardware limits;

- trip points: describe key temperatures at which cooling is recommended. The
set of points should be chosen based on hardware limits.

> +- cooling maps: used to describe links between trip points and cooling devices;
> +- thermal zones: used to describe thermal data within the hardware;
> +
> +It follows a description of each type of these device tree nodes.

How about:

The following is a description of each of these node types.

> +
> +* Thermal sensor devices
> +
> +Thermal sensor devices are nodes providing temperature sensing capabilities on
> +thermal zones. Typical devices are I2C ADC converters and bandgaps. These are
> +nodes providing temperature data to thermal zones. Thermal sensor devices may
> +control one or more internal sensors.
> +
> +Required property:
> +- #thermal-sensor-cells: Used to provide sensor device specific information
> + Type: unsigned while referring to it. Typically 0 on thermal sensor
> + Size: one cell nodes with only one sensor, and at least 1 on nodes
> + with several internal sensors, in order
> + to identify uniquely the sensor instances within
> + the IC. See thermal zone binding for more details
> + on how consumers refer to sensor devices.
> +
> +* Cooling device nodes
> +
> +Cooling devices are nodes providing control on power dissipation. There
> +are essentially two ways to provide control on power dissipation. First
> +is by means of regulating device performance, which is known as passive
> +cooling. A typical passive cooling is a CPU that has dynamic voltage and
> +frequency scaling (DVFS), and uses lower frequencies as cooling states.
> +Second is by means of activating devices in order to remove
> +the dissipated heat, which is known as active cooling, e.g. regulating
> +fan speeds. In both cases, cooling devices shall have a way to determine
> +the state of cooling in which the device is.

I'd probably say "heat dissipation" rather than "power dissipation", unless
this is a technical term I am not aware of?

Why is the determination of the state of the cooling device important?

> +
> +Required properties:
> +- cooling-min-state: An integer indicating the smallest
> + Type: unsigned cooling state accepted. Typically 0.
> + Size: one cell
> +
> +- cooling-max-state: An integer indicating the largest
> + Type: unsigned cooling state accepted.
> + Size: one cell
> +
> +- #cooling-cells: Used to provide cooling device specific information
> + Type: unsigned while referring to it. Must be at least 2, in order
> + Size: one cell to specify minimum and maximum cooling state used
> + in the reference. The first cell is the minimum
> + cooling state requested and the second cell is
> + the maximum cooling state requested in the reference.
> + See Cooling device maps section below for more details
> + on how consumers refer to cooling devices.

Is it assumed that all the values between min and max are valid, or might this
be a sparse set of cooling values?

> +
> +* Trip points
> +
> +The trip node is a node to describe a point in the temperature domain
> +in which the system takes an action. This node describes just the point,
> +not the action.
> +
> +Required properties:
> +- temperature: An integer indicating the trip temperature level,
> + Type: signed in millicelsius.
> + Size: one cell
> +
> +- hysteresis: a (low) hysteresis value on 'temperature'. This is a
> + Type: unsigned relative value, in millicelsius.
> + Size: one cell

What is it relative to?

Why is 'temperature' in quotes?

> +
> +- type: a string containing the trip type. Supported values are:
> + "active": A trip point to enable active cooling
> + "passive": A trip point to enable passive cooling
> + "hot": A trip point to notify emergency
> + "critical": Hardware not reliable.
> + Type: string
> +
> +There are also string constants defined at
> +include/dt-bindings/thermal/thermal.h.

Why?

> +
> +* Cooling device maps
> +
> +The cooling device maps node is a node to describe how cooling devices
> +get assigned to trip points of the zone. The cooling devices are expected
> +to be loaded in the target system.
> +
> +Required properties:
> +- cooling-device: A phandle of a cooling device with its specifier,
> + Type: phandle of referring to which cooling device is used in this
> + cooling device binding. In the cooling specifier, the first cell
> + is the minimum cooling state and the second cell
> + is the maximum cooling state used in this map.

The type is wrong here -- it's a phandle + cooling-cells, not just a phandle.

> +- trip: A phandle of a trip point node within the same thermal
> + Type: phandle of zone.
> + trip point node
> +
> +Optional property:
> +- contribution: The cooling contribution to the thermal zone of the
> + Type: unsigned referred cooling device at the referred trip point.
> + Size: one cell The contribution is a ratio of the sum
> + of all cooling contributions within a thermal zone.
> +
> +Note: Using the THERMAL_NO_LIMIT (-1UL) constant in the cooling-device phandle
> +limit specifier means:
> +(i) - minimum state allowed for minimum cooling state used in the reference.
> +(ii) - maximum state allowed for maximum cooling state used in the reference.
> +Refer to include/dt-bindings/thermal/thermal.h for definition of this constant.
> +
> +* Thermal zone nodes
> +
> +The thermal zone node is the node containing all the required info
> +for describing a thermal zone, including its cooling device bindings. The
> +thermal zone node must contain, apart from its own properties, one sub-node
> +containing trip nodes and one sub-node containing all the zone cooling maps.
> +
> +Required properties:
> +- polling-delay: The maximum number of milliseconds to wait between polls
> + Type: unsigned when checking this thermal zone.
> + Size: one cell
> +
> +- polling-delay-passive: The maximum number of milliseconds to wait
> + Type: unsigned between polls when performing passive cooling.
> + Size: one cell
> +
> +- thermal-sensors: A list of thermal sensor phandles and sensor specifier
> + Type: list of used while monitoring the thermal zone.
> + sensor phandles

The type is wrong here. A list of phandle + sensor specifiers.

> +
> +- trips: A sub-node which is a container of only trip point nodes
> + Type: sub-node required to describe the thermal zone.
> +
> +- cooling-maps: A sub-node which is a container of only cooling device
> + Type: sub-node map nodes, used to describe the relation between trips
> + and cooling devices.
> +
> +Optional property:
> +- coefficients: An array of integers (one signed cell) containing
> + Type: array coefficients to compose a linear relation between
> + Elem size: one cell the sensors listed in the thermal-sensors property.
> + Elem type: signed Coefficients defaults to 1, in case this property
> + is not specified. A simple linear polynomial is used:
> + Z = c0 * x0 + c1 + x1 + ... + c(n-1) * x(n-1) + cn.
> +
> + The coefficients are ordered and they match with sensors
> + by means of sensor ID. Additional coefficients are
> + interpreted as constant offset.
> +
> +Note: The delay properties are bound to the maximum dT/dt (temperature
> +derivative over time) in two situations for a thermal zone:
> +(i) - when passive cooling is activated (polling-delay-passive); and
> +(ii) - 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.
> +
> +* The thermal-zones node
> +
> +The "thermal-zones" node is a container for all thermal zone nodes. It shall
> +contain only sub-nodes describing thermal zones as in the section
> +"Thermal zone nodes". The "thermal-zones" node appears under "/".
> +
> +* Examples
> +
> +Below are several examples on how to use thermal data descriptors
> +using device tree bindings:
> +
> +(a) - CPU thermal zone
> +
> +The CPU thermal zone example below describes how to setup one thermal zone
> +using one single sensor as temperature source and many cooling devices and
> +power dissipation control sources.
> +
> +#include <dt-bindings/thermal/thermal.h>
> +
> +cpus {
> + /*
> + * Here is an example of describing a cooling device for a DVFS
> + * capable CPU. The CPU node describes its four OPPs.
> + * The cooling states possible are 0..3, and they are
> + * used as OPP indexes. The minimum cooling state is 0, which means
> + * all four OPPs can be available to the system. The maximum
> + * cooling state is 3, which means only the lowest OPPs (198MHz@xxxxx)
> + * can be available in the system.
> + */
> + cpu0: cpu@0 {
> + ...
> + operating-points = <
> + /* kHz uV */
> + 970000 1200000
> + 792000 1100000
> + 396000 950000
> + 198000 850000
> + >;
> + cooling-min-state = <0>;
> + cooling-max-state = <3>;
> + #cooling-cells = <2>; /* min followed by max */
> + };
> + ...
> +};
> +
> +&i2c1 {
> + ...
> + /*
> + * A simple fan controller which supports 10 speeds of operation
> + * (represented as 0-9).
> + */
> + fan0: fan@0x48 {
> + ...
> + cooling-min-state = <0>;
> + cooling-max-state = <9>;
> + #cooling-cells = <2>; /* min followed by max */
> + };
> +};
> +
> +ocp {
> + ...
> + /*
> + * A simple IC with a single bandgap temperature sensor.
> + */
> + bandgap0: bandgap@0x0000ED00 {
> + ...
> + #thermal-sensor-cells = <0>;
> + };
> +};
> +
> +thermal-zones {
> + cpu-thermal: cpu-thermal {
> + polling-delay-passive = <250>; /* milliseconds */
> + polling-delay = <1000>; /* milliseconds */
> +
> + thermal-sensors = <&bandgap0>;
> +
> + trips {
> + cpu-alert0: cpu-alert {
> + temperature = <90000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "active";
> + };
> + cpu-alert1: cpu-alert {
> + temperature = <100000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + };
> + cpu-crit: cpu-crit {
> + temperature = <125000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + map0 {
> + trip = <&cpu-alert0>;
> + cooling-device = <&fan0 THERMAL_NO_LIMITS 4>;
> + };
> + map1 {
> + trip = <&cpu-alert1>;
> + cooling-device = <&fan0 5 THERMAL_NO_LIMITS>;
> + };
> + map2 {
> + trip = <&cpu-alert1>;
> + cooling-device =
> + <&cpu0 THERMAL_NO_LIMITS THERMAL_NO_LIMITS>;
> + };
> + };
> + };
> +};
> +
> +In the example above, the ADC sensor (bandgap0) at address 0x0000ED00 is
> +used to monitor the zone 'cpu-thermal' using its sole sensor. A fan
> +device (fan0) is controlled via I2C bus 1, at address 0x48, and has ten
> +different cooling states 0-9. It is used to remove the heat out of
> +the thermal zone 'cpu-thermal' using its cooling states
> +from its minimum to 4, when it reaches trip point 'cpu-alert0'
> +at 90C, as an example of active cooling. The same cooling device is used at
> +'cpu-alert1', but from 5 to its maximum state. The cpu@0 device is also
> +linked to the same thermal zone, 'cpu-thermal', as a passive cooling device,
> +using all its cooling states at trip point 'cpu-alert1',
> +which is a trip point at 100C. On the thermal zone 'cpu-thermal', at the
> +temperature of 125C, represented by the trip point 'cpu-crit', the silicon
> +is not reliable anymore.
> +
> +(b) - IC with several internal sensors
> +
> +The example below describes how to deploy several thermal zones based off a
> +single sensor IC, assuming it has several internal sensors. This is a common
> +case on SoC designs with several internal IPs that may need different thermal
> +requirements, and thus may have their own sensor to monitor or detect internal
> +hotspots in their silicon.
> +
> +#include <dt-bindings/thermal/thermal.h>
> +
> +ocp {
> + ...
> + /*
> + * A simple IC with several bandgap temperature sensors.
> + */
> + bandgap0: bandgap@0x0000ED00 {
> + ...
> + #thermal-sensor-cells = <1>;
> + };
> +};
> +
> +thermal-zones {
> + cpu-thermal: cpu-thermal {
> + polling-delay-passive = <250>; /* milliseconds */
> + polling-delay = <1000>; /* milliseconds */
> +
> + /* sensor ID */
> + thermal-sensors = <&bandgap0 0>;
> +
> + trips {
> + /* each zone within the SoC may have its own trips */
> + cpu-alert: cpu-alert {
> + temperature = <100000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + };
> + cpu-crit: cpu-crit {
> + temperature = <125000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + /* each zone within the SoC may have its own cooling */
> + ...
> + };
> + };
> +
> + gpu-thermal: gpu-thermal {
> + polling-delay-passive = <120>; /* milliseconds */
> + polling-delay = <1000>; /* milliseconds */
> +
> + /* sensor ID */
> + thermal-sensors = <&bandgap0 1>;
> +
> + trips {
> + /* each zone within the SoC may have its own trips */
> + gpu-alert: gpu-alert {
> + temperature = <90000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + };
> + gpu-crit: gpu-crit {
> + temperature = <105000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + /* each zone within the SoC may have its own cooling */
> + ...
> + };
> + };
> +
> + dsp-thermal: dsp-thermal {
> + polling-delay-passive = <50>; /* milliseconds */
> + polling-delay = <1000>; /* milliseconds */
> +
> + /* sensor ID */
> + thermal-sensors = <&bandgap0 2>;
> +
> + trips {
> + /* each zone within the SoC may have its own trips */
> + dsp-alert: gpu-alert {
> + temperature = <90000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + };
> + dsp-crit: gpu-crit {
> + temperature = <135000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + /* each zone within the SoC may have its own cooling */
> + ...
> + };
> + };
> +};
> +
> +In the example above, there is one bandgap IC which has the capability to
> +monitor three sensors. The hardware has been designed so that sensors are
> +placed on different places in the DIE to monitor different temperature
> +hotspots: one for CPU thermal zone, one for GPU thermal zone and the
> +other to monitor a DSP thermal zone.
> +
> +Thus, there is a need to assign each sensor provided by the bandgap IC
> +to different thermal zones. This is achieved by means of using the
> +#thermal-sensor-cells property and using the first cell of the sensor
> +specifier as sensor ID. In the example, then, <bandgap 0> is used to
> +monitor CPU thermal zone, <bandgap 1> is used to monitor GPU thermal
> +zone and <bandgap 2> is used to monitor DSP thermal zone. Each zone
> +may be uncorrelated, having its own dT/dt requirements, trips
> +and cooling maps.
> +
> +
> +(c) - Several sensors within one single thermal zone
> +
> +The example below illustrates how to use more than one sensor within
> +one thermal zone.
> +
> +#include <dt-bindings/thermal/thermal.h>
> +
> +&i2c1 {
> + ...
> + /*
> + * A simple IC with a single temperature sensor.
> + */
> + adc: sensor@0x49 {
> + ...
> + #thermal-sensor-cells = <0>;
> + };
> +};
> +
> +ocp {
> + ...
> + /*
> + * A simple IC with a single bandgap temperature sensor.
> + */
> + bandgap0: bandgap@0x0000ED00 {
> + ...
> + #thermal-sensor-cells = <0>;
> + };
> +};
> +
> +thermal-zones {
> + cpu-thermal: cpu-thermal {
> + polling-delay-passive = <250>; /* milliseconds */
> + polling-delay = <1000>; /* milliseconds */
> +
> + thermal-sensors = <&bandgap0>, /* cpu */
> + <&adc>; /* pcb north */
> +
> + /* hotspot = 100 * bandgap - 120 * adc + 484 */
> + coefficients = <100 -120 484>;
> +
> + trips {
> + ...
> + };
> +
> + cooling-maps {
> + ...
> + };
> + };
> +};
> +
> +In some cases, there is a need to use more than one sensor to extrapolate
> +a thermal hotspot in the silicon. The above example illustrates this situation.
> +For instance, it may be the case that a sensor external to CPU IP may be placed
> +close to CPU hotspot and together with internal CPU sensor, it is used
> +to determine the hotspot. Assuming this is the case for the above example,
> +the hypothetical extrapolation rule would be:
> + hotspot = 100 * bandgap - 120 * adc + 484
> +
> +In other context, the same idea can be used to add fixed offset. For instance,
> +consider the hotspot extrapolation rule below:
> + hotspot = 1 * adc + 6000
> +
> +In the above equation, the hotspot is always 6C higher than what is read
> +from the ADC sensor. The binding would be then:
> + thermal-sensors = <&adc>;
> +
> + /* hotspot = 1 * adc + 6000 */
> + coefficients = <1 6000>;
> +
> +(d) - Board thermal
> +
> +The board thermal example below illustrates how to setup one thermal zone
> +with many sensors and many cooling devices.
> +
> +#include <dt-bindings/thermal/thermal.h>
> +
> +&i2c1 {
> + ...
> + /*
> + * An IC with several temperature sensor.
> + */
> + adc-dummy: sensor@0x50 {
> + ...
> + #thermal-sensor-cells = <1>; /* sensor internal ID */
> + };
> +};
> +
> +thermal-zones {
> + batt-thermal {
> + polling-delay-passive = <500>; /* milliseconds */
> + polling-delay = <2500>; /* milliseconds */
> +
> + /* sensor ID */
> + thermal-sensors = <&adc-dummy 4>;
> +
> + trips {
> + ...
> + };
> +
> + cooling-maps {
> + ...
> + };
> + };
> +
> + board-thermal: board-thermal {
> + polling-delay-passive = <1000>; /* milliseconds */
> + polling-delay = <2500>; /* milliseconds */
> +
> + /* sensor ID */
> + thermal-sensors = <&adc-dummy 0>, /* pcb top edge */
> + <&adc-dummy 1>, /* lcd */
> + <&adc-dymmy 2>; /* back cover */
> + /*
> + * An array of coefficients describing the sensor
> + * linear relation. E.g.:
> + * z = c1*x1 + c2*x2 + c3*x3
> + */
> + coefficients = <1200 -345 890>;
> +
> + trips {
> + /* Trips are based on resulting linear equation */
> + cpu-trip: cpu-trip {
> + temperature = <60000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + };
> + gpu-trip: gpu-trip {
> + temperature = <55000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + }
> + lcd-trip: lcp-trip {
> + temperature = <53000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "passive";
> + };
> + crit-trip: crit-trip {
> + temperature = <68000>; /* millicelsius */
> + hysteresis = <2000>; /* millicelsius */
> + type = "critical";
> + };
> + };
> +
> + cooling-maps {
> + map0 {
> + trip = <&cpu-trip>;
> + cooling-device = <&cpu0 0 2>;
> + contribution = <55>;
> + };
> + map1 {
> + trip = <&gpu-trip>;
> + cooling-device = <&gpu0 0 2>;
> + contribution = <20>;
> + };
> + map2 {
> + trip = <&lcd-trip>;
> + cooling-device = <&lcd0 5 10>;
> + contribution = <15>;
> + };
> + };
> + };
> +};
> +
> +The above example is a mix of previous examples, a sensor IP with several internal
> +sensors used to monitor different zones, one of them is composed by several sensors and
> +with different cooling devices.
> diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
> index dbfc390..dd81eb8 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
> diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
> index 584b363..4b03956 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/of-thermal.c b/drivers/thermal/of-thermal.c
> new file mode 100644
> index 0000000..66f9eb2
> --- /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@xxxxxx>
> + *
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *
> + * 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 */
> + if (!child)
> + goto finish;
> +
> + tz->num_tbps = of_get_child_count(child);
> + if (tz->num_tbps == 0)
> + goto finish;
> +
> + tz->tbps = kzalloc(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:
> + kfree(tz->tbps);
> +free_trips:
> + kfree(tz->trips);
> +free_tz:
> + kfree(tz);
> + of_node_put(child);
> +
> + return ERR_PTR(ret);
> +}
> +
> +static inline void of_thermal_free_zone(struct __thermal_zone *tz)
> +{
> + kfree(tz->tbps);
> + kfree(tz->trips);
> + kfree(tz);
> +}
> +
> +/**
> + * 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_child_of_node(np, child) {
> + struct thermal_zone_device *zone;
> + struct thermal_zone_params *tzp;
> +
> + tz = thermal_of_build_thermal_zone(child);
> + if (IS_ERR(tz)) {
> + pr_err("failed to build thermal zone %s: %ld\n",
> + child->name,
> + 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;
> +
> + zone = thermal_zone_device_register(child->name, tz->ntrips,
> + 0, tz,
> + ops, tzp,
> + tz->passive_delay,
> + tz->polling_delay);
> + if (IS_ERR(zone)) {
> + pr_err("Failed to build %s zone %ld\n", child->name,
> + PTR_ERR(zone));
> + kfree(tzp);
> + kfree(ops);
> + of_thermal_free_zone(tz);
> + /* attempting to build remaining zones still */
> + }
> + }
> +
> + return 0;
> +
> +exit_free:
> + of_thermal_free_zone(tz);
> +
> + /* no memory available, so free what we have built */
> + of_thermal_destroy_zones();
> +
> + return -ENOMEM;
> +}
> +
> +/**
> + * 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.
> + *
> + */
> +void __exit of_thermal_destroy_zones(void)
> +{
> + struct device_node *np, *child;
> +
> + np = of_find_node_by_name(NULL, "thermal-zones");
> + if (!np) {
> + pr_err("unable to find thermal zones\n");
> + return;
> + }
> +
> + for_each_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);
> + }
> +}
> diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c
> index f7a9f4f..fec3351 100644
> --- a/drivers/thermal/thermal_core.c
> +++ b/drivers/thermal/thermal_core.c
> @@ -1371,7 +1371,7 @@ static void remove_trip_attrs(struct thermal_zone_device *tz)
> */
> struct thermal_zone_device *thermal_zone_device_register(const char *type,
> int trips, int mask, void *devdata,
> - const struct thermal_zone_device_ops *ops,
> + struct thermal_zone_device_ops *ops,
> const struct thermal_zone_params *tzp,
> int passive_delay, int polling_delay)
> {
> @@ -1751,8 +1751,14 @@ static int __init thermal_init(void)
> if (result)
> goto unregister_class;
>
> + result = of_parse_thermal_zones();
> + if (result)
> + goto exit_netlink;
> +
> return 0;
>
> +exit_netlink:
> + genetlink_exit();
> unregister_governors:
> thermal_unregister_governors();
> unregister_class:
> @@ -1768,6 +1774,7 @@ error:
>
> static void __exit thermal_exit(void)
> {
> + of_thermal_destroy_zones();
> genetlink_exit();
> class_unregister(&thermal_class);
> thermal_unregister_governors();
> diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h
> index 7cf2f66..3db339f 100644
> --- a/drivers/thermal/thermal_core.h
> +++ b/drivers/thermal/thermal_core.h
> @@ -77,4 +77,13 @@ static inline int thermal_gov_user_space_register(void) { return 0; }
> static inline void thermal_gov_user_space_unregister(void) {}
> #endif /* CONFIG_THERMAL_GOV_USER_SPACE */
>
> +/* device tree support */
> +#ifdef CONFIG_THERMAL_OF
> +int of_parse_thermal_zones(void);
> +void of_thermal_destroy_zones(void);
> +#else
> +static inline int of_parse_thermal_zones(void) { return 0; }
> +static inline void of_thermal_destroy_zones(void) { }
> +#endif
> +
> #endif /* __THERMAL_CORE_H__ */
> diff --git a/include/dt-bindings/thermal/thermal.h b/include/dt-bindings/thermal/thermal.h
> new file mode 100644
> index 0000000..59c4581
> --- /dev/null
> +++ b/include/dt-bindings/thermal/thermal.h
> @@ -0,0 +1,27 @@
> +/*
> + * This header provides constants for most thermal bindings.
> + *
> + * Copyright (C) 2013 Texas Instruments
> + * Eduardo Valentin <eduardo.valentin@xxxxxx>
> + *
> + * GPLv2 only
> + */
> +
> +#ifndef _DT_BINDINGS_THERMAL_THERMAL_H
> +#define _DT_BINDINGS_THERMAL_THERMAL_H
> +
> +/*
> + * Here are the thermal trip types. This must
> + * match with enum thermal_trip_type at
> + * include/linux/thermal.h
> + */
> +#define THERMAL_TRIP_ACTIVE "active"
> +#define THERMAL_TRIP_PASSIVE "passive"
> +#define THERMAL_TRIP_HOT "hot"
> +#define THERMAL_TRIP_CRITICAL "critical"

I don't like this. If someone wants to include this in a C file they can't do so
at the same time as include/linux/thermal.h. The defined names are longer than
the actual string values, and the comment makes it sound like these could be
modified rather than being a fixed ABI.

I do not see the point of these constants.

Thanks,
Mark.

> +
> +/* On cooling devices upper and lower limits */
> +#define THERMAL_NO_LIMIT (-1UL)
> +
> +#endif
> +
> diff --git a/include/linux/thermal.h b/include/linux/thermal.h
> index b268d3c..b780c5b 100644
> --- a/include/linux/thermal.h
> +++ b/include/linux/thermal.h
> @@ -143,6 +143,7 @@ struct thermal_cooling_device {
> int id;
> char type[THERMAL_NAME_LENGTH];
> struct device device;
> + struct device_node *np;
> void *devdata;
> const struct thermal_cooling_device_ops *ops;
> bool updated; /* true if the cooling device does not need update */
> @@ -172,7 +173,7 @@ struct thermal_zone_device {
> int emul_temperature;
> int passive;
> unsigned int forced_passive;
> - const struct thermal_zone_device_ops *ops;
> + struct thermal_zone_device_ops *ops;
> const struct thermal_zone_params *tzp;
> struct thermal_governor *governor;
> struct list_head thermal_instances;
> @@ -242,8 +243,31 @@ struct thermal_genl_event {
> };
>
> /* Function declarations */
> +#ifdef CONFIG_THERMAL_OF
> +struct thermal_zone_device *
> +thermal_zone_of_sensor_register(struct device *dev, int id,
> + void *data, int (*get_temp)(void *, long *),
> + int (*get_trend)(void *, long *));
> +void thermal_zone_of_sensor_unregister(struct device *dev,
> + struct thermal_zone_device *tz);
> +#else
> +static inline struct thermal_zone_device *
> +thermal_zone_of_sensor_register(struct device *dev, int id,
> + void *data, int (*get_temp)(void *, long *),
> + int (*get_trend)(void *, long *))
> +{
> + return NULL;
> +}
> +
> +static inline
> +void thermal_zone_of_sensor_unregister(struct device *dev,
> + struct thermal_zone_device *tz)
> +{
> +}
> +
> +#endif
> struct thermal_zone_device *thermal_zone_device_register(const char *, int, int,
> - void *, const struct thermal_zone_device_ops *,
> + void *, struct thermal_zone_device_ops *,
> const struct thermal_zone_params *, int, int);
> void thermal_zone_device_unregister(struct thermal_zone_device *);
>
> --
> 1.8.2.1.342.gfa7285d
>
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/