- Add the hwmon support on the i.MX SC (Anson Huang)
- Thermal framework cleanups (self-encapsulation, pointless stubs, private structures) (Daniel Lezcano) - Use the PM QoS frequency changes for the devfreq cooling device (Matthias Kaehlcke) - Remove duplicate error messages from platform_get_irq() error handling (Markus Elfring) - Add support for the bandgap sensors (Keerthy) - Statically initialize .get_mode/.set_mode ops (Andrzej Pietrasiewicz) - Add Renesas R-Car maintainer entry (Niklas Söderlund) - Fix error checking after calling ti_bandgap_get_sensor_data() for the TI SoC thermal (Sudip Mukherjee) - Add latency constraint for the idle injection, the DT binding and the change the registering function (Daniel Lezcano) - Convert the thermal framework binding to the Yaml schema (Amit Kucheria) - Replace zero-length array with flexible-array on i.MX 8MM (Gustavo A. R. Silva) - Thermal framework cleanups (alphabetic order for heads, replace module.h by export.h, make file naming consistent) (Amit Kucheria) - Merge tsens-common into the tsens driver (Amit Kucheria) - Fix platform dependency for the Qoriq driver (Geert Uytterhoeven) - Clean up the rcar_thermal_update_temp() function in the rcar thermal driver (Niklas Söderlund) - Fix the TMSAR register for the TMUv2 on the Qoriq platform (Yuantian Tang) - Export GDDV, OEM vendor variables, and don't require IDSP for the int340x thermal driver - trivial conflicts fixed (Matthew Garrett) -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEGn3N4YVz0WNVyHskqDIjiipP6E8FAl7jra8ACgkQqDIjiipP 6E+ugAgApBF6FsHoonWIvoSrzBrrbU2oqhEJA42Mx+iY/UnXi01I79vZ/8WpZt7M D1J01Kf0PUhRbywoKaoCX3Oh9ZO9PKq4N9ZC8yqdoD6GLl+rC9Wmr7Ui+c80klcv M9rYhpPYfNXTFj0saSbbFWNNhP4TvhzGsNj8foYVQDKyhjbSmNE5ipZlbmP23jlr O53SmJAwS5zxLOd8QA5nfSWP9FYYMuCR2AHj8BUCmxiAjXZLPNB/Hz2RRBr7q0MF zRo/4HJ04mSQYp0kluP/EBhz9g2wM/htIPyWRveB/ByKEYt3UNKjB++PJmPbu5UG dS3aXZhRfaPqpdsWrMB9fY7ll+oyfw== =T+RI -----END PGP SIGNATURE----- Merge tag 'thermal-v5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux Pull thermal updates from Daniel Lezcano: - Add the hwmon support on the i.MX SC (Anson Huang) - Thermal framework cleanups (self-encapsulation, pointless stubs, private structures) (Daniel Lezcano) - Use the PM QoS frequency changes for the devfreq cooling device (Matthias Kaehlcke) - Remove duplicate error messages from platform_get_irq() error handling (Markus Elfring) - Add support for the bandgap sensors (Keerthy) - Statically initialize .get_mode/.set_mode ops (Andrzej Pietrasiewicz) - Add Renesas R-Car maintainer entry (Niklas Söderlund) - Fix error checking after calling ti_bandgap_get_sensor_data() for the TI SoC thermal (Sudip Mukherjee) - Add latency constraint for the idle injection, the DT binding and the change the registering function (Daniel Lezcano) - Convert the thermal framework binding to the Yaml schema (Amit Kucheria) - Replace zero-length array with flexible-array on i.MX 8MM (Gustavo A. R. Silva) - Thermal framework cleanups (alphabetic order for heads, replace module.h by export.h, make file naming consistent) (Amit Kucheria) - Merge tsens-common into the tsens driver (Amit Kucheria) - Fix platform dependency for the Qoriq driver (Geert Uytterhoeven) - Clean up the rcar_thermal_update_temp() function in the rcar thermal driver (Niklas Söderlund) - Fix the TMSAR register for the TMUv2 on the Qoriq platform (Yuantian Tang) - Export GDDV, OEM vendor variables, and don't require IDSP for the int340x thermal driver - trivial conflicts fixed (Matthew Garrett) * tag 'thermal-v5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux: (48 commits) thermal/int340x_thermal: Don't require IDSP to exist thermal/int340x_thermal: Export OEM vendor variables thermal/int340x_thermal: Export GDDV thermal: qoriq: Update the settings for TMUv2 thermal: rcar_thermal: Clean up rcar_thermal_update_temp() thermal: qoriq: Add platform dependencies drivers: thermal: tsens: Merge tsens-common.c into tsens.c thermal/of: Rename of-thermal.c thermal/governors: Prefix all source files with gov_ thermal/drivers/user_space: Sort headers alphabetically thermal/drivers/of-thermal: Sort headers alphabetically thermal/drivers/cpufreq_cooling: Replace module.h with export.h thermal/drivers/cpufreq_cooling: Sort headers alphabetically thermal/drivers/clock_cooling: Include export.h thermal/drivers/clock_cooling: Sort headers alphabetically thermal/drivers/thermal_hwmon: Include export.h thermal/drivers/thermal_hwmon: Sort headers alphabetically thermal/drivers/thermal_helpers: Include export.h thermal/drivers/thermal_helpers: Sort headers alphabetically thermal/core: Replace module.h with export.h ...
This commit is contained in:
Коммит
df2fbf5bfa
|
@ -0,0 +1,116 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0)
|
||||
# Copyright 2020 Linaro Ltd.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/thermal-cooling-devices.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Thermal cooling device binding
|
||||
|
||||
maintainers:
|
||||
- Amit Kucheria <amitk@kernel.org>
|
||||
|
||||
description: |
|
||||
Thermal management is achieved in devicetree by describing the sensor hardware
|
||||
and the software abstraction of cooling devices and thermal zones required to
|
||||
take appropriate action to mitigate thermal overload.
|
||||
|
||||
The following node types are used to completely describe a thermal management
|
||||
system in devicetree:
|
||||
- thermal-sensor: device that measures temperature, has SoC-specific bindings
|
||||
- cooling-device: device used to dissipate heat either passively or actively
|
||||
- thermal-zones: a container of the following node types used to describe all
|
||||
thermal data for the platform
|
||||
|
||||
This binding describes the cooling devices.
|
||||
|
||||
There are essentially two ways to provide control on power dissipation:
|
||||
- Passive cooling: by means of regulating device performance. A typical
|
||||
passive cooling mechanism is a CPU that has dynamic voltage and frequency
|
||||
scaling (DVFS), and uses lower frequencies as cooling states.
|
||||
- Active cooling: by means of activating devices in order to remove the
|
||||
dissipated heat, e.g. regulating fan speeds.
|
||||
|
||||
Any cooling device has a range of cooling states (i.e. different levels of
|
||||
heat dissipation). They also have a way to determine the state of cooling in
|
||||
which the device is. For example, a fan's cooling states correspond to the
|
||||
different fan speeds possible. Cooling states are referred to by single
|
||||
unsigned integers, where larger numbers mean greater heat dissipation. The
|
||||
precise set of cooling states associated with a device should be defined in
|
||||
a particular device's binding.
|
||||
|
||||
select: true
|
||||
|
||||
properties:
|
||||
"#cooling-cells":
|
||||
description:
|
||||
Must be 2, in order to specify minimum and maximum cooling state used in
|
||||
the cooling-maps reference. The first cell is the minimum cooling state
|
||||
and the second cell is the maximum cooling state requested.
|
||||
const: 2
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/thermal/thermal.h>
|
||||
|
||||
// Example 1: Cpufreq cooling device on CPU0
|
||||
cpus {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <0>;
|
||||
|
||||
CPU0: cpu@0 {
|
||||
device_type = "cpu";
|
||||
compatible = "qcom,kryo385";
|
||||
reg = <0x0 0x0>;
|
||||
enable-method = "psci";
|
||||
cpu-idle-states = <&LITTLE_CPU_SLEEP_0
|
||||
&LITTLE_CPU_SLEEP_1
|
||||
&CLUSTER_SLEEP_0>;
|
||||
capacity-dmips-mhz = <607>;
|
||||
dynamic-power-coefficient = <100>;
|
||||
qcom,freq-domain = <&cpufreq_hw 0>;
|
||||
#cooling-cells = <2>;
|
||||
next-level-cache = <&L2_0>;
|
||||
L2_0: l2-cache {
|
||||
compatible = "cache";
|
||||
next-level-cache = <&L3_0>;
|
||||
L3_0: l3-cache {
|
||||
compatible = "cache";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
thermal-zones {
|
||||
cpu0-thermal {
|
||||
polling-delay-passive = <250>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
thermal-sensors = <&tsens0 1>;
|
||||
|
||||
trips {
|
||||
cpu0_alert0: trip-point0 {
|
||||
temperature = <90000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
};
|
||||
|
||||
cooling-maps {
|
||||
map0 {
|
||||
trip = <&cpu0_alert0>;
|
||||
/* Corresponds to 1000MHz in OPP table */
|
||||
cooling-device = <&CPU0 5 5>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* ... */
|
||||
};
|
||||
...
|
|
@ -0,0 +1,145 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
# Copyright 2020 Linaro Ltd.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/thermal-idle.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Thermal idle cooling device binding
|
||||
|
||||
maintainers:
|
||||
- Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
|
||||
description: |
|
||||
The thermal idle cooling device allows the system to passively
|
||||
mitigate the temperature on the device by injecting idle cycles,
|
||||
forcing it to cool down.
|
||||
|
||||
This binding describes the thermal idle node.
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
const: thermal-idle
|
||||
description: |
|
||||
A thermal-idle node describes the idle cooling device properties to
|
||||
cool down efficiently the attached thermal zone.
|
||||
|
||||
'#cooling-cells':
|
||||
const: 2
|
||||
description: |
|
||||
Must be 2, in order to specify minimum and maximum cooling state used in
|
||||
the cooling-maps reference. The first cell is the minimum cooling state
|
||||
and the second cell is the maximum cooling state requested.
|
||||
|
||||
duration-us:
|
||||
description: |
|
||||
The idle duration in microsecond the device should cool down.
|
||||
|
||||
exit-latency-us:
|
||||
description: |
|
||||
The exit latency constraint in microsecond for the injected
|
||||
idle state for the device. It is the latency constraint to
|
||||
apply when selecting an idle state from among all the present
|
||||
ones.
|
||||
|
||||
required:
|
||||
- '#cooling-cells'
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/thermal/thermal.h>
|
||||
|
||||
// Example: Combining idle cooling device on big CPUs with cpufreq cooling device
|
||||
cpus {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <0>;
|
||||
|
||||
/* ... */
|
||||
|
||||
cpu_b0: cpu@100 {
|
||||
device_type = "cpu";
|
||||
compatible = "arm,cortex-a72";
|
||||
reg = <0x0 0x100>;
|
||||
enable-method = "psci";
|
||||
capacity-dmips-mhz = <1024>;
|
||||
dynamic-power-coefficient = <436>;
|
||||
#cooling-cells = <2>; /* min followed by max */
|
||||
cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>;
|
||||
thermal-idle {
|
||||
#cooling-cells = <2>;
|
||||
duration-us = <10000>;
|
||||
exit-latency-us = <500>;
|
||||
};
|
||||
};
|
||||
|
||||
cpu_b1: cpu@101 {
|
||||
device_type = "cpu";
|
||||
compatible = "arm,cortex-a72";
|
||||
reg = <0x0 0x101>;
|
||||
enable-method = "psci";
|
||||
capacity-dmips-mhz = <1024>;
|
||||
dynamic-power-coefficient = <436>;
|
||||
#cooling-cells = <2>; /* min followed by max */
|
||||
cpu-idle-states = <&CPU_SLEEP &CLUSTER_SLEEP>;
|
||||
thermal-idle {
|
||||
#cooling-cells = <2>;
|
||||
duration-us = <10000>;
|
||||
exit-latency-us = <500>;
|
||||
};
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
thermal_zones {
|
||||
cpu_thermal: cpu {
|
||||
polling-delay-passive = <100>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
/* ... */
|
||||
|
||||
trips {
|
||||
cpu_alert0: cpu_alert0 {
|
||||
temperature = <65000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
|
||||
cpu_alert1: cpu_alert1 {
|
||||
temperature = <70000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
|
||||
cpu_alert2: cpu_alert2 {
|
||||
temperature = <75000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
|
||||
cpu_crit: cpu_crit {
|
||||
temperature = <95000>;
|
||||
hysteresis = <2000>;
|
||||
type = "critical";
|
||||
};
|
||||
};
|
||||
|
||||
cooling-maps {
|
||||
map0 {
|
||||
trip = <&cpu_alert1>;
|
||||
cooling-device = <&{/cpus/cpu@100/thermal-idle} 0 15 >,
|
||||
<&{/cpus/cpu@101/thermal-idle} 0 15>;
|
||||
};
|
||||
|
||||
map1 {
|
||||
trip = <&cpu_alert2>;
|
||||
cooling-device =
|
||||
<&cpu_b0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
|
||||
<&cpu_b1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0)
|
||||
# Copyright 2020 Linaro Ltd.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/thermal-sensor.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Thermal sensor binding
|
||||
|
||||
maintainers:
|
||||
- Amit Kucheria <amitk@kernel.org>
|
||||
|
||||
description: |
|
||||
Thermal management is achieved in devicetree by describing the sensor hardware
|
||||
and the software abstraction of thermal zones required to take appropriate
|
||||
action to mitigate thermal overloads.
|
||||
|
||||
The following node types are used to completely describe a thermal management
|
||||
system in devicetree:
|
||||
- thermal-sensor: device that measures temperature, has SoC-specific bindings
|
||||
- cooling-device: device used to dissipate heat either passively or actively
|
||||
- thermal-zones: a container of the following node types used to describe all
|
||||
thermal data for the platform
|
||||
|
||||
This binding describes the thermal-sensor.
|
||||
|
||||
Thermal sensor devices provide temperature sensing capabilities on thermal
|
||||
zones. Typical devices are I2C ADC converters and bandgaps. Thermal sensor
|
||||
devices may control one or more internal sensors.
|
||||
|
||||
properties:
|
||||
"#thermal-sensor-cells":
|
||||
description:
|
||||
Used to uniquely identify a thermal sensor instance within an IC. Will be
|
||||
0 on sensor nodes with only a single sensor and at least 1 on nodes
|
||||
containing several internal sensors.
|
||||
enum: [0, 1]
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
// Example 1: SDM845 TSENS
|
||||
soc: soc@0 {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
/* ... */
|
||||
|
||||
tsens0: thermal-sensor@c263000 {
|
||||
compatible = "qcom,sdm845-tsens", "qcom,tsens-v2";
|
||||
reg = <0 0x0c263000 0 0x1ff>, /* TM */
|
||||
<0 0x0c222000 0 0x1ff>; /* SROT */
|
||||
#qcom,sensors = <13>;
|
||||
interrupts = <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow", "critical";
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
tsens1: thermal-sensor@c265000 {
|
||||
compatible = "qcom,sdm845-tsens", "qcom,tsens-v2";
|
||||
reg = <0 0x0c265000 0 0x1ff>, /* TM */
|
||||
<0 0x0c223000 0 0x1ff>; /* SROT */
|
||||
#qcom,sensors = <8>;
|
||||
interrupts = <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow", "critical";
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
};
|
||||
...
|
|
@ -0,0 +1,341 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0)
|
||||
# Copyright 2020 Linaro Ltd.
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/thermal-zones.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/base.yaml#
|
||||
|
||||
title: Thermal zone binding
|
||||
|
||||
maintainers:
|
||||
- Amit Kucheria <amitk@kernel.org>
|
||||
|
||||
description: |
|
||||
Thermal management is achieved in devicetree by describing the sensor hardware
|
||||
and the software abstraction of cooling devices and thermal zones required to
|
||||
take appropriate action to mitigate thermal overloads.
|
||||
|
||||
The following node types are used to completely describe a thermal management
|
||||
system in devicetree:
|
||||
- thermal-sensor: device that measures temperature, has SoC-specific bindings
|
||||
- cooling-device: device used to dissipate heat either passively or actively
|
||||
- thermal-zones: a container of the following node types used to describe all
|
||||
thermal data for the platform
|
||||
|
||||
This binding describes the thermal-zones.
|
||||
|
||||
The polling-delay properties of a thermal-zone are bound to the maximum dT/dt
|
||||
(temperature derivative over time) in two situations for a thermal zone:
|
||||
1. when passive cooling is activated (polling-delay-passive)
|
||||
2. when the zone just needs to be monitored (polling-delay) or when
|
||||
active cooling is activated.
|
||||
|
||||
The maximum dT/dt is highly bound to hardware power consumption and
|
||||
dissipation capability. The delays should be chosen to account for said
|
||||
max dT/dt, such that a device does not cross several trip boundaries
|
||||
unexpectedly between polls. Choosing the right polling delays shall avoid
|
||||
having the device in temperature ranges that may damage the silicon structures
|
||||
and reduce silicon lifetime.
|
||||
|
||||
properties:
|
||||
$nodename:
|
||||
const: thermal-zones
|
||||
description:
|
||||
A /thermal-zones node is required in order to use the thermal framework to
|
||||
manage input from the various thermal zones in the system in order to
|
||||
mitigate thermal overload conditions. It does not represent a real device
|
||||
in the system, but acts as a container to link a thermal sensor device,
|
||||
platform-data regarding temperature thresholds and the mitigation actions
|
||||
to take when the temperature crosses those thresholds.
|
||||
|
||||
patternProperties:
|
||||
"^[a-zA-Z][a-zA-Z0-9\\-]{1,12}-thermal$":
|
||||
type: object
|
||||
description:
|
||||
Each thermal zone node contains information about how frequently it
|
||||
must be checked, the sensor responsible for reporting temperature for
|
||||
this zone, one sub-node containing the various trip points for this
|
||||
zone and one sub-node containing all the zone cooling-maps.
|
||||
|
||||
properties:
|
||||
polling-delay:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
The maximum number of milliseconds to wait between polls when
|
||||
checking this thermal zone. Setting this to 0 disables the polling
|
||||
timers setup by the thermal framework and assumes that the thermal
|
||||
sensors in this zone support interrupts.
|
||||
|
||||
polling-delay-passive:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
The maximum number of milliseconds to wait between polls when
|
||||
checking this thermal zone while doing passive cooling. Setting
|
||||
this to 0 disables the polling timers setup by the thermal
|
||||
framework and assumes that the thermal sensors in this zone
|
||||
support interrupts.
|
||||
|
||||
thermal-sensors:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle-array
|
||||
maxItems: 1
|
||||
description:
|
||||
The thermal sensor phandle and sensor specifier used to monitor this
|
||||
thermal zone.
|
||||
|
||||
coefficients:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
description:
|
||||
An array of integers containing the coefficients of a linear equation
|
||||
that binds all the sensors listed in this thermal zone.
|
||||
|
||||
The linear equation used is as follows,
|
||||
z = c0 * x0 + c1 * x1 + ... + c(n-1) * x(n-1) + cn
|
||||
where c0, c1, .., cn are the coefficients.
|
||||
|
||||
Coefficients default to 1 in case this property is not specified. The
|
||||
coefficients are ordered and are matched with sensors by means of the
|
||||
sensor ID. Additional coefficients are interpreted as constant offset.
|
||||
|
||||
sustainable-power:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
An estimate of the sustainable power (in mW) that this thermal zone
|
||||
can dissipate at the desired control temperature. For reference, the
|
||||
sustainable power of a 4-inch phone is typically 2000mW, while on a
|
||||
10-inch tablet is around 4500mW.
|
||||
|
||||
trips:
|
||||
type: object
|
||||
description:
|
||||
This node describes a set of points in the temperature domain at
|
||||
which the thermal framework needs to take action. The actions to
|
||||
be taken are defined in another node called cooling-maps.
|
||||
|
||||
patternProperties:
|
||||
"^[a-zA-Z][a-zA-Z0-9\\-_]{0,63}$":
|
||||
type: object
|
||||
|
||||
properties:
|
||||
temperature:
|
||||
$ref: /schemas/types.yaml#/definitions/int32
|
||||
minimum: -273000
|
||||
maximum: 200000
|
||||
description:
|
||||
An integer expressing the trip temperature in millicelsius.
|
||||
|
||||
hysteresis:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
description:
|
||||
An unsigned integer expressing the hysteresis delta with
|
||||
respect to the trip temperature property above, also in
|
||||
millicelsius. Any cooling action initiated by the framework is
|
||||
maintained until the temperature falls below
|
||||
(trip temperature - hysteresis). This potentially prevents a
|
||||
situation where the trip gets constantly triggered soon after
|
||||
cooling action is removed.
|
||||
|
||||
type:
|
||||
$ref: /schemas/types.yaml#/definitions/string
|
||||
enum:
|
||||
- active # enable active cooling e.g. fans
|
||||
- passive # enable passive cooling e.g. throttling cpu
|
||||
- hot # send notification to driver
|
||||
- critical # send notification to driver, trigger shutdown
|
||||
description: |
|
||||
There are four valid trip types: active, passive, hot,
|
||||
critical.
|
||||
|
||||
The critical trip type is used to set the maximum
|
||||
temperature threshold above which the HW becomes
|
||||
unstable and underlying firmware might even trigger a
|
||||
reboot. Hitting the critical threshold triggers a system
|
||||
shutdown.
|
||||
|
||||
The hot trip type can be used to send a notification to
|
||||
the thermal driver (if a .notify callback is registered).
|
||||
The action to be taken is left to the driver.
|
||||
|
||||
The passive trip type can be used to slow down HW e.g. run
|
||||
the CPU, GPU, bus at a lower frequency.
|
||||
|
||||
The active trip type can be used to control other HW to
|
||||
help in cooling e.g. fans can be sped up or slowed down
|
||||
|
||||
required:
|
||||
- temperature
|
||||
- hysteresis
|
||||
- type
|
||||
additionalProperties: false
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
cooling-maps:
|
||||
type: object
|
||||
description:
|
||||
This node describes the action to be taken when a thermal zone
|
||||
crosses one of the temperature thresholds described in the trips
|
||||
node. The action takes the form of a mapping relation between a
|
||||
trip and the target cooling device state.
|
||||
|
||||
patternProperties:
|
||||
"^map[-a-zA-Z0-9]*$":
|
||||
type: object
|
||||
|
||||
properties:
|
||||
trip:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle
|
||||
description:
|
||||
A phandle of a trip point node within this thermal zone.
|
||||
|
||||
cooling-device:
|
||||
$ref: /schemas/types.yaml#/definitions/phandle-array
|
||||
description:
|
||||
A list of cooling device phandles along with the minimum
|
||||
and maximum cooling state specifiers for each cooling
|
||||
device. Using the THERMAL_NO_LIMIT (-1UL) constant in the
|
||||
cooling-device phandle limit specifier lets the framework
|
||||
use the minimum and maximum cooling state for that cooling
|
||||
device automatically.
|
||||
|
||||
contribution:
|
||||
$ref: /schemas/types.yaml#/definitions/uint32
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
description:
|
||||
The percentage contribution of the cooling devices at the
|
||||
specific trip temperature referenced in this map
|
||||
to this thermal zone
|
||||
|
||||
required:
|
||||
- trip
|
||||
- cooling-device
|
||||
additionalProperties: false
|
||||
|
||||
required:
|
||||
- polling-delay
|
||||
- polling-delay-passive
|
||||
- thermal-sensors
|
||||
- trips
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
#include <dt-bindings/thermal/thermal.h>
|
||||
|
||||
// Example 1: SDM845 TSENS
|
||||
soc: soc@0 {
|
||||
#address-cells = <2>;
|
||||
#size-cells = <2>;
|
||||
|
||||
/* ... */
|
||||
|
||||
tsens0: thermal-sensor@c263000 {
|
||||
compatible = "qcom,sdm845-tsens", "qcom,tsens-v2";
|
||||
reg = <0 0x0c263000 0 0x1ff>, /* TM */
|
||||
<0 0x0c222000 0 0x1ff>; /* SROT */
|
||||
#qcom,sensors = <13>;
|
||||
interrupts = <GIC_SPI 506 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 508 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow", "critical";
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
tsens1: thermal-sensor@c265000 {
|
||||
compatible = "qcom,sdm845-tsens", "qcom,tsens-v2";
|
||||
reg = <0 0x0c265000 0 0x1ff>, /* TM */
|
||||
<0 0x0c223000 0 0x1ff>; /* SROT */
|
||||
#qcom,sensors = <8>;
|
||||
interrupts = <GIC_SPI 507 IRQ_TYPE_LEVEL_HIGH>,
|
||||
<GIC_SPI 509 IRQ_TYPE_LEVEL_HIGH>;
|
||||
interrupt-names = "uplow", "critical";
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
thermal-zones {
|
||||
cpu0-thermal {
|
||||
polling-delay-passive = <250>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
thermal-sensors = <&tsens0 1>;
|
||||
|
||||
trips {
|
||||
cpu0_alert0: trip-point0 {
|
||||
temperature = <90000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
|
||||
cpu0_alert1: trip-point1 {
|
||||
temperature = <95000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
|
||||
cpu0_crit: cpu_crit {
|
||||
temperature = <110000>;
|
||||
hysteresis = <1000>;
|
||||
type = "critical";
|
||||
};
|
||||
};
|
||||
|
||||
cooling-maps {
|
||||
map0 {
|
||||
trip = <&cpu0_alert0>;
|
||||
/* Corresponds to 1400MHz in OPP table */
|
||||
cooling-device = <&CPU0 3 3>, <&CPU1 3 3>,
|
||||
<&CPU2 3 3>, <&CPU3 3 3>;
|
||||
};
|
||||
|
||||
map1 {
|
||||
trip = <&cpu0_alert1>;
|
||||
/* Corresponds to 1000MHz in OPP table */
|
||||
cooling-device = <&CPU0 5 5>, <&CPU1 5 5>,
|
||||
<&CPU2 5 5>, <&CPU3 5 5>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
cluster0-thermal {
|
||||
polling-delay-passive = <250>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
thermal-sensors = <&tsens0 5>;
|
||||
|
||||
trips {
|
||||
cluster0_alert0: trip-point0 {
|
||||
temperature = <90000>;
|
||||
hysteresis = <2000>;
|
||||
type = "hot";
|
||||
};
|
||||
cluster0_crit: cluster0_crit {
|
||||
temperature = <110000>;
|
||||
hysteresis = <2000>;
|
||||
type = "critical";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/* ... */
|
||||
|
||||
gpu-top-thermal {
|
||||
polling-delay-passive = <250>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
thermal-sensors = <&tsens0 11>;
|
||||
|
||||
trips {
|
||||
gpu1_alert0: trip-point0 {
|
||||
temperature = <90000>;
|
||||
hysteresis = <2000>;
|
||||
type = "hot";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -0,0 +1,56 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/thermal/ti,am654-thermal.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Texas Instruments AM654 VTM (DTS) binding
|
||||
|
||||
maintainers:
|
||||
- Keerthy <j-keerthy@ti.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: ti,am654-vtm
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
||||
"#thermal-sensor-cells":
|
||||
const: 1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- power-domains
|
||||
- "#thermal-sensor-cells"
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/soc/ti,sci_pm_domain.h>
|
||||
vtm: thermal@42050000 {
|
||||
compatible = "ti,am654-vtm";
|
||||
reg = <0x0 0x42050000 0x0 0x25c>;
|
||||
power-domains = <&k3_pds 80 TI_SCI_PD_EXCLUSIVE>;
|
||||
#thermal-sensor-cells = <1>;
|
||||
};
|
||||
|
||||
mpu0_thermal: mpu0_thermal {
|
||||
polling-delay-passive = <250>; /* milliseconds */
|
||||
polling-delay = <500>; /* milliseconds */
|
||||
thermal-sensors = <&vtm0 0>;
|
||||
|
||||
trips {
|
||||
mpu0_crit: mpu0_crit {
|
||||
temperature = <125000>; /* milliCelsius */
|
||||
hysteresis = <2000>; /* milliCelsius */
|
||||
type = "critical";
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -14556,6 +14556,15 @@ F: Documentation/devicetree/bindings/i2c/renesas,iic.txt
|
|||
F: drivers/i2c/busses/i2c-rcar.c
|
||||
F: drivers/i2c/busses/i2c-sh_mobile.c
|
||||
|
||||
RENESAS R-CAR THERMAL DRIVERS
|
||||
M: Niklas Söderlund <niklas.soderlund@ragnatech.se>
|
||||
L: linux-renesas-soc@vger.kernel.org
|
||||
S: Supported
|
||||
F: Documentation/devicetree/bindings/thermal/rcar-gen3-thermal.txt
|
||||
F: Documentation/devicetree/bindings/thermal/rcar-thermal.txt
|
||||
F: drivers/thermal/rcar_gen3_thermal.c
|
||||
F: drivers/thermal/rcar_thermal.c
|
||||
|
||||
RENESAS RIIC DRIVER
|
||||
M: Chris Brandt <chris.brandt@renesas.com>
|
||||
S: Supported
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#define pr_fmt(fmt) "CPUidle arm: " fmt
|
||||
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
|
@ -124,6 +125,8 @@ static int __init arm_idle_init_cpu(int cpu)
|
|||
if (ret)
|
||||
goto out_kfree_drv;
|
||||
|
||||
cpuidle_cooling_register(drv);
|
||||
|
||||
return 0;
|
||||
|
||||
out_kfree_drv:
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#define pr_fmt(fmt) "CPUidle PSCI: " fmt
|
||||
|
||||
#include <linux/cpuhotplug.h>
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/cpu_pm.h>
|
||||
|
@ -319,6 +320,8 @@ static int __init psci_idle_init_cpu(int cpu)
|
|||
if (ret)
|
||||
goto out_kfree_drv;
|
||||
|
||||
cpuidle_cooling_register(drv);
|
||||
|
||||
return 0;
|
||||
|
||||
out_kfree_drv:
|
||||
|
|
|
@ -61,12 +61,14 @@ struct idle_inject_thread {
|
|||
* @timer: idle injection period timer
|
||||
* @idle_duration_us: duration of CPU idle time to inject
|
||||
* @run_duration_us: duration of CPU run time to allow
|
||||
* @latency_us: max allowed latency
|
||||
* @cpumask: mask of CPUs affected by idle injection
|
||||
*/
|
||||
struct idle_inject_device {
|
||||
struct hrtimer timer;
|
||||
unsigned int idle_duration_us;
|
||||
unsigned int run_duration_us;
|
||||
unsigned int latency_us;
|
||||
unsigned long cpumask[];
|
||||
};
|
||||
|
||||
|
@ -138,7 +140,8 @@ static void idle_inject_fn(unsigned int cpu)
|
|||
*/
|
||||
iit->should_run = 0;
|
||||
|
||||
play_idle(READ_ONCE(ii_dev->idle_duration_us));
|
||||
play_idle_precise(READ_ONCE(ii_dev->idle_duration_us) * NSEC_PER_USEC,
|
||||
READ_ONCE(ii_dev->latency_us) * NSEC_PER_USEC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,6 +172,16 @@ void idle_inject_get_duration(struct idle_inject_device *ii_dev,
|
|||
*idle_duration_us = READ_ONCE(ii_dev->idle_duration_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_set_latency - set the maximum latency allowed
|
||||
* @latency_us: set the latency requirement for the idle state
|
||||
*/
|
||||
void idle_inject_set_latency(struct idle_inject_device *ii_dev,
|
||||
unsigned int latency_us)
|
||||
{
|
||||
WRITE_ONCE(ii_dev->latency_us, latency_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* idle_inject_start - start idle injections
|
||||
* @ii_dev: idle injection control device structure
|
||||
|
@ -297,6 +310,7 @@ struct idle_inject_device *idle_inject_register(struct cpumask *cpumask)
|
|||
cpumask_copy(to_cpumask(ii_dev->cpumask), cpumask);
|
||||
hrtimer_init(&ii_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
ii_dev->timer.function = idle_inject_timer_fn;
|
||||
ii_dev->latency_us = UINT_MAX;
|
||||
|
||||
for_each_cpu(cpu, to_cpumask(ii_dev->cpumask)) {
|
||||
|
||||
|
|
|
@ -273,6 +273,16 @@ config IMX8MM_THERMAL
|
|||
cpufreq is used as the cooling device to throttle CPUs when the passive
|
||||
trip is crossed.
|
||||
|
||||
config K3_THERMAL
|
||||
tristate "Texas Instruments K3 thermal support"
|
||||
depends on ARCH_K3 || COMPILE_TEST
|
||||
help
|
||||
If you say yes here you get thermal support for the Texas Instruments
|
||||
K3 SoC family. The current chip supported is:
|
||||
- AM654
|
||||
|
||||
This includes temperature reading functionality.
|
||||
|
||||
config MAX77620_THERMAL
|
||||
tristate "Temperature sensor driver for Maxim MAX77620 PMIC"
|
||||
depends on MFD_MAX77620
|
||||
|
@ -285,8 +295,8 @@ config MAX77620_THERMAL
|
|||
|
||||
config QORIQ_THERMAL
|
||||
tristate "QorIQ Thermal Monitoring Unit"
|
||||
depends on THERMAL_OF
|
||||
depends on HAS_IOMEM
|
||||
depends on THERMAL_OF && HAS_IOMEM
|
||||
depends on PPC_E500MC || SOC_LS1021A || ARCH_LAYERSCAPE || (ARCH_MXC && ARM64) || COMPILE_TEST
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
Support for Thermal Monitoring Unit (TMU) found on QorIQ platforms.
|
||||
|
|
|
@ -9,14 +9,14 @@ thermal_sys-y += thermal_core.o thermal_sysfs.o \
|
|||
|
||||
# interface to/from other layers providing sensors
|
||||
thermal_sys-$(CONFIG_THERMAL_HWMON) += thermal_hwmon.o
|
||||
thermal_sys-$(CONFIG_THERMAL_OF) += of-thermal.o
|
||||
thermal_sys-$(CONFIG_THERMAL_OF) += thermal_of.o
|
||||
|
||||
# governors
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += gov_fair_share.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += gov_step_wise.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += gov_user_space.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += gov_power_allocator.o
|
||||
|
||||
# cpufreq cooling
|
||||
thermal_sys-$(CONFIG_CPU_FREQ_THERMAL) += cpufreq_cooling.o
|
||||
|
@ -28,6 +28,7 @@ thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
|
|||
# devfreq cooling
|
||||
thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
|
||||
|
||||
obj-$(CONFIG_K3_THERMAL) += k3_bandgap.o
|
||||
# platform thermal drivers
|
||||
obj-y += broadcom/
|
||||
obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o
|
||||
|
|
|
@ -12,15 +12,16 @@
|
|||
* Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clock_cooling.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/clock_cooling.h>
|
||||
|
||||
/**
|
||||
* struct clock_cooling_device - data for cooling device with clock
|
||||
|
|
|
@ -10,17 +10,17 @@
|
|||
* Viresh Kumar <viresh.kumar@linaro.org>
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/energy_model.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/energy_model.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#include <trace/events/thermal.h>
|
||||
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*
|
||||
*/
|
||||
#define pr_fmt(fmt) "cpuidle cooling: " fmt
|
||||
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/cpuidle.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/idle_inject.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
|
@ -154,22 +157,25 @@ static struct thermal_cooling_device_ops cpuidle_cooling_ops = {
|
|||
};
|
||||
|
||||
/**
|
||||
* cpuidle_of_cooling_register - Idle cooling device initialization function
|
||||
* __cpuidle_cooling_register: register the cooling device
|
||||
* @drv: a cpuidle driver structure pointer
|
||||
* @np: a node pointer to a device tree cooling device node
|
||||
* @np: a device node structure pointer used for the thermal binding
|
||||
*
|
||||
* This function is in charge of creating a cooling device per cpuidle
|
||||
* driver and register it to thermal framework.
|
||||
* This function is in charge of allocating the cpuidle cooling device
|
||||
* structure, the idle injection, initialize them and register the
|
||||
* cooling device to the thermal framework.
|
||||
*
|
||||
* Return: zero on success, or negative value corresponding to the
|
||||
* error detected in the underlying subsystems.
|
||||
* Return: zero on success, a negative value returned by one of the
|
||||
* underlying subsystem in case of error
|
||||
*/
|
||||
int cpuidle_of_cooling_register(struct device_node *np,
|
||||
static int __cpuidle_cooling_register(struct device_node *np,
|
||||
struct cpuidle_driver *drv)
|
||||
{
|
||||
struct idle_inject_device *ii_dev;
|
||||
struct cpuidle_cooling_device *idle_cdev;
|
||||
struct thermal_cooling_device *cdev;
|
||||
unsigned int idle_duration_us = TICK_USEC;
|
||||
unsigned int latency_us = UINT_MAX;
|
||||
char dev_name[THERMAL_NAME_LENGTH];
|
||||
int id, ret;
|
||||
|
||||
|
@ -191,7 +197,11 @@ int cpuidle_of_cooling_register(struct device_node *np,
|
|||
goto out_id;
|
||||
}
|
||||
|
||||
idle_inject_set_duration(ii_dev, TICK_USEC, TICK_USEC);
|
||||
of_property_read_u32(np, "duration-us", &idle_duration_us);
|
||||
of_property_read_u32(np, "exit-latency-us", &latency_us);
|
||||
|
||||
idle_inject_set_duration(ii_dev, TICK_USEC, idle_duration_us);
|
||||
idle_inject_set_latency(ii_dev, latency_us);
|
||||
|
||||
idle_cdev->ii_dev = ii_dev;
|
||||
|
||||
|
@ -204,6 +214,9 @@ int cpuidle_of_cooling_register(struct device_node *np,
|
|||
goto out_unregister;
|
||||
}
|
||||
|
||||
pr_debug("%s: Idle injection set with idle duration=%u, latency=%u\n",
|
||||
dev_name, idle_duration_us, latency_us);
|
||||
|
||||
return 0;
|
||||
|
||||
out_unregister:
|
||||
|
@ -221,12 +234,38 @@ out:
|
|||
* @drv: a cpuidle driver structure pointer
|
||||
*
|
||||
* This function is in charge of creating a cooling device per cpuidle
|
||||
* driver and register it to thermal framework.
|
||||
* driver and register it to the thermal framework.
|
||||
*
|
||||
* Return: zero on success, or negative value corresponding to the
|
||||
* error detected in the underlying subsystems.
|
||||
*/
|
||||
int cpuidle_cooling_register(struct cpuidle_driver *drv)
|
||||
void cpuidle_cooling_register(struct cpuidle_driver *drv)
|
||||
{
|
||||
return cpuidle_of_cooling_register(NULL, drv);
|
||||
struct device_node *cooling_node;
|
||||
struct device_node *cpu_node;
|
||||
int cpu, ret;
|
||||
|
||||
for_each_cpu(cpu, drv->cpumask) {
|
||||
|
||||
cpu_node = of_cpu_device_node_get(cpu);
|
||||
|
||||
cooling_node = of_get_child_by_name(cpu_node, "thermal-idle");
|
||||
|
||||
of_node_put(cpu_node);
|
||||
|
||||
if (!cooling_node) {
|
||||
pr_debug("'thermal-idle' node not found for cpu%d\n", cpu);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = __cpuidle_cooling_register(cooling_node, drv);
|
||||
|
||||
of_node_put(cooling_node);
|
||||
|
||||
if (ret) {
|
||||
pr_err("Failed to register the cpuidle cooling device" \
|
||||
"for cpu%d: %d\n", cpu, ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,12 @@
|
|||
#include <linux/idr.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/pm_qos.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#include <trace/events/thermal.h>
|
||||
|
||||
#define HZ_PER_KHZ 1000
|
||||
#define SCALE_ERROR_MITIGATION 100
|
||||
|
||||
static DEFINE_IDA(devfreq_ida);
|
||||
|
@ -54,6 +56,8 @@ static DEFINE_IDA(devfreq_ida);
|
|||
* The 'res_util' range is from 100 to (power_table[state] * 100)
|
||||
* for the corresponding 'state'.
|
||||
* @capped_state: index to cooling state with in dynamic power budget
|
||||
* @req_max_freq: PM QoS request for limiting the maximum frequency
|
||||
* of the devfreq device.
|
||||
*/
|
||||
struct devfreq_cooling_device {
|
||||
int id;
|
||||
|
@ -66,49 +70,9 @@ struct devfreq_cooling_device {
|
|||
struct devfreq_cooling_power *power_ops;
|
||||
u32 res_util;
|
||||
int capped_state;
|
||||
struct dev_pm_qos_request req_max_freq;
|
||||
};
|
||||
|
||||
/**
|
||||
* partition_enable_opps() - disable all opps above a given state
|
||||
* @dfc: Pointer to devfreq we are operating on
|
||||
* @cdev_state: cooling device state we're setting
|
||||
*
|
||||
* Go through the OPPs of the device, enabling all OPPs until
|
||||
* @cdev_state and disabling those frequencies above it.
|
||||
*/
|
||||
static int partition_enable_opps(struct devfreq_cooling_device *dfc,
|
||||
unsigned long cdev_state)
|
||||
{
|
||||
int i;
|
||||
struct device *dev = dfc->devfreq->dev.parent;
|
||||
|
||||
for (i = 0; i < dfc->freq_table_size; i++) {
|
||||
struct dev_pm_opp *opp;
|
||||
int ret = 0;
|
||||
unsigned int freq = dfc->freq_table[i];
|
||||
bool want_enable = i >= cdev_state ? true : false;
|
||||
|
||||
opp = dev_pm_opp_find_freq_exact(dev, freq, !want_enable);
|
||||
|
||||
if (PTR_ERR(opp) == -ERANGE)
|
||||
continue;
|
||||
else if (IS_ERR(opp))
|
||||
return PTR_ERR(opp);
|
||||
|
||||
dev_pm_opp_put(opp);
|
||||
|
||||
if (want_enable)
|
||||
ret = dev_pm_opp_enable(dev, freq);
|
||||
else
|
||||
ret = dev_pm_opp_disable(dev, freq);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev,
|
||||
unsigned long *state)
|
||||
{
|
||||
|
@ -135,7 +99,7 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
|
|||
struct devfreq_cooling_device *dfc = cdev->devdata;
|
||||
struct devfreq *df = dfc->devfreq;
|
||||
struct device *dev = df->dev.parent;
|
||||
int ret;
|
||||
unsigned long freq;
|
||||
|
||||
if (state == dfc->cooling_state)
|
||||
return 0;
|
||||
|
@ -145,9 +109,10 @@ static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev,
|
|||
if (state >= dfc->freq_table_size)
|
||||
return -EINVAL;
|
||||
|
||||
ret = partition_enable_opps(dfc, state);
|
||||
if (ret)
|
||||
return ret;
|
||||
freq = dfc->freq_table[state];
|
||||
|
||||
dev_pm_qos_update_request(&dfc->req_max_freq,
|
||||
DIV_ROUND_UP(freq, HZ_PER_KHZ));
|
||||
|
||||
dfc->cooling_state = state;
|
||||
|
||||
|
@ -530,9 +495,15 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
|
|||
if (err)
|
||||
goto free_dfc;
|
||||
|
||||
err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL);
|
||||
err = dev_pm_qos_add_request(df->dev.parent, &dfc->req_max_freq,
|
||||
DEV_PM_QOS_MAX_FREQUENCY,
|
||||
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
|
||||
if (err < 0)
|
||||
goto free_tables;
|
||||
|
||||
err = ida_simple_get(&devfreq_ida, 0, 0, GFP_KERNEL);
|
||||
if (err < 0)
|
||||
goto remove_qos_req;
|
||||
dfc->id = err;
|
||||
|
||||
snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d", dfc->id);
|
||||
|
@ -553,6 +524,10 @@ of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df,
|
|||
|
||||
release_ida:
|
||||
ida_simple_remove(&devfreq_ida, dfc->id);
|
||||
|
||||
remove_qos_req:
|
||||
dev_pm_qos_remove_request(&dfc->req_max_freq);
|
||||
|
||||
free_tables:
|
||||
kfree(dfc->power_table);
|
||||
kfree(dfc->freq_table);
|
||||
|
@ -601,6 +576,7 @@ void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
|||
|
||||
thermal_cooling_device_unregister(dfc->cdev);
|
||||
ida_simple_remove(&devfreq_ida, dfc->id);
|
||||
dev_pm_qos_remove_request(&dfc->req_max_freq);
|
||||
kfree(dfc->power_table);
|
||||
kfree(dfc->freq_table);
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
*/
|
||||
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
|
|
@ -54,7 +54,7 @@ struct imx8mm_tmu {
|
|||
void __iomem *base;
|
||||
struct clk *clk;
|
||||
const struct thermal_soc_data *socdata;
|
||||
struct tmu_sensor sensors[0];
|
||||
struct tmu_sensor sensors[];
|
||||
};
|
||||
|
||||
static int imx8mm_tmu_get_temp(void *data, int *temp)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <linux/thermal.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
#include "thermal_hwmon.h"
|
||||
|
||||
#define IMX_SC_MISC_FUNC_GET_TEMP 13
|
||||
|
||||
|
@ -115,6 +116,9 @@ static int imx_sc_thermal_probe(struct platform_device *pdev)
|
|||
ret = PTR_ERR(sensor->tzd);
|
||||
break;
|
||||
}
|
||||
|
||||
if (devm_thermal_add_hwmon_sysfs(sensor->tzd))
|
||||
dev_warn(&pdev->dev, "failed to add hwmon sysfs attributes\n");
|
||||
}
|
||||
|
||||
of_node_put(sensor_np);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "acpi_thermal_rel.h"
|
||||
|
||||
#define INT3400_THERMAL_TABLE_CHANGED 0x83
|
||||
#define INT3400_ODVP_CHANGED 0x88
|
||||
|
||||
enum int3400_thermal_uuid {
|
||||
INT3400_THERMAL_PASSIVE_1,
|
||||
|
@ -41,8 +42,11 @@ static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
|
|||
"BE84BABF-C4D4-403D-B495-3128FD44dAC1",
|
||||
};
|
||||
|
||||
struct odvp_attr;
|
||||
|
||||
struct int3400_thermal_priv {
|
||||
struct acpi_device *adev;
|
||||
struct platform_device *pdev;
|
||||
struct thermal_zone_device *thermal;
|
||||
int mode;
|
||||
int art_count;
|
||||
|
@ -52,6 +56,36 @@ struct int3400_thermal_priv {
|
|||
u8 uuid_bitmap;
|
||||
int rel_misc_dev_res;
|
||||
int current_uuid_index;
|
||||
char *data_vault;
|
||||
int odvp_count;
|
||||
int *odvp;
|
||||
struct odvp_attr *odvp_attrs;
|
||||
};
|
||||
|
||||
static int evaluate_odvp(struct int3400_thermal_priv *priv);
|
||||
|
||||
struct odvp_attr {
|
||||
int odvp;
|
||||
struct int3400_thermal_priv *priv;
|
||||
struct kobj_attribute attr;
|
||||
};
|
||||
|
||||
static ssize_t data_vault_read(struct file *file, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf, loff_t off, size_t count)
|
||||
{
|
||||
memcpy(buf, attr->private + off, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static BIN_ATTR_RO(data_vault, 0);
|
||||
|
||||
static struct bin_attribute *data_attributes[] = {
|
||||
&bin_attr_data_vault,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group data_attribute_group = {
|
||||
.bin_attrs = data_attributes,
|
||||
};
|
||||
|
||||
static ssize_t available_uuids_show(struct device *dev,
|
||||
|
@ -62,6 +96,9 @@ static ssize_t available_uuids_show(struct device *dev,
|
|||
int i;
|
||||
int length = 0;
|
||||
|
||||
if (!priv->uuid_bitmap)
|
||||
return sprintf(buf, "UNKNOWN\n");
|
||||
|
||||
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
|
||||
if (priv->uuid_bitmap & (1 << i))
|
||||
if (PAGE_SIZE - length > 0)
|
||||
|
@ -79,11 +116,11 @@ static ssize_t current_uuid_show(struct device *dev,
|
|||
{
|
||||
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
|
||||
|
||||
if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
|
||||
if (priv->current_uuid_index == -1)
|
||||
return sprintf(buf, "INVALID\n");
|
||||
|
||||
return sprintf(buf, "%s\n",
|
||||
int3400_thermal_uuids[priv->current_uuid_index]);
|
||||
else
|
||||
return sprintf(buf, "INVALID\n");
|
||||
}
|
||||
|
||||
static ssize_t current_uuid_store(struct device *dev,
|
||||
|
@ -94,9 +131,16 @@ static ssize_t current_uuid_store(struct device *dev,
|
|||
int i;
|
||||
|
||||
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
|
||||
if ((priv->uuid_bitmap & (1 << i)) &&
|
||||
!(strncmp(buf, int3400_thermal_uuids[i],
|
||||
sizeof(int3400_thermal_uuids[i]) - 1))) {
|
||||
if (!strncmp(buf, int3400_thermal_uuids[i],
|
||||
sizeof(int3400_thermal_uuids[i]) - 1)) {
|
||||
/*
|
||||
* If we have a list of supported UUIDs, make sure
|
||||
* this one is supported.
|
||||
*/
|
||||
if (priv->uuid_bitmap &&
|
||||
!(priv->uuid_bitmap & (1 << i)))
|
||||
return -EINVAL;
|
||||
|
||||
priv->current_uuid_index = i;
|
||||
return count;
|
||||
}
|
||||
|
@ -191,9 +235,110 @@ static int int3400_thermal_run_osc(acpi_handle handle,
|
|||
result = -EPERM;
|
||||
|
||||
kfree(context.ret.pointer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct odvp_attr *odvp_attr;
|
||||
|
||||
odvp_attr = container_of(attr, struct odvp_attr, attr);
|
||||
|
||||
return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
|
||||
}
|
||||
|
||||
static void cleanup_odvp(struct int3400_thermal_priv *priv)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (priv->odvp_attrs) {
|
||||
for (i = 0; i < priv->odvp_count; i++) {
|
||||
sysfs_remove_file(&priv->pdev->dev.kobj,
|
||||
&priv->odvp_attrs[i].attr.attr);
|
||||
kfree(priv->odvp_attrs[i].attr.attr.name);
|
||||
}
|
||||
kfree(priv->odvp_attrs);
|
||||
}
|
||||
kfree(priv->odvp);
|
||||
priv->odvp_count = 0;
|
||||
}
|
||||
|
||||
static int evaluate_odvp(struct int3400_thermal_priv *priv)
|
||||
{
|
||||
struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj = NULL;
|
||||
acpi_status status;
|
||||
int i, ret;
|
||||
|
||||
status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
ret = -EINVAL;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
obj = odvp.pointer;
|
||||
if (obj->type != ACPI_TYPE_PACKAGE) {
|
||||
ret = -EINVAL;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
if (priv->odvp == NULL) {
|
||||
priv->odvp_count = obj->package.count;
|
||||
priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),
|
||||
GFP_KERNEL);
|
||||
if (!priv->odvp) {
|
||||
ret = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->odvp_attrs == NULL) {
|
||||
priv->odvp_attrs = kcalloc(priv->odvp_count,
|
||||
sizeof(struct odvp_attr),
|
||||
GFP_KERNEL);
|
||||
if (!priv->odvp_attrs) {
|
||||
ret = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
for (i = 0; i < priv->odvp_count; i++) {
|
||||
struct odvp_attr *odvp = &priv->odvp_attrs[i];
|
||||
|
||||
sysfs_attr_init(&odvp->attr.attr);
|
||||
odvp->priv = priv;
|
||||
odvp->odvp = i;
|
||||
odvp->attr.attr.name = kasprintf(GFP_KERNEL,
|
||||
"odvp%d", i);
|
||||
|
||||
if (!odvp->attr.attr.name) {
|
||||
ret = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
odvp->attr.attr.mode = 0444;
|
||||
odvp->attr.show = odvp_show;
|
||||
odvp->attr.store = NULL;
|
||||
ret = sysfs_create_file(&priv->pdev->dev.kobj,
|
||||
&odvp->attr.attr);
|
||||
if (ret)
|
||||
goto out_err;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < obj->package.count; i++) {
|
||||
if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
|
||||
priv->odvp[i] = obj->package.elements[i].integer.value;
|
||||
}
|
||||
|
||||
kfree(obj);
|
||||
return 0;
|
||||
|
||||
out_err:
|
||||
cleanup_odvp(priv);
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void int3400_notify(acpi_handle handle,
|
||||
u32 event,
|
||||
void *data)
|
||||
|
@ -217,6 +362,9 @@ static void int3400_notify(acpi_handle handle,
|
|||
kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE,
|
||||
thermal_prop);
|
||||
break;
|
||||
case INT3400_ODVP_CHANGED:
|
||||
evaluate_odvp(priv);
|
||||
break;
|
||||
default:
|
||||
/* Ignore unknown notification codes sent to INT3400 device */
|
||||
break;
|
||||
|
@ -266,11 +414,16 @@ static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
|
|||
priv->current_uuid_index,
|
||||
enable);
|
||||
}
|
||||
|
||||
evaluate_odvp(priv);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct thermal_zone_device_ops int3400_thermal_ops = {
|
||||
.get_temp = int3400_thermal_get_temp,
|
||||
.get_mode = int3400_thermal_get_mode,
|
||||
.set_mode = int3400_thermal_set_mode,
|
||||
};
|
||||
|
||||
static struct thermal_zone_params int3400_thermal_params = {
|
||||
|
@ -278,6 +431,32 @@ static struct thermal_zone_params int3400_thermal_params = {
|
|||
.no_hwmon = true,
|
||||
};
|
||||
|
||||
static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
|
||||
{
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
|
||||
&buffer);
|
||||
if (ACPI_FAILURE(status) || !buffer.length)
|
||||
return;
|
||||
|
||||
obj = buffer.pointer;
|
||||
if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
|
||||
|| obj->package.elements[0].type != ACPI_TYPE_BUFFER) {
|
||||
kfree(buffer.pointer);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
|
||||
obj->package.elements[0].buffer.length,
|
||||
GFP_KERNEL);
|
||||
bin_attr_data_vault.private = priv->data_vault;
|
||||
bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
|
||||
kfree(buffer.pointer);
|
||||
}
|
||||
|
||||
static int int3400_thermal_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
|
||||
|
@ -291,12 +470,17 @@ static int int3400_thermal_probe(struct platform_device *pdev)
|
|||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->pdev = pdev;
|
||||
priv->adev = adev;
|
||||
|
||||
result = int3400_thermal_get_uuids(priv);
|
||||
if (result)
|
||||
|
||||
/* Missing IDSP isn't fatal */
|
||||
if (result && result != -ENODEV)
|
||||
goto free_priv;
|
||||
|
||||
priv->current_uuid_index = -1;
|
||||
|
||||
result = acpi_parse_art(priv->adev->handle, &priv->art_count,
|
||||
&priv->arts, true);
|
||||
if (result)
|
||||
|
@ -309,8 +493,9 @@ static int int3400_thermal_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
int3400_thermal_ops.get_mode = int3400_thermal_get_mode;
|
||||
int3400_thermal_ops.set_mode = int3400_thermal_set_mode;
|
||||
int3400_setup_gddv(priv);
|
||||
|
||||
evaluate_odvp(priv);
|
||||
|
||||
priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
|
||||
priv, &int3400_thermal_ops,
|
||||
|
@ -327,6 +512,13 @@ static int int3400_thermal_probe(struct platform_device *pdev)
|
|||
if (result)
|
||||
goto free_rel_misc;
|
||||
|
||||
if (priv->data_vault) {
|
||||
result = sysfs_create_group(&pdev->dev.kobj,
|
||||
&data_attribute_group);
|
||||
if (result)
|
||||
goto free_uuid;
|
||||
}
|
||||
|
||||
result = acpi_install_notify_handler(
|
||||
priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
|
||||
(void *)priv);
|
||||
|
@ -336,6 +528,12 @@ static int int3400_thermal_probe(struct platform_device *pdev)
|
|||
return 0;
|
||||
|
||||
free_sysfs:
|
||||
cleanup_odvp(priv);
|
||||
if (priv->data_vault) {
|
||||
sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
|
||||
kfree(priv->data_vault);
|
||||
}
|
||||
free_uuid:
|
||||
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
|
||||
free_rel_misc:
|
||||
if (!priv->rel_misc_dev_res)
|
||||
|
@ -357,11 +555,16 @@ static int int3400_thermal_remove(struct platform_device *pdev)
|
|||
priv->adev->handle, ACPI_DEVICE_NOTIFY,
|
||||
int3400_notify);
|
||||
|
||||
cleanup_odvp(priv);
|
||||
|
||||
if (!priv->rel_misc_dev_res)
|
||||
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
|
||||
|
||||
if (priv->data_vault)
|
||||
sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
|
||||
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
|
||||
thermal_zone_device_unregister(priv->thermal);
|
||||
kfree(priv->data_vault);
|
||||
kfree(priv->trts);
|
||||
kfree(priv->arts);
|
||||
kfree(priv);
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* TI Bandgap temperature sensor driver for K3 SoC Family
|
||||
*
|
||||
* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define K3_VTM_DEVINFO_PWR0_OFFSET 0x4
|
||||
#define K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK 0xf0
|
||||
#define K3_VTM_TMPSENS0_CTRL_OFFSET 0x80
|
||||
#define K3_VTM_REGS_PER_TS 0x10
|
||||
#define K3_VTM_TS_STAT_DTEMP_MASK 0x3ff
|
||||
#define K3_VTM_TMPSENS_CTRL_CBIASSEL BIT(0)
|
||||
#define K3_VTM_TMPSENS_CTRL_SOC BIT(5)
|
||||
#define K3_VTM_TMPSENS_CTRL_CLRZ BIT(6)
|
||||
#define K3_VTM_TMPSENS_CTRL_CLKON_REQ BIT(7)
|
||||
|
||||
#define K3_VTM_ADC_BEGIN_VAL 540
|
||||
#define K3_VTM_ADC_END_VAL 944
|
||||
|
||||
static const int k3_adc_to_temp[] = {
|
||||
-40000, -40000, -40000, -40000, -39800, -39400, -39000, -38600, -38200,
|
||||
-37800, -37400, -37000, -36600, -36200, -35800, -35300, -34700, -34200,
|
||||
-33800, -33400, -33000, -32600, -32200, -31800, -31400, -31000, -30600,
|
||||
-30200, -29800, -29400, -29000, -28600, -28200, -27700, -27100, -26600,
|
||||
-26200, -25800, -25400, -25000, -24600, -24200, -23800, -23400, -23000,
|
||||
-22600, -22200, -21800, -21400, -21000, -20500, -19900, -19400, -19000,
|
||||
-18600, -18200, -17800, -17400, -17000, -16600, -16200, -15800, -15400,
|
||||
-15000, -14600, -14200, -13800, -13400, -13000, -12500, -11900, -11400,
|
||||
-11000, -10600, -10200, -9800, -9400, -9000, -8600, -8200, -7800, -7400,
|
||||
-7000, -6600, -6200, -5800, -5400, -5000, -4500, -3900, -3400, -3000,
|
||||
-2600, -2200, -1800, -1400, -1000, -600, -200, 200, 600, 1000, 1400,
|
||||
1800, 2200, 2600, 3000, 3400, 3900, 4500, 5000, 5400, 5800, 6200, 6600,
|
||||
7000, 7400, 7800, 8200, 8600, 9000, 9400, 9800, 10200, 10600, 11000,
|
||||
11400, 11800, 12200, 12700, 13300, 13800, 14200, 14600, 15000, 15400,
|
||||
15800, 16200, 16600, 17000, 17400, 17800, 18200, 18600, 19000, 19400,
|
||||
19800, 20200, 20600, 21000, 21400, 21900, 22500, 23000, 23400, 23800,
|
||||
24200, 24600, 25000, 25400, 25800, 26200, 26600, 27000, 27400, 27800,
|
||||
28200, 28600, 29000, 29400, 29800, 30200, 30600, 31000, 31400, 31900,
|
||||
32500, 33000, 33400, 33800, 34200, 34600, 35000, 35400, 35800, 36200,
|
||||
36600, 37000, 37400, 37800, 38200, 38600, 39000, 39400, 39800, 40200,
|
||||
40600, 41000, 41400, 41800, 42200, 42600, 43100, 43700, 44200, 44600,
|
||||
45000, 45400, 45800, 46200, 46600, 47000, 47400, 47800, 48200, 48600,
|
||||
49000, 49400, 49800, 50200, 50600, 51000, 51400, 51800, 52200, 52600,
|
||||
53000, 53400, 53800, 54200, 54600, 55000, 55400, 55900, 56500, 57000,
|
||||
57400, 57800, 58200, 58600, 59000, 59400, 59800, 60200, 60600, 61000,
|
||||
61400, 61800, 62200, 62600, 63000, 63400, 63800, 64200, 64600, 65000,
|
||||
65400, 65800, 66200, 66600, 67000, 67400, 67800, 68200, 68600, 69000,
|
||||
69400, 69800, 70200, 70600, 71000, 71500, 72100, 72600, 73000, 73400,
|
||||
73800, 74200, 74600, 75000, 75400, 75800, 76200, 76600, 77000, 77400,
|
||||
77800, 78200, 78600, 79000, 79400, 79800, 80200, 80600, 81000, 81400,
|
||||
81800, 82200, 82600, 83000, 83400, 83800, 84200, 84600, 85000, 85400,
|
||||
85800, 86200, 86600, 87000, 87400, 87800, 88200, 88600, 89000, 89400,
|
||||
89800, 90200, 90600, 91000, 91400, 91800, 92200, 92600, 93000, 93400,
|
||||
93800, 94200, 94600, 95000, 95400, 95800, 96200, 96600, 97000, 97500,
|
||||
98100, 98600, 99000, 99400, 99800, 100200, 100600, 101000, 101400,
|
||||
101800, 102200, 102600, 103000, 103400, 103800, 104200, 104600, 105000,
|
||||
105400, 105800, 106200, 106600, 107000, 107400, 107800, 108200, 108600,
|
||||
109000, 109400, 109800, 110200, 110600, 111000, 111400, 111800, 112200,
|
||||
112600, 113000, 113400, 113800, 114200, 114600, 115000, 115400, 115800,
|
||||
116200, 116600, 117000, 117400, 117800, 118200, 118600, 119000, 119400,
|
||||
119800, 120200, 120600, 121000, 121400, 121800, 122200, 122600, 123000,
|
||||
123400, 123800, 124200, 124600, 124900, 125000,
|
||||
};
|
||||
|
||||
struct k3_bandgap {
|
||||
void __iomem *base;
|
||||
const struct k3_bandgap_data *conf;
|
||||
};
|
||||
|
||||
/* common data structures */
|
||||
struct k3_thermal_data {
|
||||
struct thermal_zone_device *tzd;
|
||||
struct k3_bandgap *bgp;
|
||||
int sensor_id;
|
||||
u32 ctrl_offset;
|
||||
u32 stat_offset;
|
||||
};
|
||||
|
||||
static unsigned int vtm_get_best_value(unsigned int s0, unsigned int s1,
|
||||
unsigned int s2)
|
||||
{
|
||||
int d01 = abs(s0 - s1);
|
||||
int d02 = abs(s0 - s2);
|
||||
int d12 = abs(s1 - s2);
|
||||
|
||||
if (d01 <= d02 && d01 <= d12)
|
||||
return (s0 + s1) / 2;
|
||||
|
||||
if (d02 <= d01 && d02 <= d12)
|
||||
return (s0 + s2) / 2;
|
||||
|
||||
return (s1 + s2) / 2;
|
||||
}
|
||||
|
||||
static int k3_bgp_read_temp(struct k3_thermal_data *devdata,
|
||||
int *temp)
|
||||
{
|
||||
struct k3_bandgap *bgp;
|
||||
unsigned int dtemp, s0, s1, s2;
|
||||
|
||||
bgp = devdata->bgp;
|
||||
|
||||
/*
|
||||
* Errata is applicable for am654 pg 1.0 silicon. There
|
||||
* is a variation of the order for 8-10 degree centigrade.
|
||||
* Work around that by getting the average of two closest
|
||||
* readings out of three readings everytime we want to
|
||||
* report temperatures.
|
||||
*
|
||||
* Errata workaround.
|
||||
*/
|
||||
s0 = readl(bgp->base + devdata->stat_offset) &
|
||||
K3_VTM_TS_STAT_DTEMP_MASK;
|
||||
s1 = readl(bgp->base + devdata->stat_offset) &
|
||||
K3_VTM_TS_STAT_DTEMP_MASK;
|
||||
s2 = readl(bgp->base + devdata->stat_offset) &
|
||||
K3_VTM_TS_STAT_DTEMP_MASK;
|
||||
dtemp = vtm_get_best_value(s0, s1, s2);
|
||||
|
||||
if (dtemp < K3_VTM_ADC_BEGIN_VAL || dtemp > K3_VTM_ADC_END_VAL)
|
||||
return -EINVAL;
|
||||
|
||||
*temp = k3_adc_to_temp[dtemp - K3_VTM_ADC_BEGIN_VAL];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int k3_thermal_get_temp(void *devdata, int *temp)
|
||||
{
|
||||
struct k3_thermal_data *data = devdata;
|
||||
int ret = 0;
|
||||
|
||||
ret = k3_bgp_read_temp(data, temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_of_device_ops k3_of_thermal_ops = {
|
||||
.get_temp = k3_thermal_get_temp,
|
||||
};
|
||||
|
||||
static const struct of_device_id of_k3_bandgap_match[];
|
||||
|
||||
static int k3_bandgap_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0, cnt, val, id;
|
||||
struct resource *res;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct k3_bandgap *bgp;
|
||||
struct k3_thermal_data *data;
|
||||
|
||||
if (ARRAY_SIZE(k3_adc_to_temp) != (K3_VTM_ADC_END_VAL + 1 -
|
||||
K3_VTM_ADC_BEGIN_VAL))
|
||||
return -EINVAL;
|
||||
|
||||
bgp = devm_kzalloc(&pdev->dev, sizeof(*bgp), GFP_KERNEL);
|
||||
if (!bgp)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
bgp->base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(bgp->base))
|
||||
return PTR_ERR(bgp->base);
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
ret = pm_runtime_get_sync(dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_noidle(dev);
|
||||
pm_runtime_disable(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the sensor count in the VTM */
|
||||
val = readl(bgp->base + K3_VTM_DEVINFO_PWR0_OFFSET);
|
||||
cnt = val & K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK;
|
||||
cnt >>= __ffs(K3_VTM_DEVINFO_PWR0_TEMPSENS_CT_MASK);
|
||||
|
||||
data = devm_kcalloc(dev, cnt, sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
ret = -ENOMEM;
|
||||
goto err_alloc;
|
||||
}
|
||||
|
||||
/* Register the thermal sensors */
|
||||
for (id = 0; id < cnt; id++) {
|
||||
data[id].sensor_id = id;
|
||||
data[id].bgp = bgp;
|
||||
data[id].ctrl_offset = K3_VTM_TMPSENS0_CTRL_OFFSET +
|
||||
id * K3_VTM_REGS_PER_TS;
|
||||
data[id].stat_offset = data[id].ctrl_offset + 0x8;
|
||||
|
||||
val = readl(data[id].bgp->base + data[id].ctrl_offset);
|
||||
val |= (K3_VTM_TMPSENS_CTRL_SOC |
|
||||
K3_VTM_TMPSENS_CTRL_CLRZ |
|
||||
K3_VTM_TMPSENS_CTRL_CLKON_REQ);
|
||||
val &= ~K3_VTM_TMPSENS_CTRL_CBIASSEL;
|
||||
writel(val, data[id].bgp->base + data[id].ctrl_offset);
|
||||
|
||||
data[id].tzd =
|
||||
devm_thermal_zone_of_sensor_register(dev, id,
|
||||
&data[id],
|
||||
&k3_of_thermal_ops);
|
||||
if (IS_ERR(data[id].tzd)) {
|
||||
dev_err(dev, "thermal zone device is NULL\n");
|
||||
ret = PTR_ERR(data[id].tzd);
|
||||
goto err_alloc;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, bgp);
|
||||
|
||||
return 0;
|
||||
|
||||
err_alloc:
|
||||
pm_runtime_put_sync(dev);
|
||||
pm_runtime_disable(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int k3_bandgap_remove(struct platform_device *pdev)
|
||||
{
|
||||
pm_runtime_put_sync(&pdev->dev);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_k3_bandgap_match[] = {
|
||||
{
|
||||
.compatible = "ti,am654-vtm",
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_k3_bandgap_match);
|
||||
|
||||
static struct platform_driver k3_bandgap_sensor_driver = {
|
||||
.probe = k3_bandgap_probe,
|
||||
.remove = k3_bandgap_remove,
|
||||
.driver = {
|
||||
.name = "k3-soc-thermal",
|
||||
.of_match_table = of_k3_bandgap_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(k3_bandgap_sensor_driver);
|
||||
|
||||
MODULE_DESCRIPTION("K3 bandgap temperature sensor driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("J Keerthy <j-keerthy@ti.com>");
|
|
@ -1,6 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
|
||||
|
||||
qcom_tsens-y += tsens.o tsens-common.o tsens-v0_1.o \
|
||||
tsens-8960.o tsens-v2.o tsens-v1.o
|
||||
qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
|
||||
tsens-8960.o
|
||||
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
|
||||
|
|
|
@ -1,843 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include "tsens.h"
|
||||
|
||||
/**
|
||||
* struct tsens_irq_data - IRQ status and temperature violations
|
||||
* @up_viol: upper threshold violated
|
||||
* @up_thresh: upper threshold temperature value
|
||||
* @up_irq_mask: mask register for upper threshold irqs
|
||||
* @up_irq_clear: clear register for uppper threshold irqs
|
||||
* @low_viol: lower threshold violated
|
||||
* @low_thresh: lower threshold temperature value
|
||||
* @low_irq_mask: mask register for lower threshold irqs
|
||||
* @low_irq_clear: clear register for lower threshold irqs
|
||||
* @crit_viol: critical threshold violated
|
||||
* @crit_thresh: critical threshold temperature value
|
||||
* @crit_irq_mask: mask register for critical threshold irqs
|
||||
* @crit_irq_clear: clear register for critical threshold irqs
|
||||
*
|
||||
* Structure containing data about temperature threshold settings and
|
||||
* irq status if they were violated.
|
||||
*/
|
||||
struct tsens_irq_data {
|
||||
u32 up_viol;
|
||||
int up_thresh;
|
||||
u32 up_irq_mask;
|
||||
u32 up_irq_clear;
|
||||
u32 low_viol;
|
||||
int low_thresh;
|
||||
u32 low_irq_mask;
|
||||
u32 low_irq_clear;
|
||||
u32 crit_viol;
|
||||
u32 crit_thresh;
|
||||
u32 crit_irq_mask;
|
||||
u32 crit_irq_clear;
|
||||
};
|
||||
|
||||
char *qfprom_read(struct device *dev, const char *cname)
|
||||
{
|
||||
struct nvmem_cell *cell;
|
||||
ssize_t data;
|
||||
char *ret;
|
||||
|
||||
cell = nvmem_cell_get(dev, cname);
|
||||
if (IS_ERR(cell))
|
||||
return ERR_CAST(cell);
|
||||
|
||||
ret = nvmem_cell_read(cell, &data);
|
||||
nvmem_cell_put(cell);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use this function on devices where slope and offset calculations
|
||||
* depend on calibration data read from qfprom. On others the slope
|
||||
* and offset values are derived from tz->tzp->slope and tz->tzp->offset
|
||||
* resp.
|
||||
*/
|
||||
void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
|
||||
u32 *p2, u32 mode)
|
||||
{
|
||||
int i;
|
||||
int num, den;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
dev_dbg(priv->dev,
|
||||
"%s: sensor%d - data_point1:%#x data_point2:%#x\n",
|
||||
__func__, i, p1[i], p2[i]);
|
||||
|
||||
priv->sensor[i].slope = SLOPE_DEFAULT;
|
||||
if (mode == TWO_PT_CALIB) {
|
||||
/*
|
||||
* slope (m) = adc_code2 - adc_code1 (y2 - y1)/
|
||||
* temp_120_degc - temp_30_degc (x2 - x1)
|
||||
*/
|
||||
num = p2[i] - p1[i];
|
||||
num *= SLOPE_FACTOR;
|
||||
den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
|
||||
priv->sensor[i].slope = num / den;
|
||||
}
|
||||
|
||||
priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
|
||||
(CAL_DEGC_PT1 *
|
||||
priv->sensor[i].slope);
|
||||
dev_dbg(priv->dev, "%s: offset:%d\n", __func__, priv->sensor[i].offset);
|
||||
}
|
||||
}
|
||||
|
||||
static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
|
||||
{
|
||||
u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
|
||||
|
||||
pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
|
||||
return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
|
||||
}
|
||||
|
||||
static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
|
||||
{
|
||||
int degc, num, den;
|
||||
|
||||
num = (adc_code * SLOPE_FACTOR) - s->offset;
|
||||
den = s->slope;
|
||||
|
||||
if (num > 0)
|
||||
degc = num + (den / 2);
|
||||
else if (num < 0)
|
||||
degc = num - (den / 2);
|
||||
else
|
||||
degc = num;
|
||||
|
||||
degc /= den;
|
||||
|
||||
return degc;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
|
||||
* @s: Pointer to sensor struct
|
||||
* @field: Index into regmap_field array pointing to temperature data
|
||||
*
|
||||
* This function handles temperature returned in ADC code or deciCelsius
|
||||
* depending on IP version.
|
||||
*
|
||||
* Return: Temperature in milliCelsius on success, a negative errno will
|
||||
* be returned in error cases
|
||||
*/
|
||||
static int tsens_hw_to_mC(const struct tsens_sensor *s, int field)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
u32 resolution;
|
||||
u32 temp = 0;
|
||||
int ret;
|
||||
|
||||
resolution = priv->fields[LAST_TEMP_0].msb -
|
||||
priv->fields[LAST_TEMP_0].lsb;
|
||||
|
||||
ret = regmap_field_read(priv->rf[field], &temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Convert temperature from ADC code to milliCelsius */
|
||||
if (priv->feat->adc)
|
||||
return code_to_degc(temp, s) * 1000;
|
||||
|
||||
/* deciCelsius -> milliCelsius along with sign extension */
|
||||
return sign_extend32(temp, resolution) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_mC_to_hw - Convert temperature to hardware register value
|
||||
* @s: Pointer to sensor struct
|
||||
* @temp: temperature in milliCelsius to be programmed to hardware
|
||||
*
|
||||
* This function outputs the value to be written to hardware in ADC code
|
||||
* or deciCelsius depending on IP version.
|
||||
*
|
||||
* Return: ADC code or temperature in deciCelsius.
|
||||
*/
|
||||
static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
|
||||
/* milliC to adc code */
|
||||
if (priv->feat->adc)
|
||||
return degc_to_code(temp / 1000, s);
|
||||
|
||||
/* milliC to deciC */
|
||||
return temp / 100;
|
||||
}
|
||||
|
||||
static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
|
||||
{
|
||||
return priv->feat->ver_major;
|
||||
}
|
||||
|
||||
static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
u32 index = 0;
|
||||
|
||||
switch (irq_type) {
|
||||
case UPPER:
|
||||
index = UP_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case LOWER:
|
||||
index = LOW_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case CRITICAL:
|
||||
/* No critical interrupts before v2 */
|
||||
return;
|
||||
}
|
||||
regmap_field_write(priv->rf[index], enable ? 0 : 1);
|
||||
}
|
||||
|
||||
static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
u32 index_mask = 0, index_clear = 0;
|
||||
|
||||
/*
|
||||
* To enable the interrupt flag for a sensor:
|
||||
* - clear the mask bit
|
||||
* To disable the interrupt flag for a sensor:
|
||||
* - Mask further interrupts for this sensor
|
||||
* - Write 1 followed by 0 to clear the interrupt
|
||||
*/
|
||||
switch (irq_type) {
|
||||
case UPPER:
|
||||
index_mask = UP_INT_MASK_0 + hw_id;
|
||||
index_clear = UP_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case LOWER:
|
||||
index_mask = LOW_INT_MASK_0 + hw_id;
|
||||
index_clear = LOW_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case CRITICAL:
|
||||
index_mask = CRIT_INT_MASK_0 + hw_id;
|
||||
index_clear = CRIT_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
regmap_field_write(priv->rf[index_mask], 0);
|
||||
} else {
|
||||
regmap_field_write(priv->rf[index_mask], 1);
|
||||
regmap_field_write(priv->rf[index_clear], 1);
|
||||
regmap_field_write(priv->rf[index_clear], 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_set_interrupt - Set state of an interrupt
|
||||
* @priv: Pointer to tsens controller private data
|
||||
* @hw_id: Hardware ID aka. sensor number
|
||||
* @irq_type: irq_type from enum tsens_irq_type
|
||||
* @enable: false = disable, true = enable
|
||||
*
|
||||
* Call IP-specific function to set state of an interrupt
|
||||
*
|
||||
* Return: void
|
||||
*/
|
||||
static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
|
||||
irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
|
||||
enable ? "en" : "dis");
|
||||
if (tsens_version(priv) > VER_1_X)
|
||||
tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
|
||||
else
|
||||
tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
|
||||
* @priv: Pointer to tsens controller private data
|
||||
* @hw_id: Hardware ID aka. sensor number
|
||||
* @d: Pointer to irq state data
|
||||
*
|
||||
* Return: 0 if threshold was not violated, 1 if it was violated and negative
|
||||
* errno in case of errors
|
||||
*/
|
||||
static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
|
||||
struct tsens_irq_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (priv->feat->crit_int) {
|
||||
ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id],
|
||||
&d->crit_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (d->up_viol || d->low_viol || d->crit_viol)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
|
||||
const struct tsens_sensor *s,
|
||||
struct tsens_irq_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (tsens_version(priv) > VER_1_X) {
|
||||
ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id],
|
||||
&d->crit_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id],
|
||||
&d->crit_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id);
|
||||
} else {
|
||||
/* No mask register on older TSENS */
|
||||
d->up_irq_mask = 0;
|
||||
d->low_irq_mask = 0;
|
||||
d->crit_irq_clear = 0;
|
||||
d->crit_irq_mask = 0;
|
||||
d->crit_thresh = 0;
|
||||
}
|
||||
|
||||
d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
|
||||
d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
|
||||
|
||||
dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
|
||||
hw_id, __func__,
|
||||
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
|
||||
d->low_viol, d->up_viol, d->crit_viol,
|
||||
d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear,
|
||||
d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask);
|
||||
dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__,
|
||||
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
|
||||
d->low_thresh, d->up_thresh, d->crit_thresh);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
|
||||
{
|
||||
if (ver > VER_1_X)
|
||||
return mask & (1 << hw_id);
|
||||
|
||||
/* v1, v0.1 don't have a irq mask register */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_critical_irq_thread() - Threaded handler for critical interrupts
|
||||
* @irq: irq number
|
||||
* @data: tsens controller private data
|
||||
*
|
||||
* Check FSM watchdog bark status and clear if needed.
|
||||
* Check all sensors to find ones that violated their critical threshold limits.
|
||||
* Clear and then re-enable the interrupt.
|
||||
*
|
||||
* The level-triggered interrupt might deassert if the temperature returned to
|
||||
* within the threshold limits by the time the handler got scheduled. We
|
||||
* consider the irq to have been handled in that case.
|
||||
*
|
||||
* Return: IRQ_HANDLED
|
||||
*/
|
||||
irqreturn_t tsens_critical_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_priv *priv = data;
|
||||
struct tsens_irq_data d;
|
||||
int temp, ret, i;
|
||||
u32 wdog_status, wdog_count;
|
||||
|
||||
if (priv->feat->has_watchdog) {
|
||||
ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS],
|
||||
&wdog_status);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (wdog_status) {
|
||||
/* Clear WDOG interrupt */
|
||||
regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
|
||||
regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
|
||||
ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT],
|
||||
&wdog_count);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (wdog_count)
|
||||
dev_dbg(priv->dev, "%s: watchdog count: %d\n",
|
||||
__func__, wdog_count);
|
||||
|
||||
/* Fall through to handle critical interrupts if any */
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
const struct tsens_sensor *s = &priv->sensor[i];
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
if (IS_ERR(s->tzd))
|
||||
continue;
|
||||
if (!tsens_threshold_violated(priv, hw_id, &d))
|
||||
continue;
|
||||
ret = get_temp_tsens_valid(s, &temp);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "[%u] %s: error reading sensor\n",
|
||||
hw_id, __func__);
|
||||
continue;
|
||||
}
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
if (d.crit_viol &&
|
||||
!masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) {
|
||||
/* Mask critical interrupts, unused on Linux */
|
||||
tsens_set_interrupt(priv, hw_id, CRITICAL, false);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_irq_thread - Threaded interrupt handler for uplow interrupts
|
||||
* @irq: irq number
|
||||
* @data: tsens controller private data
|
||||
*
|
||||
* Check all sensors to find ones that violated their threshold limits. If the
|
||||
* temperature is still outside the limits, call thermal_zone_device_update() to
|
||||
* update the thresholds, else re-enable the interrupts.
|
||||
*
|
||||
* The level-triggered interrupt might deassert if the temperature returned to
|
||||
* within the threshold limits by the time the handler got scheduled. We
|
||||
* consider the irq to have been handled in that case.
|
||||
*
|
||||
* Return: IRQ_HANDLED
|
||||
*/
|
||||
irqreturn_t tsens_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_priv *priv = data;
|
||||
struct tsens_irq_data d;
|
||||
bool enable = true, disable = false;
|
||||
unsigned long flags;
|
||||
int temp, ret, i;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
bool trigger = false;
|
||||
const struct tsens_sensor *s = &priv->sensor[i];
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
if (IS_ERR(s->tzd))
|
||||
continue;
|
||||
if (!tsens_threshold_violated(priv, hw_id, &d))
|
||||
continue;
|
||||
ret = get_temp_tsens_valid(s, &temp);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "[%u] %s: error reading sensor\n", hw_id, __func__);
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&priv->ul_lock, flags);
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
|
||||
if (d.up_viol &&
|
||||
!masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) {
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, disable);
|
||||
if (d.up_thresh > temp) {
|
||||
dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
|
||||
hw_id, __func__);
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, enable);
|
||||
} else {
|
||||
trigger = true;
|
||||
/* Keep irq masked */
|
||||
}
|
||||
} else if (d.low_viol &&
|
||||
!masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) {
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, disable);
|
||||
if (d.low_thresh < temp) {
|
||||
dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
|
||||
hw_id, __func__);
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, enable);
|
||||
} else {
|
||||
trigger = true;
|
||||
/* Keep irq masked */
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&priv->ul_lock, flags);
|
||||
|
||||
if (trigger) {
|
||||
dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
|
||||
hw_id, __func__, temp);
|
||||
thermal_zone_device_update(s->tzd,
|
||||
THERMAL_EVENT_UNSPECIFIED);
|
||||
} else {
|
||||
dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
|
||||
hw_id, __func__, temp);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int tsens_set_trips(void *_sensor, int low, int high)
|
||||
{
|
||||
struct tsens_sensor *s = _sensor;
|
||||
struct tsens_priv *priv = s->priv;
|
||||
struct device *dev = priv->dev;
|
||||
struct tsens_irq_data d;
|
||||
unsigned long flags;
|
||||
int high_val, low_val, cl_high, cl_low;
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
|
||||
hw_id, __func__, low, high);
|
||||
|
||||
cl_high = clamp_val(high, -40000, 120000);
|
||||
cl_low = clamp_val(low, -40000, 120000);
|
||||
|
||||
high_val = tsens_mC_to_hw(s, cl_high);
|
||||
low_val = tsens_mC_to_hw(s, cl_low);
|
||||
|
||||
spin_lock_irqsave(&priv->ul_lock, flags);
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
|
||||
/* Write the new thresholds and clear the status */
|
||||
regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
|
||||
regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, true);
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, true);
|
||||
|
||||
spin_unlock_irqrestore(&priv->ul_lock, flags);
|
||||
|
||||
dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
|
||||
hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tsens_enable_irq(struct tsens_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
int val = tsens_version(priv) > VER_1_X ? 7 : 1;
|
||||
|
||||
ret = regmap_field_write(priv->rf[INT_EN], val);
|
||||
if (ret < 0)
|
||||
dev_err(priv->dev, "%s: failed to enable interrupts\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void tsens_disable_irq(struct tsens_priv *priv)
|
||||
{
|
||||
regmap_field_write(priv->rf[INT_EN], 0);
|
||||
}
|
||||
|
||||
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
int hw_id = s->hw_id;
|
||||
u32 temp_idx = LAST_TEMP_0 + hw_id;
|
||||
u32 valid_idx = VALID_0 + hw_id;
|
||||
u32 valid;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[valid_idx], &valid);
|
||||
if (ret)
|
||||
return ret;
|
||||
while (!valid) {
|
||||
/* Valid bit is 0 for 6 AHB clock cycles.
|
||||
* At 19.2MHz, 1 AHB clock is ~60ns.
|
||||
* We should enter this loop very, very rarely.
|
||||
*/
|
||||
ndelay(400);
|
||||
ret = regmap_field_read(priv->rf[valid_idx], &valid);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Valid bit is set, OK to read the temperature */
|
||||
*temp = tsens_hw_to_mC(s, temp_idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_temp_common(const struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
int hw_id = s->hw_id;
|
||||
int last_temp = 0, ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*temp = code_to_degc(last_temp, s) * 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int dbg_sensors_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct platform_device *pdev = s->private;
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
seq_printf(s, "max: %2d\nnum: %2d\n\n",
|
||||
priv->feat->max_sensors, priv->num_sensors);
|
||||
|
||||
seq_puts(s, " id slope offset\n--------------------------\n");
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
|
||||
priv->sensor[i].slope, priv->sensor[i].offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dbg_version_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct platform_device *pdev = s->private;
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
u32 maj_ver, min_ver, step_ver;
|
||||
int ret;
|
||||
|
||||
if (tsens_version(priv) > VER_0_1) {
|
||||
ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
|
||||
} else {
|
||||
seq_puts(s, "0.1.0\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(dbg_version);
|
||||
DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
|
||||
|
||||
static void tsens_debug_init(struct platform_device *pdev)
|
||||
{
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
struct dentry *root, *file;
|
||||
|
||||
root = debugfs_lookup("tsens", NULL);
|
||||
if (!root)
|
||||
priv->debug_root = debugfs_create_dir("tsens", NULL);
|
||||
else
|
||||
priv->debug_root = root;
|
||||
|
||||
file = debugfs_lookup("version", priv->debug_root);
|
||||
if (!file)
|
||||
debugfs_create_file("version", 0444, priv->debug_root,
|
||||
pdev, &dbg_version_fops);
|
||||
|
||||
/* A directory for each instance of the TSENS IP */
|
||||
priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
|
||||
debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
|
||||
}
|
||||
#else
|
||||
static inline void tsens_debug_init(struct platform_device *pdev) {}
|
||||
#endif
|
||||
|
||||
static const struct regmap_config tsens_config = {
|
||||
.name = "tm",
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
static const struct regmap_config tsens_srot_config = {
|
||||
.name = "srot",
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
int __init init_common(struct tsens_priv *priv)
|
||||
{
|
||||
void __iomem *tm_base, *srot_base;
|
||||
struct device *dev = priv->dev;
|
||||
u32 ver_minor;
|
||||
struct resource *res;
|
||||
u32 enabled;
|
||||
int ret, i, j;
|
||||
struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
|
||||
|
||||
if (!op)
|
||||
return -EINVAL;
|
||||
|
||||
if (op->num_resources > 1) {
|
||||
/* DT with separate SROT and TM address space */
|
||||
priv->tm_offset = 0;
|
||||
res = platform_get_resource(op, IORESOURCE_MEM, 1);
|
||||
srot_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(srot_base)) {
|
||||
ret = PTR_ERR(srot_base);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
|
||||
&tsens_srot_config);
|
||||
if (IS_ERR(priv->srot_map)) {
|
||||
ret = PTR_ERR(priv->srot_map);
|
||||
goto err_put_device;
|
||||
}
|
||||
} else {
|
||||
/* old DTs where SROT and TM were in a contiguous 2K block */
|
||||
priv->tm_offset = 0x1000;
|
||||
}
|
||||
|
||||
res = platform_get_resource(op, IORESOURCE_MEM, 0);
|
||||
tm_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(tm_base)) {
|
||||
ret = PTR_ERR(tm_base);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
|
||||
if (IS_ERR(priv->tm_map)) {
|
||||
ret = PTR_ERR(priv->tm_map);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
if (tsens_version(priv) > VER_0_1) {
|
||||
for (i = VER_MAJOR; i <= VER_STEP; i++) {
|
||||
priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[i]);
|
||||
if (IS_ERR(priv->rf[i]))
|
||||
return PTR_ERR(priv->rf[i]);
|
||||
}
|
||||
ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor);
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[TSENS_EN]);
|
||||
if (IS_ERR(priv->rf[TSENS_EN])) {
|
||||
ret = PTR_ERR(priv->rf[TSENS_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
if (!enabled) {
|
||||
dev_err(dev, "%s: device not enabled\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[SENSOR_EN]);
|
||||
if (IS_ERR(priv->rf[SENSOR_EN])) {
|
||||
ret = PTR_ERR(priv->rf[SENSOR_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[INT_EN]);
|
||||
if (IS_ERR(priv->rf[INT_EN])) {
|
||||
ret = PTR_ERR(priv->rf[INT_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
/* This loop might need changes if enum regfield_ids is reordered */
|
||||
for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
|
||||
for (i = 0; i < priv->feat->max_sensors; i++) {
|
||||
int idx = j + i;
|
||||
|
||||
priv->rf[idx] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[idx]);
|
||||
if (IS_ERR(priv->rf[idx])) {
|
||||
ret = PTR_ERR(priv->rf[idx]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->feat->crit_int) {
|
||||
/* Loop might need changes if enum regfield_ids is reordered */
|
||||
for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) {
|
||||
for (i = 0; i < priv->feat->max_sensors; i++) {
|
||||
int idx = j + i;
|
||||
|
||||
priv->rf[idx] =
|
||||
devm_regmap_field_alloc(dev,
|
||||
priv->tm_map,
|
||||
priv->fields[idx]);
|
||||
if (IS_ERR(priv->rf[idx])) {
|
||||
ret = PTR_ERR(priv->rf[idx]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tsens_version(priv) > VER_1_X && ver_minor > 2) {
|
||||
/* Watchdog is present only on v2.3+ */
|
||||
priv->feat->has_watchdog = 1;
|
||||
for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) {
|
||||
priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[i]);
|
||||
if (IS_ERR(priv->rf[i])) {
|
||||
ret = PTR_ERR(priv->rf[i]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Watchdog is already enabled, unmask the bark.
|
||||
* Disable cycle completion monitoring
|
||||
*/
|
||||
regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
|
||||
regmap_field_write(priv->rf[CC_MON_MASK], 1);
|
||||
}
|
||||
|
||||
spin_lock_init(&priv->ul_lock);
|
||||
tsens_enable_irq(priv);
|
||||
tsens_debug_init(op);
|
||||
|
||||
err_put_device:
|
||||
put_device(&op->dev);
|
||||
return ret;
|
||||
}
|
|
@ -1,19 +1,857 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2019, 2020, Linaro Ltd.
|
||||
*/
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/nvmem-consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
#include "tsens.h"
|
||||
|
||||
/**
|
||||
* struct tsens_irq_data - IRQ status and temperature violations
|
||||
* @up_viol: upper threshold violated
|
||||
* @up_thresh: upper threshold temperature value
|
||||
* @up_irq_mask: mask register for upper threshold irqs
|
||||
* @up_irq_clear: clear register for uppper threshold irqs
|
||||
* @low_viol: lower threshold violated
|
||||
* @low_thresh: lower threshold temperature value
|
||||
* @low_irq_mask: mask register for lower threshold irqs
|
||||
* @low_irq_clear: clear register for lower threshold irqs
|
||||
* @crit_viol: critical threshold violated
|
||||
* @crit_thresh: critical threshold temperature value
|
||||
* @crit_irq_mask: mask register for critical threshold irqs
|
||||
* @crit_irq_clear: clear register for critical threshold irqs
|
||||
*
|
||||
* Structure containing data about temperature threshold settings and
|
||||
* irq status if they were violated.
|
||||
*/
|
||||
struct tsens_irq_data {
|
||||
u32 up_viol;
|
||||
int up_thresh;
|
||||
u32 up_irq_mask;
|
||||
u32 up_irq_clear;
|
||||
u32 low_viol;
|
||||
int low_thresh;
|
||||
u32 low_irq_mask;
|
||||
u32 low_irq_clear;
|
||||
u32 crit_viol;
|
||||
u32 crit_thresh;
|
||||
u32 crit_irq_mask;
|
||||
u32 crit_irq_clear;
|
||||
};
|
||||
|
||||
char *qfprom_read(struct device *dev, const char *cname)
|
||||
{
|
||||
struct nvmem_cell *cell;
|
||||
ssize_t data;
|
||||
char *ret;
|
||||
|
||||
cell = nvmem_cell_get(dev, cname);
|
||||
if (IS_ERR(cell))
|
||||
return ERR_CAST(cell);
|
||||
|
||||
ret = nvmem_cell_read(cell, &data);
|
||||
nvmem_cell_put(cell);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use this function on devices where slope and offset calculations
|
||||
* depend on calibration data read from qfprom. On others the slope
|
||||
* and offset values are derived from tz->tzp->slope and tz->tzp->offset
|
||||
* resp.
|
||||
*/
|
||||
void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
|
||||
u32 *p2, u32 mode)
|
||||
{
|
||||
int i;
|
||||
int num, den;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
dev_dbg(priv->dev,
|
||||
"%s: sensor%d - data_point1:%#x data_point2:%#x\n",
|
||||
__func__, i, p1[i], p2[i]);
|
||||
|
||||
priv->sensor[i].slope = SLOPE_DEFAULT;
|
||||
if (mode == TWO_PT_CALIB) {
|
||||
/*
|
||||
* slope (m) = adc_code2 - adc_code1 (y2 - y1)/
|
||||
* temp_120_degc - temp_30_degc (x2 - x1)
|
||||
*/
|
||||
num = p2[i] - p1[i];
|
||||
num *= SLOPE_FACTOR;
|
||||
den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
|
||||
priv->sensor[i].slope = num / den;
|
||||
}
|
||||
|
||||
priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
|
||||
(CAL_DEGC_PT1 *
|
||||
priv->sensor[i].slope);
|
||||
dev_dbg(priv->dev, "%s: offset:%d\n", __func__,
|
||||
priv->sensor[i].offset);
|
||||
}
|
||||
}
|
||||
|
||||
static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
|
||||
{
|
||||
u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
|
||||
|
||||
pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
|
||||
return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
|
||||
}
|
||||
|
||||
static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
|
||||
{
|
||||
int degc, num, den;
|
||||
|
||||
num = (adc_code * SLOPE_FACTOR) - s->offset;
|
||||
den = s->slope;
|
||||
|
||||
if (num > 0)
|
||||
degc = num + (den / 2);
|
||||
else if (num < 0)
|
||||
degc = num - (den / 2);
|
||||
else
|
||||
degc = num;
|
||||
|
||||
degc /= den;
|
||||
|
||||
return degc;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
|
||||
* @s: Pointer to sensor struct
|
||||
* @field: Index into regmap_field array pointing to temperature data
|
||||
*
|
||||
* This function handles temperature returned in ADC code or deciCelsius
|
||||
* depending on IP version.
|
||||
*
|
||||
* Return: Temperature in milliCelsius on success, a negative errno will
|
||||
* be returned in error cases
|
||||
*/
|
||||
static int tsens_hw_to_mC(const struct tsens_sensor *s, int field)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
u32 resolution;
|
||||
u32 temp = 0;
|
||||
int ret;
|
||||
|
||||
resolution = priv->fields[LAST_TEMP_0].msb -
|
||||
priv->fields[LAST_TEMP_0].lsb;
|
||||
|
||||
ret = regmap_field_read(priv->rf[field], &temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Convert temperature from ADC code to milliCelsius */
|
||||
if (priv->feat->adc)
|
||||
return code_to_degc(temp, s) * 1000;
|
||||
|
||||
/* deciCelsius -> milliCelsius along with sign extension */
|
||||
return sign_extend32(temp, resolution) * 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_mC_to_hw - Convert temperature to hardware register value
|
||||
* @s: Pointer to sensor struct
|
||||
* @temp: temperature in milliCelsius to be programmed to hardware
|
||||
*
|
||||
* This function outputs the value to be written to hardware in ADC code
|
||||
* or deciCelsius depending on IP version.
|
||||
*
|
||||
* Return: ADC code or temperature in deciCelsius.
|
||||
*/
|
||||
static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
|
||||
/* milliC to adc code */
|
||||
if (priv->feat->adc)
|
||||
return degc_to_code(temp / 1000, s);
|
||||
|
||||
/* milliC to deciC */
|
||||
return temp / 100;
|
||||
}
|
||||
|
||||
static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
|
||||
{
|
||||
return priv->feat->ver_major;
|
||||
}
|
||||
|
||||
static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
u32 index = 0;
|
||||
|
||||
switch (irq_type) {
|
||||
case UPPER:
|
||||
index = UP_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case LOWER:
|
||||
index = LOW_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case CRITICAL:
|
||||
/* No critical interrupts before v2 */
|
||||
return;
|
||||
}
|
||||
regmap_field_write(priv->rf[index], enable ? 0 : 1);
|
||||
}
|
||||
|
||||
static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
u32 index_mask = 0, index_clear = 0;
|
||||
|
||||
/*
|
||||
* To enable the interrupt flag for a sensor:
|
||||
* - clear the mask bit
|
||||
* To disable the interrupt flag for a sensor:
|
||||
* - Mask further interrupts for this sensor
|
||||
* - Write 1 followed by 0 to clear the interrupt
|
||||
*/
|
||||
switch (irq_type) {
|
||||
case UPPER:
|
||||
index_mask = UP_INT_MASK_0 + hw_id;
|
||||
index_clear = UP_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case LOWER:
|
||||
index_mask = LOW_INT_MASK_0 + hw_id;
|
||||
index_clear = LOW_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
case CRITICAL:
|
||||
index_mask = CRIT_INT_MASK_0 + hw_id;
|
||||
index_clear = CRIT_INT_CLEAR_0 + hw_id;
|
||||
break;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
regmap_field_write(priv->rf[index_mask], 0);
|
||||
} else {
|
||||
regmap_field_write(priv->rf[index_mask], 1);
|
||||
regmap_field_write(priv->rf[index_clear], 1);
|
||||
regmap_field_write(priv->rf[index_clear], 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_set_interrupt - Set state of an interrupt
|
||||
* @priv: Pointer to tsens controller private data
|
||||
* @hw_id: Hardware ID aka. sensor number
|
||||
* @irq_type: irq_type from enum tsens_irq_type
|
||||
* @enable: false = disable, true = enable
|
||||
*
|
||||
* Call IP-specific function to set state of an interrupt
|
||||
*
|
||||
* Return: void
|
||||
*/
|
||||
static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
|
||||
enum tsens_irq_type irq_type, bool enable)
|
||||
{
|
||||
dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
|
||||
irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
|
||||
enable ? "en" : "dis");
|
||||
if (tsens_version(priv) > VER_1_X)
|
||||
tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
|
||||
else
|
||||
tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
|
||||
* @priv: Pointer to tsens controller private data
|
||||
* @hw_id: Hardware ID aka. sensor number
|
||||
* @d: Pointer to irq state data
|
||||
*
|
||||
* Return: 0 if threshold was not violated, 1 if it was violated and negative
|
||||
* errno in case of errors
|
||||
*/
|
||||
static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
|
||||
struct tsens_irq_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (priv->feat->crit_int) {
|
||||
ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id],
|
||||
&d->crit_viol);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (d->up_viol || d->low_viol || d->crit_viol)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
|
||||
const struct tsens_sensor *s,
|
||||
struct tsens_irq_data *d)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (tsens_version(priv) > VER_1_X) {
|
||||
ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id],
|
||||
&d->crit_irq_clear);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id],
|
||||
&d->crit_irq_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id);
|
||||
} else {
|
||||
/* No mask register on older TSENS */
|
||||
d->up_irq_mask = 0;
|
||||
d->low_irq_mask = 0;
|
||||
d->crit_irq_clear = 0;
|
||||
d->crit_irq_mask = 0;
|
||||
d->crit_thresh = 0;
|
||||
}
|
||||
|
||||
d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
|
||||
d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
|
||||
|
||||
dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
|
||||
hw_id, __func__,
|
||||
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
|
||||
d->low_viol, d->up_viol, d->crit_viol,
|
||||
d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear,
|
||||
d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask);
|
||||
dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__,
|
||||
(d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
|
||||
d->low_thresh, d->up_thresh, d->crit_thresh);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
|
||||
{
|
||||
if (ver > VER_1_X)
|
||||
return mask & (1 << hw_id);
|
||||
|
||||
/* v1, v0.1 don't have a irq mask register */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_critical_irq_thread() - Threaded handler for critical interrupts
|
||||
* @irq: irq number
|
||||
* @data: tsens controller private data
|
||||
*
|
||||
* Check FSM watchdog bark status and clear if needed.
|
||||
* Check all sensors to find ones that violated their critical threshold limits.
|
||||
* Clear and then re-enable the interrupt.
|
||||
*
|
||||
* The level-triggered interrupt might deassert if the temperature returned to
|
||||
* within the threshold limits by the time the handler got scheduled. We
|
||||
* consider the irq to have been handled in that case.
|
||||
*
|
||||
* Return: IRQ_HANDLED
|
||||
*/
|
||||
irqreturn_t tsens_critical_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_priv *priv = data;
|
||||
struct tsens_irq_data d;
|
||||
int temp, ret, i;
|
||||
u32 wdog_status, wdog_count;
|
||||
|
||||
if (priv->feat->has_watchdog) {
|
||||
ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS],
|
||||
&wdog_status);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (wdog_status) {
|
||||
/* Clear WDOG interrupt */
|
||||
regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
|
||||
regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
|
||||
ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT],
|
||||
&wdog_count);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (wdog_count)
|
||||
dev_dbg(priv->dev, "%s: watchdog count: %d\n",
|
||||
__func__, wdog_count);
|
||||
|
||||
/* Fall through to handle critical interrupts if any */
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
const struct tsens_sensor *s = &priv->sensor[i];
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
if (IS_ERR(s->tzd))
|
||||
continue;
|
||||
if (!tsens_threshold_violated(priv, hw_id, &d))
|
||||
continue;
|
||||
ret = get_temp_tsens_valid(s, &temp);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "[%u] %s: error reading sensor\n",
|
||||
hw_id, __func__);
|
||||
continue;
|
||||
}
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
if (d.crit_viol &&
|
||||
!masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) {
|
||||
/* Mask critical interrupts, unused on Linux */
|
||||
tsens_set_interrupt(priv, hw_id, CRITICAL, false);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* tsens_irq_thread - Threaded interrupt handler for uplow interrupts
|
||||
* @irq: irq number
|
||||
* @data: tsens controller private data
|
||||
*
|
||||
* Check all sensors to find ones that violated their threshold limits. If the
|
||||
* temperature is still outside the limits, call thermal_zone_device_update() to
|
||||
* update the thresholds, else re-enable the interrupts.
|
||||
*
|
||||
* The level-triggered interrupt might deassert if the temperature returned to
|
||||
* within the threshold limits by the time the handler got scheduled. We
|
||||
* consider the irq to have been handled in that case.
|
||||
*
|
||||
* Return: IRQ_HANDLED
|
||||
*/
|
||||
irqreturn_t tsens_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct tsens_priv *priv = data;
|
||||
struct tsens_irq_data d;
|
||||
bool enable = true, disable = false;
|
||||
unsigned long flags;
|
||||
int temp, ret, i;
|
||||
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
bool trigger = false;
|
||||
const struct tsens_sensor *s = &priv->sensor[i];
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
if (IS_ERR(s->tzd))
|
||||
continue;
|
||||
if (!tsens_threshold_violated(priv, hw_id, &d))
|
||||
continue;
|
||||
ret = get_temp_tsens_valid(s, &temp);
|
||||
if (ret) {
|
||||
dev_err(priv->dev, "[%u] %s: error reading sensor\n",
|
||||
hw_id, __func__);
|
||||
continue;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&priv->ul_lock, flags);
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
|
||||
if (d.up_viol &&
|
||||
!masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) {
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, disable);
|
||||
if (d.up_thresh > temp) {
|
||||
dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
|
||||
hw_id, __func__);
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, enable);
|
||||
} else {
|
||||
trigger = true;
|
||||
/* Keep irq masked */
|
||||
}
|
||||
} else if (d.low_viol &&
|
||||
!masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) {
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, disable);
|
||||
if (d.low_thresh < temp) {
|
||||
dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
|
||||
hw_id, __func__);
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, enable);
|
||||
} else {
|
||||
trigger = true;
|
||||
/* Keep irq masked */
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&priv->ul_lock, flags);
|
||||
|
||||
if (trigger) {
|
||||
dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
|
||||
hw_id, __func__, temp);
|
||||
thermal_zone_device_update(s->tzd,
|
||||
THERMAL_EVENT_UNSPECIFIED);
|
||||
} else {
|
||||
dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
|
||||
hw_id, __func__, temp);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int tsens_set_trips(void *_sensor, int low, int high)
|
||||
{
|
||||
struct tsens_sensor *s = _sensor;
|
||||
struct tsens_priv *priv = s->priv;
|
||||
struct device *dev = priv->dev;
|
||||
struct tsens_irq_data d;
|
||||
unsigned long flags;
|
||||
int high_val, low_val, cl_high, cl_low;
|
||||
u32 hw_id = s->hw_id;
|
||||
|
||||
dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
|
||||
hw_id, __func__, low, high);
|
||||
|
||||
cl_high = clamp_val(high, -40000, 120000);
|
||||
cl_low = clamp_val(low, -40000, 120000);
|
||||
|
||||
high_val = tsens_mC_to_hw(s, cl_high);
|
||||
low_val = tsens_mC_to_hw(s, cl_low);
|
||||
|
||||
spin_lock_irqsave(&priv->ul_lock, flags);
|
||||
|
||||
tsens_read_irq_state(priv, hw_id, s, &d);
|
||||
|
||||
/* Write the new thresholds and clear the status */
|
||||
regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
|
||||
regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
|
||||
tsens_set_interrupt(priv, hw_id, LOWER, true);
|
||||
tsens_set_interrupt(priv, hw_id, UPPER, true);
|
||||
|
||||
spin_unlock_irqrestore(&priv->ul_lock, flags);
|
||||
|
||||
dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
|
||||
hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tsens_enable_irq(struct tsens_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
int val = tsens_version(priv) > VER_1_X ? 7 : 1;
|
||||
|
||||
ret = regmap_field_write(priv->rf[INT_EN], val);
|
||||
if (ret < 0)
|
||||
dev_err(priv->dev, "%s: failed to enable interrupts\n",
|
||||
__func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void tsens_disable_irq(struct tsens_priv *priv)
|
||||
{
|
||||
regmap_field_write(priv->rf[INT_EN], 0);
|
||||
}
|
||||
|
||||
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
int hw_id = s->hw_id;
|
||||
u32 temp_idx = LAST_TEMP_0 + hw_id;
|
||||
u32 valid_idx = VALID_0 + hw_id;
|
||||
u32 valid;
|
||||
int ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[valid_idx], &valid);
|
||||
if (ret)
|
||||
return ret;
|
||||
while (!valid) {
|
||||
/* Valid bit is 0 for 6 AHB clock cycles.
|
||||
* At 19.2MHz, 1 AHB clock is ~60ns.
|
||||
* We should enter this loop very, very rarely.
|
||||
*/
|
||||
ndelay(400);
|
||||
ret = regmap_field_read(priv->rf[valid_idx], &valid);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Valid bit is set, OK to read the temperature */
|
||||
*temp = tsens_hw_to_mC(s, temp_idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_temp_common(const struct tsens_sensor *s, int *temp)
|
||||
{
|
||||
struct tsens_priv *priv = s->priv;
|
||||
int hw_id = s->hw_id;
|
||||
int last_temp = 0, ret;
|
||||
|
||||
ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*temp = code_to_degc(last_temp, s) * 1000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int dbg_sensors_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct platform_device *pdev = s->private;
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
seq_printf(s, "max: %2d\nnum: %2d\n\n",
|
||||
priv->feat->max_sensors, priv->num_sensors);
|
||||
|
||||
seq_puts(s, " id slope offset\n--------------------------\n");
|
||||
for (i = 0; i < priv->num_sensors; i++) {
|
||||
seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
|
||||
priv->sensor[i].slope, priv->sensor[i].offset);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dbg_version_show(struct seq_file *s, void *data)
|
||||
{
|
||||
struct platform_device *pdev = s->private;
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
u32 maj_ver, min_ver, step_ver;
|
||||
int ret;
|
||||
|
||||
if (tsens_version(priv) > VER_0_1) {
|
||||
ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
|
||||
if (ret)
|
||||
return ret;
|
||||
seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
|
||||
} else {
|
||||
seq_puts(s, "0.1.0\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(dbg_version);
|
||||
DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
|
||||
|
||||
static void tsens_debug_init(struct platform_device *pdev)
|
||||
{
|
||||
struct tsens_priv *priv = platform_get_drvdata(pdev);
|
||||
struct dentry *root, *file;
|
||||
|
||||
root = debugfs_lookup("tsens", NULL);
|
||||
if (!root)
|
||||
priv->debug_root = debugfs_create_dir("tsens", NULL);
|
||||
else
|
||||
priv->debug_root = root;
|
||||
|
||||
file = debugfs_lookup("version", priv->debug_root);
|
||||
if (!file)
|
||||
debugfs_create_file("version", 0444, priv->debug_root,
|
||||
pdev, &dbg_version_fops);
|
||||
|
||||
/* A directory for each instance of the TSENS IP */
|
||||
priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
|
||||
debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
|
||||
}
|
||||
#else
|
||||
static inline void tsens_debug_init(struct platform_device *pdev) {}
|
||||
#endif
|
||||
|
||||
static const struct regmap_config tsens_config = {
|
||||
.name = "tm",
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
static const struct regmap_config tsens_srot_config = {
|
||||
.name = "srot",
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_stride = 4,
|
||||
};
|
||||
|
||||
int __init init_common(struct tsens_priv *priv)
|
||||
{
|
||||
void __iomem *tm_base, *srot_base;
|
||||
struct device *dev = priv->dev;
|
||||
u32 ver_minor;
|
||||
struct resource *res;
|
||||
u32 enabled;
|
||||
int ret, i, j;
|
||||
struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
|
||||
|
||||
if (!op)
|
||||
return -EINVAL;
|
||||
|
||||
if (op->num_resources > 1) {
|
||||
/* DT with separate SROT and TM address space */
|
||||
priv->tm_offset = 0;
|
||||
res = platform_get_resource(op, IORESOURCE_MEM, 1);
|
||||
srot_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(srot_base)) {
|
||||
ret = PTR_ERR(srot_base);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
|
||||
&tsens_srot_config);
|
||||
if (IS_ERR(priv->srot_map)) {
|
||||
ret = PTR_ERR(priv->srot_map);
|
||||
goto err_put_device;
|
||||
}
|
||||
} else {
|
||||
/* old DTs where SROT and TM were in a contiguous 2K block */
|
||||
priv->tm_offset = 0x1000;
|
||||
}
|
||||
|
||||
res = platform_get_resource(op, IORESOURCE_MEM, 0);
|
||||
tm_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(tm_base)) {
|
||||
ret = PTR_ERR(tm_base);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
|
||||
if (IS_ERR(priv->tm_map)) {
|
||||
ret = PTR_ERR(priv->tm_map);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
if (tsens_version(priv) > VER_0_1) {
|
||||
for (i = VER_MAJOR; i <= VER_STEP; i++) {
|
||||
priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[i]);
|
||||
if (IS_ERR(priv->rf[i]))
|
||||
return PTR_ERR(priv->rf[i]);
|
||||
}
|
||||
ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor);
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[TSENS_EN]);
|
||||
if (IS_ERR(priv->rf[TSENS_EN])) {
|
||||
ret = PTR_ERR(priv->rf[TSENS_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
|
||||
if (ret)
|
||||
goto err_put_device;
|
||||
if (!enabled) {
|
||||
dev_err(dev, "%s: device not enabled\n", __func__);
|
||||
ret = -ENODEV;
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
|
||||
priv->fields[SENSOR_EN]);
|
||||
if (IS_ERR(priv->rf[SENSOR_EN])) {
|
||||
ret = PTR_ERR(priv->rf[SENSOR_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[INT_EN]);
|
||||
if (IS_ERR(priv->rf[INT_EN])) {
|
||||
ret = PTR_ERR(priv->rf[INT_EN]);
|
||||
goto err_put_device;
|
||||
}
|
||||
|
||||
/* This loop might need changes if enum regfield_ids is reordered */
|
||||
for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
|
||||
for (i = 0; i < priv->feat->max_sensors; i++) {
|
||||
int idx = j + i;
|
||||
|
||||
priv->rf[idx] = devm_regmap_field_alloc(dev,
|
||||
priv->tm_map,
|
||||
priv->fields[idx]);
|
||||
if (IS_ERR(priv->rf[idx])) {
|
||||
ret = PTR_ERR(priv->rf[idx]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->feat->crit_int) {
|
||||
/* Loop might need changes if enum regfield_ids is reordered */
|
||||
for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) {
|
||||
for (i = 0; i < priv->feat->max_sensors; i++) {
|
||||
int idx = j + i;
|
||||
|
||||
priv->rf[idx] =
|
||||
devm_regmap_field_alloc(dev,
|
||||
priv->tm_map,
|
||||
priv->fields[idx]);
|
||||
if (IS_ERR(priv->rf[idx])) {
|
||||
ret = PTR_ERR(priv->rf[idx]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tsens_version(priv) > VER_1_X && ver_minor > 2) {
|
||||
/* Watchdog is present only on v2.3+ */
|
||||
priv->feat->has_watchdog = 1;
|
||||
for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) {
|
||||
priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map,
|
||||
priv->fields[i]);
|
||||
if (IS_ERR(priv->rf[i])) {
|
||||
ret = PTR_ERR(priv->rf[i]);
|
||||
goto err_put_device;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Watchdog is already enabled, unmask the bark.
|
||||
* Disable cycle completion monitoring
|
||||
*/
|
||||
regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
|
||||
regmap_field_write(priv->rf[CC_MON_MASK], 1);
|
||||
}
|
||||
|
||||
spin_lock_init(&priv->ul_lock);
|
||||
tsens_enable_irq(priv);
|
||||
tsens_debug_init(op);
|
||||
|
||||
err_put_device:
|
||||
put_device(&op->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tsens_get_temp(void *data, int *temp)
|
||||
{
|
||||
struct tsens_sensor *s = data;
|
||||
|
|
|
@ -580,11 +580,6 @@ void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mo
|
|||
int init_common(struct tsens_priv *priv);
|
||||
int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
|
||||
int get_temp_common(const struct tsens_sensor *s, int *temp);
|
||||
int tsens_enable_irq(struct tsens_priv *priv);
|
||||
void tsens_disable_irq(struct tsens_priv *priv);
|
||||
int tsens_set_trips(void *_sensor, int low, int high);
|
||||
irqreturn_t tsens_irq_thread(int irq, void *data);
|
||||
irqreturn_t tsens_critical_irq_thread(int irq, void *data);
|
||||
|
||||
/* TSENS target */
|
||||
extern struct tsens_plat_data data_8960;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/regmap.h>
|
||||
#include <linux/sizes.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
#include "thermal_hwmon.h"
|
||||
|
@ -23,6 +24,7 @@
|
|||
#define TMTMIR_DEFAULT 0x0000000f
|
||||
#define TIER_DISABLE 0x0
|
||||
#define TEUMR0_V2 0x51009c00
|
||||
#define TMSARA_V2 0xe
|
||||
#define TMU_VER1 0x1
|
||||
#define TMU_VER2 0x2
|
||||
|
||||
|
@ -50,6 +52,9 @@
|
|||
* Site Register
|
||||
*/
|
||||
#define TRITSR_V BIT(31)
|
||||
#define REGS_V2_TMSAR(n) (0x304 + 16 * (n)) /* TMU monitoring
|
||||
* site adjustment register
|
||||
*/
|
||||
#define REGS_TTRnCR(n) (0xf10 + 4 * (n)) /* Temperature Range n
|
||||
* Control Register
|
||||
*/
|
||||
|
@ -85,12 +90,21 @@ static int tmu_get_temp(void *p, int *temp)
|
|||
/*
|
||||
* REGS_TRITSR(id) has the following layout:
|
||||
*
|
||||
* For TMU Rev1:
|
||||
* 31 ... 7 6 5 4 3 2 1 0
|
||||
* V TEMP
|
||||
*
|
||||
* Where V bit signifies if the measurement is ready and is
|
||||
* within sensor range. TEMP is an 8 bit value representing
|
||||
* temperature in C.
|
||||
* temperature in Celsius.
|
||||
|
||||
* For TMU Rev2:
|
||||
* 31 ... 8 7 6 5 4 3 2 1 0
|
||||
* V TEMP
|
||||
*
|
||||
* Where V bit signifies if the measurement is ready and is
|
||||
* within sensor range. TEMP is an 9 bit value representing
|
||||
* temperature in KelVin.
|
||||
*/
|
||||
if (regmap_read_poll_timeout(qdata->regmap,
|
||||
REGS_TRITSR(qsensor->id),
|
||||
|
@ -100,7 +114,10 @@ static int tmu_get_temp(void *p, int *temp)
|
|||
10 * USEC_PER_MSEC))
|
||||
return -ENODATA;
|
||||
|
||||
*temp = (val & 0xff) * 1000;
|
||||
if (qdata->ver == TMU_VER1)
|
||||
*temp = (val & GENMASK(7, 0)) * MILLIDEGREE_PER_DEGREE;
|
||||
else
|
||||
*temp = kelvin_to_millicelsius(val & GENMASK(8, 0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -192,6 +209,8 @@ static int qoriq_tmu_calibration(struct device *dev,
|
|||
|
||||
static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Disable interrupt, using polling instead */
|
||||
regmap_write(data->regmap, REGS_TIER, TIER_DISABLE);
|
||||
|
||||
|
@ -202,6 +221,8 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
|
|||
} else {
|
||||
regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT);
|
||||
regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2);
|
||||
for (i = 0; i < SITES_MAX; i++)
|
||||
regmap_write(data->regmap, REGS_V2_TMSAR(i), TMSARA_V2);
|
||||
}
|
||||
|
||||
/* Disable monitoring */
|
||||
|
@ -212,6 +233,7 @@ static const struct regmap_range qoriq_yes_ranges[] = {
|
|||
regmap_reg_range(REGS_TMR, REGS_TSCFGR),
|
||||
regmap_reg_range(REGS_TTRnCR(0), REGS_TTRnCR(3)),
|
||||
regmap_reg_range(REGS_V2_TEUMR(0), REGS_V2_TEUMR(2)),
|
||||
regmap_reg_range(REGS_V2_TMSAR(0), REGS_V2_TMSAR(15)),
|
||||
regmap_reg_range(REGS_IPBRR(0), REGS_IPBRR(1)),
|
||||
/* Read only registers below */
|
||||
regmap_reg_range(REGS_TRITSR(0), REGS_TRITSR(15)),
|
||||
|
|
|
@ -198,8 +198,8 @@ static void _rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
|
|||
static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
|
||||
{
|
||||
struct device *dev = rcar_priv_to_dev(priv);
|
||||
int i;
|
||||
u32 ctemp, old, new;
|
||||
int old, new, ctemp = -EINVAL;
|
||||
unsigned int i;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
|
@ -209,7 +209,6 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
|
|||
*/
|
||||
rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL);
|
||||
|
||||
ctemp = 0;
|
||||
old = ~0;
|
||||
for (i = 0; i < 128; i++) {
|
||||
/*
|
||||
|
@ -227,7 +226,7 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
|
|||
old = new;
|
||||
}
|
||||
|
||||
if (!ctemp) {
|
||||
if (ctemp < 0) {
|
||||
dev_err(dev, "thermal sensor was broken\n");
|
||||
goto err_out_unlock;
|
||||
}
|
||||
|
@ -248,7 +247,7 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
|
|||
err_out_unlock:
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
return ctemp ? ctemp : -EINVAL;
|
||||
return ctemp;
|
||||
}
|
||||
|
||||
static int rcar_thermal_get_current_temp(struct rcar_thermal_priv *priv,
|
||||
|
|
|
@ -1241,10 +1241,8 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
|
|||
return -ENXIO;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
dev_err(&pdev->dev, "no irq resource?\n");
|
||||
if (irq < 0)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
thermal = devm_kzalloc(&pdev->dev, sizeof(struct rockchip_thermal_data),
|
||||
GFP_KERNEL);
|
||||
|
|
|
@ -94,10 +94,8 @@ static int st_mmap_register_enable_irq(struct st_thermal_sensor *sensor)
|
|||
int ret;
|
||||
|
||||
sensor->irq = platform_get_irq(pdev, 0);
|
||||
if (sensor->irq < 0) {
|
||||
dev_err(dev, "failed to register IRQ\n");
|
||||
if (sensor->irq < 0)
|
||||
return sensor->irq;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(dev, sensor->irq,
|
||||
NULL, st_mmap_thermal_trip_handler,
|
||||
|
|
|
@ -385,10 +385,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor)
|
|||
int ret;
|
||||
|
||||
sensor->irq = platform_get_irq(pdev, 0);
|
||||
if (sensor->irq < 0) {
|
||||
dev_err(dev, "%s: Unable to find IRQ\n", __func__);
|
||||
if (sensor->irq < 0)
|
||||
return sensor->irq;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(dev, sensor->irq,
|
||||
NULL,
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kdev_t.h>
|
||||
#include <linux/idr.h>
|
||||
|
@ -27,10 +27,6 @@
|
|||
#include "thermal_core.h"
|
||||
#include "thermal_hwmon.h"
|
||||
|
||||
MODULE_AUTHOR("Zhang Rui");
|
||||
MODULE_DESCRIPTION("Generic thermal management sysfs support");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
||||
static DEFINE_IDA(thermal_tz_ida);
|
||||
static DEFINE_IDA(thermal_cdev_ida);
|
||||
|
||||
|
@ -447,12 +443,6 @@ static void update_temperature(struct thermal_zone_device *tz)
|
|||
mutex_unlock(&tz->lock);
|
||||
|
||||
trace_thermal_temperature(tz);
|
||||
if (tz->last_temperature == THERMAL_TEMP_INVALID)
|
||||
dev_dbg(&tz->device, "last_temperature N/A, current_temperature=%d\n",
|
||||
tz->temperature);
|
||||
else
|
||||
dev_dbg(&tz->device, "last_temperature=%d, current_temperature=%d\n",
|
||||
tz->last_temperature, tz->temperature);
|
||||
}
|
||||
|
||||
static void thermal_zone_device_init(struct thermal_zone_device *tz)
|
||||
|
|
|
@ -12,6 +12,17 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
/* Default Thermal Governor */
|
||||
#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "step_wise"
|
||||
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "fair_share"
|
||||
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "user_space"
|
||||
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "power_allocator"
|
||||
#endif
|
||||
|
||||
/* Initial state of a cooling device during binding */
|
||||
#define THERMAL_NO_TARGET -1UL
|
||||
|
||||
|
@ -30,6 +41,44 @@ extern struct thermal_governor *__governor_thermal_table_end[];
|
|||
__governor < __governor_thermal_table_end; \
|
||||
__governor++)
|
||||
|
||||
struct thermal_attr {
|
||||
struct device_attribute attr;
|
||||
char name[THERMAL_NAME_LENGTH];
|
||||
};
|
||||
|
||||
static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
return cdev->ops->get_requested_power && cdev->ops->state2power &&
|
||||
cdev->ops->power2state;
|
||||
}
|
||||
|
||||
int power_actor_get_max_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_zone_device *tz, u32 *max_power);
|
||||
int power_actor_get_min_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_zone_device *tz, u32 *min_power);
|
||||
int power_actor_set_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_instance *ti, u32 power);
|
||||
/**
|
||||
* struct thermal_trip - representation of a point in temperature domain
|
||||
* @np: pointer to struct device_node that this trip point was created from
|
||||
* @temperature: temperature value in miliCelsius
|
||||
* @hysteresis: relative hysteresis in miliCelsius
|
||||
* @type: trip point type
|
||||
*/
|
||||
struct thermal_trip {
|
||||
struct device_node *np;
|
||||
int temperature;
|
||||
int hysteresis;
|
||||
enum thermal_trip_type type;
|
||||
};
|
||||
|
||||
int get_tz_trend(struct thermal_zone_device *tz, int trip);
|
||||
|
||||
struct thermal_instance *
|
||||
get_thermal_instance(struct thermal_zone_device *tz,
|
||||
struct thermal_cooling_device *cdev,
|
||||
int trip);
|
||||
|
||||
/*
|
||||
* This structure is used to describe the behavior of
|
||||
* a certain cooling device on a certain trip point
|
||||
|
@ -69,6 +118,9 @@ void thermal_zone_device_unbind_exception(struct thermal_zone_device *,
|
|||
int thermal_zone_device_set_policy(struct thermal_zone_device *, char *);
|
||||
int thermal_build_list_of_policies(char *buf);
|
||||
|
||||
/* Helpers */
|
||||
void thermal_zone_set_trips(struct thermal_zone_device *tz);
|
||||
|
||||
/* sysfs I/F */
|
||||
int thermal_zone_create_device_groups(struct thermal_zone_device *, int);
|
||||
void thermal_zone_destroy_device_groups(struct thermal_zone_device *);
|
||||
|
|
|
@ -12,11 +12,12 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
#include <trace/events/thermal.h>
|
||||
|
||||
|
@ -113,6 +114,18 @@ exit:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_zone_get_temp);
|
||||
|
||||
/**
|
||||
* thermal_zone_set_trips - Computes the next trip points for the driver
|
||||
* @tz: a pointer to a thermal zone device structure
|
||||
*
|
||||
* The function computes the next temperature boundaries by browsing
|
||||
* the trip points. The result is the closer low and high trip points
|
||||
* to the current temperature. These values are passed to the backend
|
||||
* driver to let it set its own notification mechanism (usually an
|
||||
* interrupt).
|
||||
*
|
||||
* It does not return a value
|
||||
*/
|
||||
void thermal_zone_set_trips(struct thermal_zone_device *tz)
|
||||
{
|
||||
int low = -INT_MAX;
|
||||
|
@ -161,7 +174,6 @@ void thermal_zone_set_trips(struct thermal_zone_device *tz)
|
|||
exit:
|
||||
mutex_unlock(&tz->lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_zone_set_trips);
|
||||
|
||||
void thermal_cdev_update(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
|
|
|
@ -10,10 +10,12 @@
|
|||
* Copyright (C) 2013 Texas Instruments
|
||||
* Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
|
||||
*/
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#include "thermal_hwmon.h"
|
||||
|
||||
/* hwmon sys I/F */
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#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/of_device.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
#include "thermal_core.h"
|
|
@ -772,10 +772,9 @@ static int ti_bandgap_talert_init(struct ti_bandgap *bgp,
|
|||
int ret;
|
||||
|
||||
bgp->irq = platform_get_irq(pdev, 0);
|
||||
if (bgp->irq < 0) {
|
||||
dev_err(&pdev->dev, "get_irq failed\n");
|
||||
if (bgp->irq < 0)
|
||||
return bgp->irq;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(bgp->irq, NULL,
|
||||
ti_bandgap_talert_irq_handler,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
|
|
|
@ -169,7 +169,7 @@ int ti_thermal_expose_sensor(struct ti_bandgap *bgp, int id,
|
|||
|
||||
data = ti_bandgap_get_sensor_data(bgp, id);
|
||||
|
||||
if (!data || IS_ERR(data))
|
||||
if (!IS_ERR_OR_NULL(data))
|
||||
data = ti_thermal_build_data(bgp, id);
|
||||
|
||||
if (!data)
|
||||
|
@ -196,7 +196,7 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id)
|
|||
|
||||
data = ti_bandgap_get_sensor_data(bgp, id);
|
||||
|
||||
if (data && data->ti_thermal) {
|
||||
if (!IS_ERR_OR_NULL(data) && data->ti_thermal) {
|
||||
if (data->our_zone)
|
||||
thermal_zone_device_unregister(data->ti_thermal);
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id)
|
|||
|
||||
data = ti_bandgap_get_sensor_data(bgp, id);
|
||||
|
||||
if (data) {
|
||||
if (!IS_ERR_OR_NULL(data)) {
|
||||
cpufreq_cooling_unregister(data->cool_dev);
|
||||
if (data->policy)
|
||||
cpufreq_cpu_put(data->policy);
|
||||
|
|
|
@ -63,18 +63,10 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
|
|||
struct cpuidle_driver;
|
||||
|
||||
#ifdef CONFIG_CPU_IDLE_THERMAL
|
||||
int cpuidle_cooling_register(struct cpuidle_driver *drv);
|
||||
int cpuidle_of_cooling_register(struct device_node *np,
|
||||
struct cpuidle_driver *drv);
|
||||
void cpuidle_cooling_register(struct cpuidle_driver *drv);
|
||||
#else /* CONFIG_CPU_IDLE_THERMAL */
|
||||
static inline int cpuidle_cooling_register(struct cpuidle_driver *drv)
|
||||
static inline void cpuidle_cooling_register(struct cpuidle_driver *drv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline int cpuidle_of_cooling_register(struct device_node *np,
|
||||
struct cpuidle_driver *drv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_CPU_IDLE_THERMAL */
|
||||
|
||||
|
|
|
@ -26,4 +26,8 @@ void idle_inject_set_duration(struct idle_inject_device *ii_dev,
|
|||
void idle_inject_get_duration(struct idle_inject_device *ii_dev,
|
||||
unsigned int *run_duration_us,
|
||||
unsigned int *idle_duration_us);
|
||||
|
||||
void idle_inject_set_latency(struct idle_inject_device *ii_dev,
|
||||
unsigned int latency_ns);
|
||||
|
||||
#endif /* __IDLE_INJECT_H__ */
|
||||
|
|
|
@ -32,20 +32,10 @@
|
|||
/* use value, which < 0K, to indicate an invalid/uninitialized temperature */
|
||||
#define THERMAL_TEMP_INVALID -274000
|
||||
|
||||
/* Default Thermal Governor */
|
||||
#if defined(CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "step_wise"
|
||||
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "fair_share"
|
||||
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "user_space"
|
||||
#elif defined(CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR)
|
||||
#define DEFAULT_THERMAL_GOVERNOR "power_allocator"
|
||||
#endif
|
||||
|
||||
struct thermal_zone_device;
|
||||
struct thermal_cooling_device;
|
||||
struct thermal_instance;
|
||||
struct thermal_attr;
|
||||
|
||||
enum thermal_device_mode {
|
||||
THERMAL_DEVICE_DISABLED = 0,
|
||||
|
@ -130,11 +120,6 @@ struct thermal_cooling_device {
|
|||
struct list_head node;
|
||||
};
|
||||
|
||||
struct thermal_attr {
|
||||
struct device_attribute attr;
|
||||
char name[THERMAL_NAME_LENGTH];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct thermal_zone_device - structure for a thermal zone
|
||||
* @id: unique id number for each thermal zone
|
||||
|
@ -347,21 +332,6 @@ struct thermal_zone_of_device_ops {
|
|||
int (*set_trip_temp)(void *, int, int);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct thermal_trip - representation of a point in temperature domain
|
||||
* @np: pointer to struct device_node that this trip point was created from
|
||||
* @temperature: temperature value in miliCelsius
|
||||
* @hysteresis: relative hysteresis in miliCelsius
|
||||
* @type: trip point type
|
||||
*/
|
||||
|
||||
struct thermal_trip {
|
||||
struct device_node *np;
|
||||
int temperature;
|
||||
int hysteresis;
|
||||
enum thermal_trip_type type;
|
||||
};
|
||||
|
||||
/* Function declarations */
|
||||
#ifdef CONFIG_THERMAL_OF
|
||||
int thermal_zone_of_get_sensor_id(struct device_node *tz_np,
|
||||
|
@ -413,19 +383,7 @@ void devm_thermal_zone_of_sensor_unregister(struct device *dev,
|
|||
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_THERMAL)
|
||||
static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
return cdev->ops->get_requested_power && cdev->ops->state2power &&
|
||||
cdev->ops->power2state;
|
||||
}
|
||||
|
||||
int power_actor_get_max_power(struct thermal_cooling_device *,
|
||||
struct thermal_zone_device *tz, u32 *max_power);
|
||||
int power_actor_get_min_power(struct thermal_cooling_device *,
|
||||
struct thermal_zone_device *tz, u32 *min_power);
|
||||
int power_actor_set_power(struct thermal_cooling_device *,
|
||||
struct thermal_instance *, u32);
|
||||
#ifdef CONFIG_THERMAL
|
||||
struct thermal_zone_device *thermal_zone_device_register(const char *, int, int,
|
||||
void *, struct thermal_zone_device_ops *,
|
||||
struct thermal_zone_params *, int, int);
|
||||
|
@ -439,7 +397,6 @@ int thermal_zone_unbind_cooling_device(struct thermal_zone_device *, int,
|
|||
struct thermal_cooling_device *);
|
||||
void thermal_zone_device_update(struct thermal_zone_device *,
|
||||
enum thermal_notify_event);
|
||||
void thermal_zone_set_trips(struct thermal_zone_device *);
|
||||
|
||||
struct thermal_cooling_device *thermal_cooling_device_register(const char *,
|
||||
void *, const struct thermal_cooling_device_ops *);
|
||||
|
@ -457,24 +414,9 @@ int thermal_zone_get_temp(struct thermal_zone_device *tz, int *temp);
|
|||
int thermal_zone_get_slope(struct thermal_zone_device *tz);
|
||||
int thermal_zone_get_offset(struct thermal_zone_device *tz);
|
||||
|
||||
int get_tz_trend(struct thermal_zone_device *, int);
|
||||
struct thermal_instance *get_thermal_instance(struct thermal_zone_device *,
|
||||
struct thermal_cooling_device *, int);
|
||||
void thermal_cdev_update(struct thermal_cooling_device *);
|
||||
void thermal_notify_framework(struct thermal_zone_device *, int);
|
||||
#else
|
||||
static inline bool cdev_is_power_actor(struct thermal_cooling_device *cdev)
|
||||
{ return false; }
|
||||
static inline int power_actor_get_max_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_zone_device *tz, u32 *max_power)
|
||||
{ return 0; }
|
||||
static inline int power_actor_get_min_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_zone_device *tz,
|
||||
u32 *min_power)
|
||||
{ return -ENODEV; }
|
||||
static inline int power_actor_set_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_instance *tz, u32 power)
|
||||
{ return 0; }
|
||||
static inline struct thermal_zone_device *thermal_zone_device_register(
|
||||
const char *type, int trips, int mask, void *devdata,
|
||||
struct thermal_zone_device_ops *ops,
|
||||
|
@ -484,21 +426,6 @@ static inline struct thermal_zone_device *thermal_zone_device_register(
|
|||
static inline void thermal_zone_device_unregister(
|
||||
struct thermal_zone_device *tz)
|
||||
{ }
|
||||
static inline int thermal_zone_bind_cooling_device(
|
||||
struct thermal_zone_device *tz, int trip,
|
||||
struct thermal_cooling_device *cdev,
|
||||
unsigned long upper, unsigned long lower,
|
||||
unsigned int weight)
|
||||
{ return -ENODEV; }
|
||||
static inline int thermal_zone_unbind_cooling_device(
|
||||
struct thermal_zone_device *tz, int trip,
|
||||
struct thermal_cooling_device *cdev)
|
||||
{ return -ENODEV; }
|
||||
static inline void thermal_zone_device_update(struct thermal_zone_device *tz,
|
||||
enum thermal_notify_event event)
|
||||
{ }
|
||||
static inline void thermal_zone_set_trips(struct thermal_zone_device *tz)
|
||||
{ }
|
||||
static inline struct thermal_cooling_device *
|
||||
thermal_cooling_device_register(char *type, void *devdata,
|
||||
const struct thermal_cooling_device_ops *ops)
|
||||
|
@ -530,12 +457,7 @@ static inline int thermal_zone_get_slope(
|
|||
static inline int thermal_zone_get_offset(
|
||||
struct thermal_zone_device *tz)
|
||||
{ return -ENODEV; }
|
||||
static inline int get_tz_trend(struct thermal_zone_device *tz, int trip)
|
||||
{ return -ENODEV; }
|
||||
static inline struct thermal_instance *
|
||||
get_thermal_instance(struct thermal_zone_device *tz,
|
||||
struct thermal_cooling_device *cdev, int trip)
|
||||
{ return ERR_PTR(-ENODEV); }
|
||||
|
||||
static inline void thermal_cdev_update(struct thermal_cooling_device *cdev)
|
||||
{ }
|
||||
static inline void thermal_notify_framework(struct thermal_zone_device *tz,
|
||||
|
|
Загрузка…
Ссылка в новой задаче