- Depromote debug print on the db8500 platform (Linus Walleij)

- Fix compilation warning when compiling with make W=1 (Amit Kucheria)
 
 - Code cleanup and refactoring, regmap conversion and add hwmon
   support on Qoriq (Andrey Smirnov)
 
 - Add an idle injection cpu cooling device and its documentation,
   rename the cpu_cooling device to cpufreq_cooling device (Daniel
   Lezcano)
 
 - Convert unexported functions to static, add the __init annotation in
   the thermal-of code and remove the pointless wrapper functions
   (Daniel Lezcano)
 
 - Fix register offset for Armada XP and register reset bit
   initialization (Zak Hays)
 
 - Enable hwmon on the rockchip (Stefan Schaeckeler)
 
 - Add the thermal sensor for the H6/H5/H3/A64/A83T/R40 sun8i platform
   and their device tree bindings, followed by a fix for the ths number
   and the sparse warnings (Yangtao Li)
 
 - Code cleansup for the sun8i and hwmon support (Yangtao Li)
 
 - Silent some messages which are misleading given the changes made in
   the previous version on generic-adc (Martin Blumenstingl)
 
 - Rename exynos to Exynos (Krzysztof Kozlowski)
 
 - Add the bcm2711 thermal driver with the device tree bindings (Stefan
   Wahren)
 
 - Use usleep_range() instead of udelay() as the call is always done in
   a sleep-able context (Geert Uytterhoeven)
 
 - Do code cleanup and re-organization to set the scene for a new
   process for the brcmstb (Florian Fainelli)
 
 - Fix bindings check issues on brcm (Stefan Wahren)
 
 - Add Jasper Lake support on int340x (Nivedita Swaminathan)
 
 - Add Comet Lake support on intel pch (Gayatri Kammela)
 
 - Fix unmatched pci_release_region() on x86 (Chuhong Yuan)
 
 - Remove temperature boundaries for rcar and rcar3 (Niklas Söderlund)
 
 - Fix return value to -ENODEV when thermal_zone_of_sensor_register()
   is called with the of-node is missing (Peter Mamonov)
 
 - Code cleanup, interrupt bouncing, and better support on stm32
   (Pascal Paillet)
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQRuKdf4M92Gi9vqihve5qtOL396pgUCXjAFCAAKCRDe5qtOL396
 prIfAQCjhNWKqP3U4oewia9p8dwwfsOpJMqUXge/k6sKzAqscAD/Rg4lWXaayOsX
 OWKhS/iL5eN7aXVnSH4MxONsTWhLTAs=
 =eZkX
 -----END PGP SIGNATURE-----

Merge tag 'thermal-v5.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux

Pull thermal updates from Daniel Lezcano:

 - Depromote debug print on the db8500 platform (Linus Walleij)

 - Fix compilation warning when compiling with make W=1 (Amit Kucheria)

 - Code cleanup and refactoring, regmap conversion and add hwmon support
   on Qoriq (Andrey Smirnov)

 - Add an idle injection cpu cooling device and its documentation,
   rename the cpu_cooling device to cpufreq_cooling device (Daniel
   Lezcano)

 - Convert unexported functions to static, add the __init annotation in
   the thermal-of code and remove the pointless wrapper functions
   (Daniel Lezcano)

 - Fix register offset for Armada XP and register reset bit
   initialization (Zak Hays)

 - Enable hwmon on the rockchip (Stefan Schaeckeler)

 - Add the thermal sensor for the H6/H5/H3/A64/A83T/R40 sun8i platform
   and their device tree bindings, followed by a fix for the ths number
   and the sparse warnings (Yangtao Li)

 - Code cleansup for the sun8i and hwmon support (Yangtao Li)

 - Silent some messages which are misleading given the changes made in
   the previous version on generic-adc (Martin Blumenstingl)

 - Rename exynos to Exynos (Krzysztof Kozlowski)

 - Add the bcm2711 thermal driver with the device tree bindings (Stefan
   Wahren)

 - Use usleep_range() instead of udelay() as the call is always done in
   a sleep-able context (Geert Uytterhoeven)

 - Do code cleanup and re-organization to set the scene for a new
   process for the brcmstb (Florian Fainelli)

 - Fix bindings check issues on brcm (Stefan Wahren)

 - Add Jasper Lake support on int340x (Nivedita Swaminathan)

 - Add Comet Lake support on intel pch (Gayatri Kammela)

 - Fix unmatched pci_release_region() on x86 (Chuhong Yuan)

 - Remove temperature boundaries for rcar and rcar3 (Niklas Söderlund)

 - Fix return value to -ENODEV when thermal_zone_of_sensor_register() is
   called with the of-node is missing (Peter Mamonov)

 - Code cleanup, interrupt bouncing, and better support on stm32 (Pascal
   Paillet)

* tag 'thermal-v5.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thermal/linux: (66 commits)
  thermal: stm32: Fix low threshold interrupt flood
  thermal: stm32: Improve temperature computing
  thermal: stm32: Handle multiple trip points
  thermal: stm32: Disable interrupts at probe
  thermal: stm32: Rework sensor mode management
  thermal: stm32: Fix icifr register name
  thermal: of: Make thermal_zone_of_sensor_register return -ENODEV if a sensor OF node is missing
  thermal: rcar_gen3_thermal: Remove temperature bound
  thermal: rcar_thermal: Remove temperature bound
  thermal: intel: intel_pch_thermal: Add Comet Lake (CML) platform support
  thermal: intel: Fix unmatched pci_release_region
  thermal: int340x: processor_thermal: Add Jasper Lake support
  dt-bindings: brcm,avs-ro-thermal: Fix binding check issues
  thermal: brcmstb_thermal: Register different ops per process
  thermal: brcmstb_thermal: Restructure interrupt registration
  thermal: brcmstb_thermal: Add 16nm process thermal parameters
  dt-bindings: thermal: Define BCM7216 thermal sensor compatible
  thermal: brcmstb_thermal: Prepare to support a different process
  thermal: brcmstb_thermal: Do not use DT coefficients
  thermal: rcar_thermal: Use usleep_range() instead of udelay()
  ...
This commit is contained in:
Linus Torvalds 2020-01-28 16:31:08 -08:00
commit abb22e44cf
48 changed files with 2019 additions and 597 deletions

View File

@ -0,0 +1,160 @@
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/thermal/allwinner,sun8i-a83t-ths.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Allwinner SUN8I Thermal Controller Device Tree Bindings
maintainers:
- Vasily Khoruzhick <anarsoul@gmail.com>
- Yangtao Li <tiny.windzz@gmail.com>
properties:
compatible:
enum:
- allwinner,sun8i-a83t-ths
- allwinner,sun8i-h3-ths
- allwinner,sun8i-r40-ths
- allwinner,sun50i-a64-ths
- allwinner,sun50i-h5-ths
- allwinner,sun50i-h6-ths
clocks:
minItems: 1
maxItems: 2
items:
- description: Bus Clock
- description: Module Clock
clock-names:
minItems: 1
maxItems: 2
items:
- const: bus
- const: mod
reg:
maxItems: 1
interrupts:
maxItems: 1
resets:
maxItems: 1
nvmem-cells:
maxItems: 1
description: Calibration data for thermal sensors
nvmem-cell-names:
const: calibration
# See ./thermal.txt for details
"#thermal-sensor-cells":
enum:
- 0
- 1
allOf:
- if:
properties:
compatible:
contains:
const: allwinner,sun50i-h6-ths
then:
properties:
clocks:
maxItems: 1
clock-names:
maxItems: 1
else:
properties:
clocks:
minItems: 2
clock-names:
minItems: 2
- if:
properties:
compatible:
contains:
const: allwinner,sun8i-h3-ths
then:
properties:
"#thermal-sensor-cells":
const: 0
else:
properties:
"#thermal-sensor-cells":
const: 1
- if:
properties:
compatible:
contains:
enum:
- const: allwinner,sun8i-h3-ths
- const: allwinner,sun8i-r40-ths
- const: allwinner,sun50i-a64-ths
- const: allwinner,sun50i-h5-ths
- const: allwinner,sun50i-h6-ths
then:
required:
- clocks
- clock-names
- resets
required:
- compatible
- reg
- interrupts
- '#thermal-sensor-cells'
additionalProperties: false
examples:
- |
thermal-sensor@1f04000 {
compatible = "allwinner,sun8i-a83t-ths";
reg = <0x01f04000 0x100>;
interrupts = <0 31 0>;
nvmem-cells = <&ths_calibration>;
nvmem-cell-names = "calibration";
#thermal-sensor-cells = <1>;
};
- |
thermal-sensor@1c25000 {
compatible = "allwinner,sun8i-h3-ths";
reg = <0x01c25000 0x400>;
clocks = <&ccu 0>, <&ccu 1>;
clock-names = "bus", "mod";
resets = <&ccu 2>;
interrupts = <0 31 0>;
nvmem-cells = <&ths_calibration>;
nvmem-cell-names = "calibration";
#thermal-sensor-cells = <0>;
};
- |
thermal-sensor@5070400 {
compatible = "allwinner,sun50i-h6-ths";
reg = <0x05070400 0x100>;
clocks = <&ccu 0>;
clock-names = "bus";
resets = <&ccu 2>;
interrupts = <0 15 0>;
nvmem-cells = <&ths_calibration>;
nvmem-cell-names = "calibration";
#thermal-sensor-cells = <1>;
};
...

View File

@ -0,0 +1,48 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/thermal/brcm,avs-ro-thermal.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Broadcom AVS ring oscillator thermal
maintainers:
- Stefan Wahren <wahrenst@gmx.net>
description: |+
The thermal node should be the child of a syscon node with the
required property:
- compatible: Should be one of the following:
"brcm,bcm2711-avs-monitor", "syscon", "simple-mfd"
Refer to the the bindings described in
Documentation/devicetree/bindings/mfd/syscon.txt
properties:
compatible:
const: brcm,bcm2711-thermal
# See ./thermal.txt for details
"#thermal-sensor-cells":
const: 0
required:
- compatible
- '#thermal-sensor-cells'
additionalProperties: false
examples:
- |
avs-monitor@7d5d2000 {
compatible = "brcm,bcm2711-avs-monitor",
"syscon", "simple-mfd";
reg = <0x7d5d2000 0xf00>;
thermal: thermal {
compatible = "brcm,bcm2711-thermal";
#thermal-sensor-cells = <0>;
};
};
...

View File

@ -3,9 +3,13 @@
Thermal management core, provided by the AVS TMON hardware block.
Required properties:
- compatible: must be "brcm,avs-tmon" and/or "brcm,avs-tmon-bcm7445"
- compatible: must be one of:
"brcm,avs-tmon-bcm7216"
"brcm,avs-tmon-bcm7445"
"brcm,avs-tmon"
- reg: address range for the AVS TMON registers
- interrupts: temperature monitor interrupt, for high/low threshold triggers
- interrupts: temperature monitor interrupt, for high/low threshold triggers,
required except for "brcm,avs-tmon-bcm7216"
- interrupt-names: should be "tmon"
Example:

View File

@ -0,0 +1,189 @@
Situation:
----------
Under certain circumstances a SoC can reach a critical temperature
limit and is unable to stabilize the temperature around a temperature
control. When the SoC has to stabilize the temperature, the kernel can
act on a cooling device to mitigate the dissipated power. When the
critical temperature is reached, a decision must be taken to reduce
the temperature, that, in turn impacts performance.
Another situation is when the silicon temperature continues to
increase even after the dynamic leakage is reduced to its minimum by
clock gating the component. This runaway phenomenon can continue due
to the static leakage. The only solution is to power down the
component, thus dropping the dynamic and static leakage that will
allow the component to cool down.
Last but not least, the system can ask for a specific power budget but
because of the OPP density, we can only choose an OPP with a power
budget lower than the requested one and under-utilize the CPU, thus
losing performance. In other words, one OPP under-utilizes the CPU
with a power less than the requested power budget and the next OPP
exceeds the power budget. An intermediate OPP could have been used if
it were present.
Solutions:
----------
If we can remove the static and the dynamic leakage for a specific
duration in a controlled period, the SoC temperature will
decrease. Acting on the idle state duration or the idle cycle
injection period, we can mitigate the temperature by modulating the
power budget.
The Operating Performance Point (OPP) density has a great influence on
the control precision of cpufreq, however different vendors have a
plethora of OPP density, and some have large power gap between OPPs,
that will result in loss of performance during thermal control and
loss of power in other scenarios.
At a specific OPP, we can assume that injecting idle cycle on all CPUs
belong to the same cluster, with a duration greater than the cluster
idle state target residency, we lead to dropping the static and the
dynamic leakage for this period (modulo the energy needed to enter
this state). So the sustainable power with idle cycles has a linear
relation with the OPPs sustainable power and can be computed with a
coefficient similar to:
Power(IdleCycle) = Coef x Power(OPP)
Idle Injection:
---------------
The base concept of the idle injection is to force the CPU to go to an
idle state for a specified time each control cycle, it provides
another way to control CPU power and heat in addition to
cpufreq. Ideally, if all CPUs belonging to the same cluster, inject
their idle cycles synchronously, the cluster can reach its power down
state with a minimum power consumption and reduce the static leakage
to almost zero. However, these idle cycles injection will add extra
latencies as the CPUs will have to wakeup from a deep sleep state.
We use a fixed duration of idle injection that gives an acceptable
performance penalty and a fixed latency. Mitigation can be increased
or decreased by modulating the duty cycle of the idle injection.
^
|
|
|------- -------
|_______|_______________________|_______|___________
<------>
idle <---------------------->
running
<----------------------------->
duty cycle 25%
The implementation of the cooling device bases the number of states on
the duty cycle percentage. When no mitigation is happening the cooling
device state is zero, meaning the duty cycle is 0%.
When the mitigation begins, depending on the governor's policy, a
starting state is selected. With a fixed idle duration and the duty
cycle (aka the cooling device state), the running duration can be
computed.
The governor will change the cooling device state thus the duty cycle
and this variation will modulate the cooling effect.
^
|
|
|------- -------
|_______|_______________|_______|___________
<------>
idle <-------------->
running
<----------------------------->
duty cycle 33%
^
|
|
|------- -------
|_______|_______|_______|___________
<------>
idle <------>
running
<------------->
duty cycle 50%
The idle injection duration value must comply with the constraints:
- It is less than or equal to the latency we tolerate when the
mitigation begins. It is platform dependent and will depend on the
user experience, reactivity vs performance trade off we want. This
value should be specified.
- It is greater than the idle states target residency we want to go
for thermal mitigation, otherwise we end up consuming more energy.
Power considerations
--------------------
When we reach the thermal trip point, we have to sustain a specified
power for a specific temperature but at this time we consume:
Power = Capacitance x Voltage^2 x Frequency x Utilisation
... which is more than the sustainable power (or there is something
wrong in the system setup). The Capacitance and Utilisation are a
fixed value, Voltage and the Frequency are fixed artificially
because we dont want to change the OPP. We can group the
Capacitance and the Utilisation into a single term which is the
Dynamic Power Coefficient (Cdyn) Simplifying the above, we have:
Pdyn = Cdyn x Voltage^2 x Frequency
The power allocator governor will ask us somehow to reduce our power
in order to target the sustainable power defined in the device
tree. So with the idle injection mechanism, we want an average power
(Ptarget) resulting in an amount of time running at full power on a
specific OPP and idle another amount of time. That could be put in a
equation:
P(opp)target = ((Trunning x (P(opp)running) + (Tidle x P(opp)idle)) /
(Trunning + Tidle)
...
Tidle = Trunning x ((P(opp)running / P(opp)target) - 1)
At this point if we know the running period for the CPU, that gives us
the idle injection we need. Alternatively if we have the idle
injection duration, we can compute the running duration with:
Trunning = Tidle / ((P(opp)running / P(opp)target) - 1)
Practically, if the running power is less than the targeted power, we
end up with a negative time value, so obviously the equation usage is
bound to a power reduction, hence a higher OPP is needed to have the
running power greater than the targeted power.
However, in this demonstration we ignore three aspects:
* The static leakage is not defined here, we can introduce it in the
equation but assuming it will be zero most of the time as it is
difficult to get the values from the SoC vendors
* The idle state wake up latency (or entry + exit latency) is not
taken into account, it must be added in the equation in order to
rigorously compute the idle injection
* The injected idle duration must be greater than the idle state
target residency, otherwise we end up consuming more energy and
potentially invert the mitigation effect
So the final equation is:
Trunning = (Tidle - Twakeup ) x
(((P(opp)dyn + P(opp)static ) - P(opp)target) / P(opp)target )

View File

@ -4,7 +4,7 @@ Kernel driver exynos_tmu
Supported chips:
* ARM SAMSUNG EXYNOS4, EXYNOS5 series of SoC
* ARM Samsung Exynos4, Exynos5 series of SoC
Datasheet: Not publicly available
@ -14,7 +14,7 @@ Authors: Amit Daniel <amit.daniel@samsung.com>
TMU controller Description:
---------------------------
This driver allows to read temperature inside SAMSUNG EXYNOS4/5 series of SoC.
This driver allows to read temperature inside Samsung Exynos4/5 series of SoC.
The chip only exposes the measured 8-bit temperature code value
through a register.
@ -43,7 +43,7 @@ The three equations are:
Trimming info for 85 degree Celsius (stored at TRIMINFO register)
Temperature code measured at 85 degree Celsius which is unchanged
TMU(Thermal Management Unit) in EXYNOS4/5 generates interrupt
TMU(Thermal Management Unit) in Exynos4/5 generates interrupt
when temperature exceeds pre-defined levels.
The maximum number of configurable threshold is five.
The threshold levels are defined as follows::
@ -67,7 +67,7 @@ TMU driver description:
The exynos thermal driver is structured as::
Kernel Core thermal framework
(thermal_core.c, step_wise.c, cpu_cooling.c)
(thermal_core.c, step_wise.c, cpufreq_cooling.c)
^
|
|

View File

@ -694,6 +694,14 @@ L: linux-crypto@vger.kernel.org
S: Maintained
F: drivers/crypto/allwinner/
ALLWINNER THERMAL DRIVER
M: Vasily Khoruzhick <anarsoul@gmail.com>
M: Yangtao Li <tiny.windzz@gmail.com>
L: linux-pm@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/thermal/allwinner,sun8i-a83t-ths.yaml
F: drivers/thermal/sun8i_thermal.c
ALLWINNER VPU DRIVER
M: Maxime Ripard <mripard@kernel.org>
M: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
@ -16432,12 +16440,15 @@ F: Documentation/devicetree/bindings/thermal/
THERMAL/CPU_COOLING
M: Amit Daniel Kachhap <amit.kachhap@gmail.com>
M: Daniel Lezcano <daniel.lezcano@linaro.org>
M: Viresh Kumar <viresh.kumar@linaro.org>
M: Javi Merino <javi.merino@kernel.org>
L: linux-pm@vger.kernel.org
S: Supported
F: Documentation/driver-api/thermal/cpu-cooling-api.rst
F: drivers/thermal/cpu_cooling.c
F: Documentation/driver-api/thermal/cpu-idle-cooling.rst
F: drivers/thermal/cpufreq_cooling.c
F: drivers/thermal/cpuidle_cooling.c
F: include/linux/cpu_cooling.h
THERMAL DRIVER FOR AMLOGIC SOCS

View File

@ -66,6 +66,17 @@
IRQ_TYPE_LEVEL_HIGH)>;
};
avs_monitor: avs-monitor@7d5d2000 {
compatible = "brcm,bcm2711-avs-monitor",
"syscon", "simple-mfd";
reg = <0x7d5d2000 0xf00>;
thermal: thermal {
compatible = "brcm,bcm2711-thermal";
#thermal-sensor-cells = <0>;
};
};
dma: dma@7e007000 {
compatible = "brcm,bcm2835-dma";
reg = <0x7e007000 0xb00>;
@ -363,6 +374,7 @@
&cpu_thermal {
coefficients = <(-487) 410040>;
thermal-sensors = <&thermal>;
};
&dsi0 {

View File

@ -496,6 +496,7 @@ CONFIG_IMX_THERMAL=y
CONFIG_ROCKCHIP_THERMAL=y
CONFIG_RCAR_THERMAL=y
CONFIG_ARMADA_THERMAL=y
CONFIG_BCM2711_THERMAL=m
CONFIG_BCM2835_THERMAL=m
CONFIG_BRCMSTB_THERMAL=m
CONFIG_ST_THERMAL_MEMMAP=y

View File

@ -442,6 +442,7 @@ CONFIG_ROCKCHIP_THERMAL=m
CONFIG_RCAR_THERMAL=y
CONFIG_RCAR_GEN3_THERMAL=y
CONFIG_ARMADA_THERMAL=y
CONFIG_BCM2711_THERMAL=m
CONFIG_BCM2835_THERMAL=m
CONFIG_BRCMSTB_THERMAL=m
CONFIG_EXYNOS_THERMAL=y

View File

@ -151,8 +151,18 @@ config THERMAL_GOV_POWER_ALLOCATOR
config CPU_THERMAL
bool "Generic cpu cooling support"
depends on CPU_FREQ
depends on THERMAL_OF
help
Enable the CPU cooling features. If the system has no active
cooling device available, this option allows to use the CPU
as a cooling device.
if CPU_THERMAL
config CPU_FREQ_THERMAL
bool "CPU frequency cooling device"
depends on CPU_FREQ
default y
help
This implements the generic cpu cooling mechanism through frequency
reduction. An ACPI version of this already exists
@ -160,7 +170,14 @@ config CPU_THERMAL
This will be useful for platforms using the generic thermal interface
and not the ACPI interface.
If you want this support, you should say Y here.
config CPU_IDLE_THERMAL
bool "CPU idle cooling device"
depends on IDLE_INJECT
help
This implements the CPU cooling mechanism through
idle injection. This will throttle the CPU by injecting
idle cycle.
endif
config CLOCK_THERMAL
bool "Generic clock cooling support"
@ -263,6 +280,20 @@ config SPEAR_THERMAL
Enable this to plug the SPEAr thermal sensor driver into the Linux
thermal framework.
config SUN8I_THERMAL
tristate "Allwinner sun8i thermal driver"
depends on ARCH_SUNXI || COMPILE_TEST
depends on HAS_IOMEM
depends on NVMEM
depends on OF
depends on RESET_CONTROLLER
help
Support for the sun8i thermal sensor driver into the Linux thermal
framework.
To compile this driver as a module, choose M here: the
module will be called sun8i-thermal.
config ROCKCHIP_THERMAL
tristate "Rockchip thermal driver"
depends on ARCH_ROCKCHIP || COMPILE_TEST

View File

@ -19,7 +19,8 @@ thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o
# cpufreq cooling
thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
thermal_sys-$(CONFIG_CPU_FREQ_THERMAL) += cpufreq_cooling.o
thermal_sys-$(CONFIG_CPU_IDLE_THERMAL) += cpuidle_cooling.o
# clock cooling
thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
@ -31,6 +32,7 @@ thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o
obj-y += broadcom/
obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_SUN8I_THERMAL) += sun8i_thermal.o
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o

View File

@ -67,7 +67,11 @@
/**
* struct amlogic_thermal_soc_calib_data
* @A, B, m, n: calibration parameters
* @A: calibration parameters
* @B: calibration parameters
* @m: calibration parameters
* @n: calibration parameters
*
* This structure is required for configuration of amlogic thermal driver.
*/
struct amlogic_thermal_soc_calib_data {

View File

@ -155,6 +155,9 @@ static void armadaxp_init(struct platform_device *pdev,
regmap_write(priv->syscon, data->syscon_control1_off, reg);
reg &= ~PMU_TDC0_SW_RST_MASK;
regmap_write(priv->syscon, data->syscon_control1_off, reg);
/* Enable the sensor */
regmap_read(priv->syscon, data->syscon_status_off, &reg);
reg &= ~PMU_TM_DISABLE_MASK;
@ -578,7 +581,7 @@ static const struct armada_thermal_data armadaxp_data = {
.coef_m = 10000000ULL,
.coef_div = 13825,
.syscon_status_off = 0xb0,
.syscon_control1_off = 0xd0,
.syscon_control1_off = 0x2d0,
};
static const struct armada_thermal_data armada370_data = {

View File

@ -1,4 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
config BCM2711_THERMAL
tristate "Broadcom AVS RO thermal sensor driver"
depends on ARCH_BCM2835 || COMPILE_TEST
depends on THERMAL_OF && MFD_SYSCON
help
Support for thermal sensors on Broadcom BCM2711 SoCs.
config BCM2835_THERMAL
tristate "Thermal sensors on bcm2835 SoC"
depends on ARCH_BCM2835 || COMPILE_TEST

View File

@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_BCM2711_THERMAL) += bcm2711_thermal.o
obj-$(CONFIG_BCM2835_THERMAL) += bcm2835_thermal.o
obj-$(CONFIG_BRCMSTB_THERMAL) += brcmstb_thermal.o
obj-$(CONFIG_BCM_NS_THERMAL) += ns-thermal.o

View File

@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Broadcom AVS RO thermal sensor driver
*
* based on brcmstb_thermal
*
* Copyright (C) 2020 Stefan Wahren
*/
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/thermal.h>
#include "../thermal_hwmon.h"
#define AVS_RO_TEMP_STATUS 0x200
#define AVS_RO_TEMP_STATUS_VALID_MSK (BIT(16) | BIT(10))
#define AVS_RO_TEMP_STATUS_DATA_MSK GENMASK(9, 0)
struct bcm2711_thermal_priv {
struct regmap *regmap;
struct thermal_zone_device *thermal;
};
static int bcm2711_get_temp(void *data, int *temp)
{
struct bcm2711_thermal_priv *priv = data;
int slope = thermal_zone_get_slope(priv->thermal);
int offset = thermal_zone_get_offset(priv->thermal);
u32 val;
int ret;
long t;
ret = regmap_read(priv->regmap, AVS_RO_TEMP_STATUS, &val);
if (ret)
return ret;
if (!(val & AVS_RO_TEMP_STATUS_VALID_MSK))
return -EIO;
val &= AVS_RO_TEMP_STATUS_DATA_MSK;
/* Convert a HW code to a temperature reading (millidegree celsius) */
t = slope * val + offset;
*temp = t < 0 ? 0 : t;
return 0;
}
static const struct thermal_zone_of_device_ops bcm2711_thermal_of_ops = {
.get_temp = bcm2711_get_temp,
};
static const struct of_device_id bcm2711_thermal_id_table[] = {
{ .compatible = "brcm,bcm2711-thermal" },
{},
};
MODULE_DEVICE_TABLE(of, bcm2711_thermal_id_table);
static int bcm2711_thermal_probe(struct platform_device *pdev)
{
struct thermal_zone_device *thermal;
struct bcm2711_thermal_priv *priv;
struct device *dev = &pdev->dev;
struct device_node *parent;
struct regmap *regmap;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* get regmap from syscon node */
parent = of_get_parent(dev->of_node); /* parent should be syscon node */
regmap = syscon_node_to_regmap(parent);
of_node_put(parent);
if (IS_ERR(regmap)) {
ret = PTR_ERR(regmap);
dev_err(dev, "failed to get regmap: %d\n", ret);
return ret;
}
priv->regmap = regmap;
thermal = devm_thermal_zone_of_sensor_register(dev, 0, priv,
&bcm2711_thermal_of_ops);
if (IS_ERR(thermal)) {
ret = PTR_ERR(thermal);
dev_err(dev, "could not register sensor: %d\n", ret);
return ret;
}
priv->thermal = thermal;
thermal->tzp->no_hwmon = false;
ret = thermal_add_hwmon_sysfs(thermal);
if (ret)
return ret;
return 0;
}
static struct platform_driver bcm2711_thermal_driver = {
.probe = bcm2711_thermal_probe,
.driver = {
.name = "bcm2711_thermal",
.of_match_table = bcm2711_thermal_id_table,
},
};
module_platform_driver(bcm2711_thermal_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stefan Wahren");
MODULE_DESCRIPTION("Broadcom AVS RO thermal sensor driver");

View File

@ -49,7 +49,7 @@
#define AVS_TMON_TP_TEST_ENABLE 0x20
/* Default coefficients */
#define AVS_TMON_TEMP_SLOPE -487
#define AVS_TMON_TEMP_SLOPE 487
#define AVS_TMON_TEMP_OFFSET 410040
/* HW related temperature constants */
@ -102,29 +102,28 @@ static struct avs_tmon_trip avs_tmon_trips[] = {
},
};
struct brcmstb_thermal_params {
unsigned int offset;
unsigned int mult;
const struct thermal_zone_of_device_ops *of_ops;
};
struct brcmstb_thermal_priv {
void __iomem *tmon_base;
struct device *dev;
struct thermal_zone_device *thermal;
/* Process specific thermal parameters used for calculations */
const struct brcmstb_thermal_params *temp_params;
};
static void avs_tmon_get_coeffs(struct thermal_zone_device *tz, int *slope,
int *offset)
{
*slope = thermal_zone_get_slope(tz);
*offset = thermal_zone_get_offset(tz);
}
/* Convert a HW code to a temperature reading (millidegree celsius) */
static inline int avs_tmon_code_to_temp(struct thermal_zone_device *tz,
static inline int avs_tmon_code_to_temp(struct brcmstb_thermal_priv *priv,
u32 code)
{
const int val = code & AVS_TMON_TEMP_MASK;
int slope, offset;
int offset = priv->temp_params->offset;
int mult = priv->temp_params->mult;
avs_tmon_get_coeffs(tz, &slope, &offset);
return slope * val + offset;
return (offset - (int)((code & AVS_TMON_TEMP_MASK) * mult));
}
/*
@ -133,23 +132,22 @@ static inline int avs_tmon_code_to_temp(struct thermal_zone_device *tz,
* @temp: temperature to convert
* @low: if true, round toward the low side
*/
static inline u32 avs_tmon_temp_to_code(struct thermal_zone_device *tz,
static inline u32 avs_tmon_temp_to_code(struct brcmstb_thermal_priv *priv,
int temp, bool low)
{
int slope, offset;
int offset = priv->temp_params->offset;
int mult = priv->temp_params->mult;
if (temp < AVS_TMON_TEMP_MIN)
return AVS_TMON_TEMP_MAX; /* Maximum code value */
avs_tmon_get_coeffs(tz, &slope, &offset);
return AVS_TMON_TEMP_MAX; /* Maximum code value */
if (temp >= offset)
return 0; /* Minimum code value */
if (low)
return (u32)(DIV_ROUND_UP(offset - temp, abs(slope)));
return (u32)(DIV_ROUND_UP(offset - temp, mult));
else
return (u32)((offset - temp) / abs(slope));
return (u32)((offset - temp) / mult);
}
static int brcmstb_get_temp(void *data, int *temp)
@ -167,7 +165,7 @@ static int brcmstb_get_temp(void *data, int *temp)
val = (val & AVS_TMON_STATUS_data_msk) >> AVS_TMON_STATUS_data_shift;
t = avs_tmon_code_to_temp(priv->thermal, val);
t = avs_tmon_code_to_temp(priv, val);
if (t < 0)
*temp = 0;
else
@ -201,7 +199,7 @@ static int avs_tmon_get_trip_temp(struct brcmstb_thermal_priv *priv,
val &= trip->reg_msk;
val >>= trip->reg_shift;
return avs_tmon_code_to_temp(priv->thermal, val);
return avs_tmon_code_to_temp(priv, val);
}
static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,
@ -214,7 +212,7 @@ static void avs_tmon_set_trip_temp(struct brcmstb_thermal_priv *priv,
dev_dbg(priv->dev, "set temp %d to %d\n", type, temp);
/* round toward low temp for the low interrupt */
val = avs_tmon_temp_to_code(priv->thermal, temp,
val = avs_tmon_temp_to_code(priv, temp,
type == TMON_TRIP_TYPE_LOW);
val <<= trip->reg_shift;
@ -231,7 +229,7 @@ static int avs_tmon_get_intr_temp(struct brcmstb_thermal_priv *priv)
u32 val;
val = __raw_readl(priv->tmon_base + AVS_TMON_TEMP_INT_CODE);
return avs_tmon_code_to_temp(priv->thermal, val);
return avs_tmon_code_to_temp(priv, val);
}
static irqreturn_t brcmstb_tmon_irq_thread(int irq, void *data)
@ -290,19 +288,37 @@ static int brcmstb_set_trips(void *data, int low, int high)
return 0;
}
static const struct thermal_zone_of_device_ops of_ops = {
static const struct thermal_zone_of_device_ops brcmstb_16nm_of_ops = {
.get_temp = brcmstb_get_temp,
};
static const struct brcmstb_thermal_params brcmstb_16nm_params = {
.offset = 457829,
.mult = 557,
.of_ops = &brcmstb_16nm_of_ops,
};
static const struct thermal_zone_of_device_ops brcmstb_28nm_of_ops = {
.get_temp = brcmstb_get_temp,
.set_trips = brcmstb_set_trips,
};
static const struct brcmstb_thermal_params brcmstb_28nm_params = {
.offset = 410040,
.mult = 487,
.of_ops = &brcmstb_28nm_of_ops,
};
static const struct of_device_id brcmstb_thermal_id_table[] = {
{ .compatible = "brcm,avs-tmon" },
{ .compatible = "brcm,avs-tmon-bcm7216", .data = &brcmstb_16nm_params },
{ .compatible = "brcm,avs-tmon", .data = &brcmstb_28nm_params },
{},
};
MODULE_DEVICE_TABLE(of, brcmstb_thermal_id_table);
static int brcmstb_thermal_probe(struct platform_device *pdev)
{
const struct thermal_zone_of_device_ops *of_ops;
struct thermal_zone_device *thermal;
struct brcmstb_thermal_priv *priv;
struct resource *res;
@ -312,6 +328,10 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
priv->temp_params = of_device_get_match_data(&pdev->dev);
if (!priv->temp_params)
return -EINVAL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->tmon_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->tmon_base))
@ -319,9 +339,10 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
priv->dev = &pdev->dev;
platform_set_drvdata(pdev, priv);
of_ops = priv->temp_params->of_ops;
thermal = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, priv,
&of_ops);
of_ops);
if (IS_ERR(thermal)) {
ret = PTR_ERR(thermal);
dev_err(&pdev->dev, "could not register sensor: %d\n", ret);
@ -331,16 +352,15 @@ static int brcmstb_thermal_probe(struct platform_device *pdev)
priv->thermal = thermal;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "could not get IRQ\n");
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
brcmstb_tmon_irq_thread, IRQF_ONESHOT,
DRV_NAME, priv);
if (ret < 0) {
dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
return ret;
if (irq >= 0) {
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
brcmstb_tmon_irq_thread,
IRQF_ONESHOT,
DRV_NAME, priv);
if (ret < 0) {
dev_err(&pdev->dev, "could not request IRQ: %d\n", ret);
return ret;
}
}
dev_info(&pdev->dev, "registered AVS TMON of-sensor driver\n");

View File

@ -7,7 +7,7 @@
* Copyright (C) 2013 Texas Instruments Inc.
* Contact: Eduardo Valentin <eduardo.valentin@ti.com>
*
* Highly based on cpu_cooling.c.
* Highly based on cpufreq_cooling.c.
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
* Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
*/

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
* linux/drivers/thermal/cpu_cooling.c
* linux/drivers/thermal/cpufreq_cooling.c
*
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
*
@ -63,6 +63,7 @@ struct time_in_idle {
* @policy: cpufreq policy.
* @node: list_head to link all cpufreq_cooling_device together.
* @idle_time: idle time stats
* @qos_req: PM QoS contraint to apply
*
* This structure is required for keeping information of each registered
* cpufreq_cooling_device.
@ -620,7 +621,7 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
struct thermal_cooling_device *cdev = NULL;
if (!np) {
pr_err("cpu_cooling: OF node not available for cpu%d\n",
pr_err("cpufreq_cooling: OF node not available for cpu%d\n",
policy->cpu);
return NULL;
}
@ -630,7 +631,7 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
cdev = __cpufreq_cooling_register(np, policy, em);
if (IS_ERR(cdev)) {
pr_err("cpu_cooling: cpu%d failed to register as cooling device: %ld\n",
pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %ld\n",
policy->cpu, PTR_ERR(cdev));
cdev = NULL;
}

View File

@ -0,0 +1,232 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Linaro Limited.
*
* Author: Daniel Lezcano <daniel.lezcano@linaro.org>
*
*/
#include <linux/cpu_cooling.h>
#include <linux/cpuidle.h>
#include <linux/err.h>
#include <linux/idle_inject.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/thermal.h>
/**
* struct cpuidle_cooling_device - data for the idle cooling device
* @ii_dev: an atomic to keep track of the last task exiting the idle cycle
* @state: a normalized integer giving the state of the cooling device
*/
struct cpuidle_cooling_device {
struct idle_inject_device *ii_dev;
unsigned long state;
};
static DEFINE_IDA(cpuidle_ida);
/**
* cpuidle_cooling_runtime - Running time computation
* @idle_duration_us: the idle cooling device
* @state: a percentile based number
*
* The running duration is computed from the idle injection duration
* which is fixed. If we reach 100% of idle injection ratio, that
* means the running duration is zero. If we have a 50% ratio
* injection, that means we have equal duration for idle and for
* running duration.
*
* The formula is deduced as follows:
*
* running = idle x ((100 / ratio) - 1)
*
* For precision purpose for integer math, we use the following:
*
* running = (idle x 100) / ratio - idle
*
* For example, if we have an injected duration of 50%, then we end up
* with 10ms of idle injection and 10ms of running duration.
*
* Return: An unsigned int for a usec based runtime duration.
*/
static unsigned int cpuidle_cooling_runtime(unsigned int idle_duration_us,
unsigned long state)
{
if (!state)
return 0;
return ((idle_duration_us * 100) / state) - idle_duration_us;
}
/**
* cpuidle_cooling_get_max_state - Get the maximum state
* @cdev : the thermal cooling device
* @state : a pointer to the state variable to be filled
*
* The function always returns 100 as the injection ratio. It is
* percentile based for consistency accross different platforms.
*
* Return: The function can not fail, it is always zero
*/
static int cpuidle_cooling_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
/*
* Depending on the configuration or the hardware, the running
* cycle and the idle cycle could be different. We want to
* unify that to an 0..100 interval, so the set state
* interface will be the same whatever the platform is.
*
* The state 100% will make the cluster 100% ... idle. A 0%
* injection ratio means no idle injection at all and 50%
* means for 10ms of idle injection, we have 10ms of running
* time.
*/
*state = 100;
return 0;
}
/**
* cpuidle_cooling_get_cur_state - Get the current cooling state
* @cdev: the thermal cooling device
* @state: a pointer to the state
*
* The function just copies the state value from the private thermal
* cooling device structure, the mapping is 1 <-> 1.
*
* Return: The function can not fail, it is always zero
*/
static int cpuidle_cooling_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct cpuidle_cooling_device *idle_cdev = cdev->devdata;
*state = idle_cdev->state;
return 0;
}
/**
* cpuidle_cooling_set_cur_state - Set the current cooling state
* @cdev: the thermal cooling device
* @state: the target state
*
* The function checks first if we are initiating the mitigation which
* in turn wakes up all the idle injection tasks belonging to the idle
* cooling device. In any case, it updates the internal state for the
* cooling device.
*
* Return: The function can not fail, it is always zero
*/
static int cpuidle_cooling_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct cpuidle_cooling_device *idle_cdev = cdev->devdata;
struct idle_inject_device *ii_dev = idle_cdev->ii_dev;
unsigned long current_state = idle_cdev->state;
unsigned int runtime_us, idle_duration_us;
idle_cdev->state = state;
idle_inject_get_duration(ii_dev, &runtime_us, &idle_duration_us);
runtime_us = cpuidle_cooling_runtime(idle_duration_us, state);
idle_inject_set_duration(ii_dev, runtime_us, idle_duration_us);
if (current_state == 0 && state > 0) {
idle_inject_start(ii_dev);
} else if (current_state > 0 && !state) {
idle_inject_stop(ii_dev);
}
return 0;
}
/**
* cpuidle_cooling_ops - thermal cooling device ops
*/
static struct thermal_cooling_device_ops cpuidle_cooling_ops = {
.get_max_state = cpuidle_cooling_get_max_state,
.get_cur_state = cpuidle_cooling_get_cur_state,
.set_cur_state = cpuidle_cooling_set_cur_state,
};
/**
* cpuidle_of_cooling_register - Idle cooling device initialization function
* @drv: a cpuidle driver structure pointer
* @np: a node pointer to a device tree cooling device node
*
* This function is in charge of creating a cooling device per cpuidle
* driver and register it to thermal framework.
*
* Return: zero on success, or negative value corresponding to the
* error detected in the underlying subsystems.
*/
int cpuidle_of_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;
char dev_name[THERMAL_NAME_LENGTH];
int id, ret;
idle_cdev = kzalloc(sizeof(*idle_cdev), GFP_KERNEL);
if (!idle_cdev) {
ret = -ENOMEM;
goto out;
}
id = ida_simple_get(&cpuidle_ida, 0, 0, GFP_KERNEL);
if (id < 0) {
ret = id;
goto out_kfree;
}
ii_dev = idle_inject_register(drv->cpumask);
if (!ii_dev) {
ret = -EINVAL;
goto out_id;
}
idle_inject_set_duration(ii_dev, TICK_USEC, TICK_USEC);
idle_cdev->ii_dev = ii_dev;
snprintf(dev_name, sizeof(dev_name), "thermal-idle-%d", id);
cdev = thermal_of_cooling_device_register(np, dev_name, idle_cdev,
&cpuidle_cooling_ops);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
goto out_unregister;
}
return 0;
out_unregister:
idle_inject_unregister(ii_dev);
out_id:
ida_simple_remove(&cpuidle_ida, id);
out_kfree:
kfree(idle_cdev);
out:
return ret;
}
/**
* cpuidle_cooling_register - Idle cooling device initialization function
* @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.
*
* Return: zero on success, or negative value corresponding to the
* error detected in the underlying subsystems.
*/
int cpuidle_cooling_register(struct cpuidle_driver *drv)
{
return cpuidle_of_cooling_register(NULL, drv);
}

View File

@ -152,8 +152,8 @@ static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data)
db8500_thermal_update_config(th, idx, THERMAL_TREND_RAISING,
next_low, next_high);
dev_info(&th->tz->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
dev_dbg(&th->tz->device,
"PRCMU set max %ld, min %ld\n", next_high, next_low);
} else if (idx == num_points - 1)
/* So we roof out 1 degree over the max point */
th->interpolated_temp = db8500_thermal_points[idx] + 1;

View File

@ -53,6 +53,7 @@ static DEFINE_IDA(devfreq_ida);
* 'utilization' (which is 'busy_time / 'total_time').
* 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
*/
struct devfreq_cooling_device {
int id;
@ -587,7 +588,7 @@ EXPORT_SYMBOL_GPL(devfreq_cooling_register);
/**
* devfreq_cooling_unregister() - Unregister devfreq cooling device.
* @dfc: Pointer to devfreq cooling device to unregister.
* @cdev: Pointer to devfreq cooling device to unregister.
*/
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{

View File

@ -58,8 +58,8 @@ static long get_target_state(struct thermal_zone_device *tz,
/**
* fair_share_throttle - throttles devices associated with the given zone
* @tz - thermal_zone_device
* @trip - trip point index
* @tz: thermal_zone_device
* @trip: trip point index
*
* Throttling Logic: This uses three parameters to calculate the new
* throttle state of the cooling devices associated with the given zone.

View File

@ -71,8 +71,8 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
/**
* bang_bang_control - controls devices associated with the given zone
* @tz - thermal_zone_device
* @trip - the trip point
* @tz: thermal_zone_device
* @trip: the trip point
*
* Regulation Logic: a two point regulation, deliver cooling state depending
* on the previous state shown in this diagram:

View File

@ -42,6 +42,9 @@
/* IceLake thermal reporting device */
#define PCI_DEVICE_ID_PROC_ICL_THERMAL 0x8a03
/* JasperLake thermal reporting device */
#define PCI_DEVICE_ID_PROC_JSL_THERMAL 0x4503
#define DRV_NAME "proc_thermal"
struct power_config {
@ -724,6 +727,7 @@ static const struct pci_device_id proc_thermal_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_ICL_THERMAL),
.driver_data = (kernel_ulong_t)&rapl_mmio_hsw, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_JSL_THERMAL)},
{ 0, },
};

View File

@ -23,6 +23,7 @@
#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */
#define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */
#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */
#define PCH_THERMAL_DID_CML_H 0X06F9 /* CML-H PCH */
/* Wildcat Point-LP PCH Thermal registers */
#define WPT_TEMP 0x0000 /* Temperature */
@ -272,6 +273,7 @@ enum board_ids {
board_wpt,
board_skl,
board_cnl,
board_cml,
};
static const struct board_info {
@ -294,6 +296,10 @@ static const struct board_info {
.name = "pch_cannonlake",
.ops = &pch_dev_ops_wpt,
},
[board_cml] = {
.name = "pch_cometlake",
.ops = &pch_dev_ops_wpt,
}
};
static int intel_pch_thermal_probe(struct pci_dev *pdev,
@ -365,7 +371,7 @@ static void intel_pch_thermal_remove(struct pci_dev *pdev)
thermal_zone_device_unregister(ptd->tzd);
iounmap(ptd->hw_base);
pci_set_drvdata(pdev, NULL);
pci_release_region(pdev, 0);
pci_release_regions(pdev);
pci_disable_device(pdev);
}
@ -398,6 +404,8 @@ static const struct pci_device_id intel_pch_thermal_id[] = {
.driver_data = board_cnl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
.driver_data = board_cnl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CML_H),
.driver_data = board_cml, },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);

View File

@ -33,7 +33,7 @@ struct max77620_therm_info {
/**
* max77620_thermal_read_temp: Read PMIC die temperatue.
* @data: Device specific data.
* temp: Temperature in millidegrees Celsius
* @temp: Temperature in millidegrees Celsius
*
* The actual temperature of PMIC die is not available from PMIC.
* PMIC only tells the status if it has crossed or not the threshold level

View File

@ -358,7 +358,7 @@ static const int mt7622_mux_values[MT7622_NUM_SENSORS] = { 0, };
static const int mt7622_vts_index[MT7622_NUM_SENSORS] = { VTS1 };
static const int mt7622_tc_offset[MT7622_NUM_CONTROLLER] = { 0x0, };
/**
/*
* The MT8173 thermal controller has four banks. Each bank can read up to
* four temperature sensors simultaneously. The MT8173 has a total of 5
* temperature sensors. We use each bank to measure a certain area of the
@ -400,7 +400,7 @@ static const struct mtk_thermal_data mt8173_thermal_data = {
.sensor_mux_values = mt8173_mux_values,
};
/**
/*
* The MT2701 thermal controller has one bank, which can read up to
* three temperature sensors simultaneously. The MT2701 has a total of 3
* temperature sensors.
@ -430,7 +430,7 @@ static const struct mtk_thermal_data mt2701_thermal_data = {
.sensor_mux_values = mt2701_mux_values,
};
/**
/*
* The MT2712 thermal controller has one bank, which can read up to
* four temperature sensors simultaneously. The MT2712 has a total of 4
* temperature sensors.
@ -484,7 +484,7 @@ static const struct mtk_thermal_data mt7622_thermal_data = {
.sensor_mux_values = mt7622_mux_values,
};
/**
/*
* The MT8183 thermal controller has one bank for the current SW framework.
* The MT8183 has a total of 6 temperature sensors.
* There are two thermal controller to control the six sensor.
@ -495,7 +495,6 @@ static const struct mtk_thermal_data mt7622_thermal_data = {
* data, and this indeed needs the temperatures of the individual banks
* for making better decisions.
*/
static const struct mtk_thermal_data mt8183_thermal_data = {
.auxadc_channel = MT8183_TEMP_AUXADC_CHANNEL,
.num_banks = MT8183_NUM_SENSORS_PER_ZONE,
@ -519,7 +518,8 @@ static const struct mtk_thermal_data mt8183_thermal_data = {
/**
* raw_to_mcelsius - convert a raw ADC value to mcelsius
* @mt: The thermal controller
* @mt: The thermal controller
* @sensno: sensor number
* @raw: raw ADC value
*
* This converts the raw ADC value to mcelsius using the SoC specific

View File

@ -493,7 +493,7 @@ thermal_zone_of_sensor_register(struct device *dev, int sensor_id, void *data,
if (!dev || !dev->of_node) {
of_node_put(np);
return ERR_PTR(-EINVAL);
return ERR_PTR(-ENODEV);
}
sensor_np = of_node_get(dev->of_node);
@ -754,7 +754,7 @@ end:
return ret;
}
/**
/*
* It maps 'enum thermal_trip_type' found in include/linux/thermal.h
* into the device tree binding of 'trip', property type.
*/
@ -977,7 +977,7 @@ free_tz:
return ERR_PTR(ret);
}
static inline void of_thermal_free_zone(struct __thermal_zone *tz)
static __init void of_thermal_free_zone(struct __thermal_zone *tz)
{
struct __thermal_bind_params *tbp;
int i, j;
@ -998,6 +998,38 @@ static inline void of_thermal_free_zone(struct __thermal_zone *tz)
kfree(tz);
}
/**
* of_thermal_destroy_zones - remove all zones parsed and allocated resources
*
* Finds all zones parsed and added to the thermal framework and remove them
* from the system, together with their resources.
*
*/
static __init void of_thermal_destroy_zones(void)
{
struct device_node *np, *child;
np = of_find_node_by_name(NULL, "thermal-zones");
if (!np) {
pr_debug("unable to find thermal zones\n");
return;
}
for_each_available_child_of_node(np, child) {
struct thermal_zone_device *zone;
zone = thermal_zone_get_zone_by_name(child->name);
if (IS_ERR(zone))
continue;
thermal_zone_device_unregister(zone);
kfree(zone->tzp);
kfree(zone->ops);
of_thermal_free_zone(zone->devdata);
}
of_node_put(np);
}
/**
* of_parse_thermal_zones - parse device tree thermal data
*
@ -1087,35 +1119,3 @@ exit_free:
return -ENOMEM;
}
/**
* of_thermal_destroy_zones - remove all zones parsed and allocated resources
*
* Finds all zones parsed and added to the thermal framework and remove them
* from the system, together with their resources.
*
*/
void of_thermal_destroy_zones(void)
{
struct device_node *np, *child;
np = of_find_node_by_name(NULL, "thermal-zones");
if (!np) {
pr_debug("unable to find thermal zones\n");
return;
}
for_each_available_child_of_node(np, child) {
struct thermal_zone_device *zone;
zone = thermal_zone_get_zone_by_name(child->name);
if (IS_ERR(zone))
continue;
thermal_zone_device_unregister(zone);
kfree(zone->tzp);
kfree(zone->ops);
of_thermal_free_zone(zone->devdata);
}
of_node_put(np);
}

View File

@ -9,9 +9,12 @@
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/regmap.h>
#include <linux/sizes.h>
#include <linux/thermal.h>
#include "thermal_core.h"
#include "thermal_hwmon.h"
#define SITES_MAX 16
#define TMR_DISABLE 0x0
@ -24,129 +27,80 @@
#define TMU_VER1 0x1
#define TMU_VER2 0x2
/*
* QorIQ TMU Registers
*/
struct qoriq_tmu_site_regs {
u32 tritsr; /* Immediate Temperature Site Register */
u32 tratsr; /* Average Temperature Site Register */
u8 res0[0x8];
};
#define REGS_TMR 0x000 /* Mode Register */
#define TMR_DISABLE 0x0
#define TMR_ME 0x80000000
#define TMR_ALPF 0x0c000000
#define TMR_MSITE_ALL GENMASK(15, 0)
struct qoriq_tmu_regs_v1 {
u32 tmr; /* Mode Register */
u32 tsr; /* Status Register */
u32 tmtmir; /* Temperature measurement interval Register */
u8 res0[0x14];
u32 tier; /* Interrupt Enable Register */
u32 tidr; /* Interrupt Detect Register */
u32 tiscr; /* Interrupt Site Capture Register */
u32 ticscr; /* Interrupt Critical Site Capture Register */
u8 res1[0x10];
u32 tmhtcrh; /* High Temperature Capture Register */
u32 tmhtcrl; /* Low Temperature Capture Register */
u8 res2[0x8];
u32 tmhtitr; /* High Temperature Immediate Threshold */
u32 tmhtatr; /* High Temperature Average Threshold */
u32 tmhtactr; /* High Temperature Average Crit Threshold */
u8 res3[0x24];
u32 ttcfgr; /* Temperature Configuration Register */
u32 tscfgr; /* Sensor Configuration Register */
u8 res4[0x78];
struct qoriq_tmu_site_regs site[SITES_MAX];
u8 res5[0x9f8];
u32 ipbrr0; /* IP Block Revision Register 0 */
u32 ipbrr1; /* IP Block Revision Register 1 */
u8 res6[0x310];
u32 ttrcr[4]; /* Temperature Range Control Register */
};
#define REGS_TMTMIR 0x008 /* Temperature measurement interval Register */
#define TMTMIR_DEFAULT 0x0000000f
struct qoriq_tmu_regs_v2 {
u32 tmr; /* Mode Register */
u32 tsr; /* Status Register */
u32 tmsr; /* monitor site register */
u32 tmtmir; /* Temperature measurement interval Register */
u8 res0[0x10];
u32 tier; /* Interrupt Enable Register */
u32 tidr; /* Interrupt Detect Register */
u8 res1[0x8];
u32 tiiscr; /* interrupt immediate site capture register */
u32 tiascr; /* interrupt average site capture register */
u32 ticscr; /* Interrupt Critical Site Capture Register */
u32 res2;
u32 tmhtcr; /* monitor high temperature capture register */
u32 tmltcr; /* monitor low temperature capture register */
u32 tmrtrcr; /* monitor rising temperature rate capture register */
u32 tmftrcr; /* monitor falling temperature rate capture register */
u32 tmhtitr; /* High Temperature Immediate Threshold */
u32 tmhtatr; /* High Temperature Average Threshold */
u32 tmhtactr; /* High Temperature Average Crit Threshold */
u32 res3;
u32 tmltitr; /* monitor low temperature immediate threshold */
u32 tmltatr; /* monitor low temperature average threshold register */
u32 tmltactr; /* monitor low temperature average critical threshold */
u32 res4;
u32 tmrtrctr; /* monitor rising temperature rate critical threshold */
u32 tmftrctr; /* monitor falling temperature rate critical threshold*/
u8 res5[0x8];
u32 ttcfgr; /* Temperature Configuration Register */
u32 tscfgr; /* Sensor Configuration Register */
u8 res6[0x78];
struct qoriq_tmu_site_regs site[SITES_MAX];
u8 res7[0x9f8];
u32 ipbrr0; /* IP Block Revision Register 0 */
u32 ipbrr1; /* IP Block Revision Register 1 */
u8 res8[0x300];
u32 teumr0;
u32 teumr1;
u32 teumr2;
u32 res9;
u32 ttrcr[4]; /* Temperature Range Control Register */
};
#define REGS_V2_TMSR 0x008 /* monitor site register */
struct qoriq_tmu_data;
#define REGS_V2_TMTMIR 0x00c /* Temperature measurement interval Register */
#define REGS_TIER 0x020 /* Interrupt Enable Register */
#define TIER_DISABLE 0x0
#define REGS_TTCFGR 0x080 /* Temperature Configuration Register */
#define REGS_TSCFGR 0x084 /* Sensor Configuration Register */
#define REGS_TRITSR(n) (0x100 + 16 * (n)) /* Immediate Temperature
* Site Register
*/
#define TRITSR_V BIT(31)
#define REGS_TTRnCR(n) (0xf10 + 4 * (n)) /* Temperature Range n
* Control Register
*/
#define REGS_IPBRR(n) (0xbf8 + 4 * (n)) /* IP Block Revision
* Register n
*/
#define REGS_V2_TEUMR(n) (0xf00 + 4 * (n))
/*
* Thermal zone data
*/
struct qoriq_sensor {
struct thermal_zone_device *tzd;
struct qoriq_tmu_data *qdata;
int id;
};
struct qoriq_tmu_data {
int ver;
struct qoriq_tmu_regs_v1 __iomem *regs;
struct qoriq_tmu_regs_v2 __iomem *regs_v2;
struct regmap *regmap;
struct clk *clk;
bool little_endian;
struct qoriq_sensor *sensor[SITES_MAX];
struct qoriq_sensor sensor[SITES_MAX];
};
static void tmu_write(struct qoriq_tmu_data *p, u32 val, void __iomem *addr)
static struct qoriq_tmu_data *qoriq_sensor_to_data(struct qoriq_sensor *s)
{
if (p->little_endian)
iowrite32(val, addr);
else
iowrite32be(val, addr);
}
static u32 tmu_read(struct qoriq_tmu_data *p, void __iomem *addr)
{
if (p->little_endian)
return ioread32(addr);
else
return ioread32be(addr);
return container_of(s, struct qoriq_tmu_data, sensor[s->id]);
}
static int tmu_get_temp(void *p, int *temp)
{
struct qoriq_sensor *qsensor = p;
struct qoriq_tmu_data *qdata = qsensor->qdata;
struct qoriq_tmu_data *qdata = qoriq_sensor_to_data(qsensor);
u32 val;
/*
* REGS_TRITSR(id) has the following layout:
*
* 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.
*/
if (regmap_read_poll_timeout(qdata->regmap,
REGS_TRITSR(qsensor->id),
val,
val & TRITSR_V,
USEC_PER_MSEC,
10 * USEC_PER_MSEC))
return -ENODATA;
val = tmu_read(qdata, &qdata->regs->site[qsensor->id].tritsr);
*temp = (val & 0xff) * 1000;
return 0;
@ -156,84 +110,82 @@ static const struct thermal_zone_of_device_ops tmu_tz_ops = {
.get_temp = tmu_get_temp,
};
static int qoriq_tmu_register_tmu_zone(struct platform_device *pdev)
static int qoriq_tmu_register_tmu_zone(struct device *dev,
struct qoriq_tmu_data *qdata)
{
struct qoriq_tmu_data *qdata = platform_get_drvdata(pdev);
int id, sites = 0;
int id;
for (id = 0; id < SITES_MAX; id++) {
qdata->sensor[id] = devm_kzalloc(&pdev->dev,
sizeof(struct qoriq_sensor), GFP_KERNEL);
if (!qdata->sensor[id])
return -ENOMEM;
qdata->sensor[id]->id = id;
qdata->sensor[id]->qdata = qdata;
qdata->sensor[id]->tzd = devm_thermal_zone_of_sensor_register(
&pdev->dev, id, qdata->sensor[id], &tmu_tz_ops);
if (IS_ERR(qdata->sensor[id]->tzd)) {
if (PTR_ERR(qdata->sensor[id]->tzd) == -ENODEV)
continue;
else
return PTR_ERR(qdata->sensor[id]->tzd);
}
if (qdata->ver == TMU_VER1)
sites |= 0x1 << (15 - id);
else
sites |= 0x1 << id;
if (qdata->ver == TMU_VER1) {
regmap_write(qdata->regmap, REGS_TMR,
TMR_MSITE_ALL | TMR_ME | TMR_ALPF);
} else {
regmap_write(qdata->regmap, REGS_V2_TMSR, TMR_MSITE_ALL);
regmap_write(qdata->regmap, REGS_TMR, TMR_ME | TMR_ALPF_V2);
}
/* Enable monitoring */
if (sites != 0) {
if (qdata->ver == TMU_VER1) {
tmu_write(qdata, sites | TMR_ME | TMR_ALPF,
&qdata->regs->tmr);
} else {
tmu_write(qdata, sites, &qdata->regs_v2->tmsr);
tmu_write(qdata, TMR_ME | TMR_ALPF_V2,
&qdata->regs_v2->tmr);
for (id = 0; id < SITES_MAX; id++) {
struct thermal_zone_device *tzd;
struct qoriq_sensor *sensor = &qdata->sensor[id];
int ret;
sensor->id = id;
tzd = devm_thermal_zone_of_sensor_register(dev, id,
sensor,
&tmu_tz_ops);
ret = PTR_ERR_OR_ZERO(tzd);
if (ret) {
if (ret == -ENODEV)
continue;
regmap_write(qdata->regmap, REGS_TMR, TMR_DISABLE);
return ret;
}
if (devm_thermal_add_hwmon_sysfs(tzd))
dev_warn(dev,
"Failed to add hwmon sysfs attributes\n");
}
return 0;
}
static int qoriq_tmu_calibration(struct platform_device *pdev)
static int qoriq_tmu_calibration(struct device *dev,
struct qoriq_tmu_data *data)
{
int i, val, len;
u32 range[4];
const u32 *calibration;
struct device_node *np = pdev->dev.of_node;
struct qoriq_tmu_data *data = platform_get_drvdata(pdev);
struct device_node *np = dev->of_node;
len = of_property_count_u32_elems(np, "fsl,tmu-range");
if (len < 0 || len > 4) {
dev_err(&pdev->dev, "invalid range data.\n");
dev_err(dev, "invalid range data.\n");
return len;
}
val = of_property_read_u32_array(np, "fsl,tmu-range", range, len);
if (val != 0) {
dev_err(&pdev->dev, "failed to read range data.\n");
dev_err(dev, "failed to read range data.\n");
return val;
}
/* Init temperature range registers */
for (i = 0; i < len; i++)
tmu_write(data, range[i], &data->regs->ttrcr[i]);
regmap_write(data->regmap, REGS_TTRnCR(i), range[i]);
calibration = of_get_property(np, "fsl,tmu-calibration", &len);
if (calibration == NULL || len % 8) {
dev_err(&pdev->dev, "invalid calibration data.\n");
dev_err(dev, "invalid calibration data.\n");
return -ENODEV;
}
for (i = 0; i < len; i += 8, calibration += 2) {
val = of_read_number(calibration, 1);
tmu_write(data, val, &data->regs->ttcfgr);
regmap_write(data->regmap, REGS_TTCFGR, val);
val = of_read_number(calibration + 1, 1);
tmu_write(data, val, &data->regs->tscfgr);
regmap_write(data->regmap, REGS_TSCFGR, val);
}
return 0;
@ -242,76 +194,117 @@ static int qoriq_tmu_calibration(struct platform_device *pdev)
static void qoriq_tmu_init_device(struct qoriq_tmu_data *data)
{
/* Disable interrupt, using polling instead */
tmu_write(data, TIER_DISABLE, &data->regs->tier);
regmap_write(data->regmap, REGS_TIER, TIER_DISABLE);
/* Set update_interval */
if (data->ver == TMU_VER1) {
tmu_write(data, TMTMIR_DEFAULT, &data->regs->tmtmir);
regmap_write(data->regmap, REGS_TMTMIR, TMTMIR_DEFAULT);
} else {
tmu_write(data, TMTMIR_DEFAULT, &data->regs_v2->tmtmir);
tmu_write(data, TEUMR0_V2, &data->regs_v2->teumr0);
regmap_write(data->regmap, REGS_V2_TMTMIR, TMTMIR_DEFAULT);
regmap_write(data->regmap, REGS_V2_TEUMR(0), TEUMR0_V2);
}
/* Disable monitoring */
tmu_write(data, TMR_DISABLE, &data->regs->tmr);
regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
}
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_IPBRR(0), REGS_IPBRR(1)),
/* Read only registers below */
regmap_reg_range(REGS_TRITSR(0), REGS_TRITSR(15)),
};
static const struct regmap_access_table qoriq_wr_table = {
.yes_ranges = qoriq_yes_ranges,
.n_yes_ranges = ARRAY_SIZE(qoriq_yes_ranges) - 1,
};
static const struct regmap_access_table qoriq_rd_table = {
.yes_ranges = qoriq_yes_ranges,
.n_yes_ranges = ARRAY_SIZE(qoriq_yes_ranges),
};
static int qoriq_tmu_probe(struct platform_device *pdev)
{
int ret;
u32 ver;
struct qoriq_tmu_data *data;
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
const bool little_endian = of_property_read_bool(np, "little-endian");
const enum regmap_endian format_endian =
little_endian ? REGMAP_ENDIAN_LITTLE : REGMAP_ENDIAN_BIG;
const struct regmap_config regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.rd_table = &qoriq_rd_table,
.wr_table = &qoriq_wr_table,
.val_format_endian = format_endian,
.max_register = SZ_4K,
};
void __iomem *base;
data = devm_kzalloc(&pdev->dev, sizeof(struct qoriq_tmu_data),
data = devm_kzalloc(dev, sizeof(struct qoriq_tmu_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
data->little_endian = of_property_read_bool(np, "little-endian");
data->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->regs)) {
dev_err(&pdev->dev, "Failed to get memory region\n");
return PTR_ERR(data->regs);
base = devm_platform_ioremap_resource(pdev, 0);
ret = PTR_ERR_OR_ZERO(base);
if (ret) {
dev_err(dev, "Failed to get memory region\n");
return ret;
}
data->clk = devm_clk_get_optional(&pdev->dev, NULL);
data->regmap = devm_regmap_init_mmio(dev, base, &regmap_config);
ret = PTR_ERR_OR_ZERO(data->regmap);
if (ret) {
dev_err(dev, "Failed to init regmap (%d)\n", ret);
return ret;
}
data->clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(data->clk))
return PTR_ERR(data->clk);
ret = clk_prepare_enable(data->clk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable clock\n");
dev_err(dev, "Failed to enable clock\n");
return ret;
}
/* version register offset at: 0xbf8 on both v1 and v2 */
ver = tmu_read(data, &data->regs->ipbrr0);
ret = regmap_read(data->regmap, REGS_IPBRR(0), &ver);
if (ret) {
dev_err(&pdev->dev, "Failed to read IP block version\n");
return ret;
}
data->ver = (ver >> 8) & 0xff;
if (data->ver == TMU_VER2)
data->regs_v2 = (void __iomem *)data->regs;
qoriq_tmu_init_device(data); /* TMU initialization */
ret = qoriq_tmu_calibration(pdev); /* TMU calibration */
ret = qoriq_tmu_calibration(dev, data); /* TMU calibration */
if (ret < 0)
goto err;
ret = qoriq_tmu_register_tmu_zone(pdev);
ret = qoriq_tmu_register_tmu_zone(dev, data);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register sensors\n");
dev_err(dev, "Failed to register sensors\n");
ret = -ENODEV;
goto err;
}
platform_set_drvdata(pdev, data);
return 0;
err:
clk_disable_unprepare(data->clk);
platform_set_drvdata(pdev, NULL);
return ret;
}
@ -321,24 +314,21 @@ static int qoriq_tmu_remove(struct platform_device *pdev)
struct qoriq_tmu_data *data = platform_get_drvdata(pdev);
/* Disable monitoring */
tmu_write(data, TMR_DISABLE, &data->regs->tmr);
regmap_write(data->regmap, REGS_TMR, TMR_DISABLE);
clk_disable_unprepare(data->clk);
platform_set_drvdata(pdev, NULL);
return 0;
}
static int __maybe_unused qoriq_tmu_suspend(struct device *dev)
{
u32 tmr;
struct qoriq_tmu_data *data = dev_get_drvdata(dev);
int ret;
/* Disable monitoring */
tmr = tmu_read(data, &data->regs->tmr);
tmr &= ~TMR_ME;
tmu_write(data, tmr, &data->regs->tmr);
ret = regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, 0);
if (ret)
return ret;
clk_disable_unprepare(data->clk);
@ -347,7 +337,6 @@ static int __maybe_unused qoriq_tmu_suspend(struct device *dev)
static int __maybe_unused qoriq_tmu_resume(struct device *dev)
{
u32 tmr;
int ret;
struct qoriq_tmu_data *data = dev_get_drvdata(dev);
@ -356,11 +345,7 @@ static int __maybe_unused qoriq_tmu_resume(struct device *dev)
return ret;
/* Enable monitoring */
tmr = tmu_read(data, &data->regs->tmr);
tmr |= TMR_ME;
tmu_write(data, tmr, &data->regs->tmr);
return 0;
return regmap_update_bits(data->regmap, REGS_TMR, TMR_ME, TMR_ME);
}
static SIMPLE_DEV_PM_OPS(qoriq_tmu_pm_ops,

View File

@ -182,9 +182,7 @@ static int rcar_gen3_thermal_get_temp(void *devdata, int *temp)
tsc->coef.a2);
mcelsius = FIXPT_TO_MCELSIUS(val);
/* Make sure we are inside specifications */
if ((mcelsius < MCELSIUS(-40)) || (mcelsius > MCELSIUS(125)))
return -EIO;
/* Guaranteed operating range is -40C to 125C. */
/* Round value to device granularity setting */
*temp = rcar_gen3_thermal_round(mcelsius);

View File

@ -219,7 +219,7 @@ static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)
* to get stable temperature.
* see "Usage Notes" on datasheet
*/
udelay(300);
usleep_range(300, 400);
new = rcar_thermal_read(priv, THSSR) & CTEMP;
if (new == old) {
@ -275,12 +275,7 @@ static int rcar_thermal_get_current_temp(struct rcar_thermal_priv *priv,
tmp = MCELSIUS((priv->ctemp * 5) - 60);
mutex_unlock(&priv->lock);
if ((tmp < MCELSIUS(-45)) || (tmp > MCELSIUS(125))) {
struct device *dev = rcar_priv_to_dev(priv);
dev_err(dev, "it couldn't measure temperature correctly\n");
return -EIO;
}
/* Guaranteed operating range is -45C to 125C. */
*temp = tmp;

View File

@ -19,7 +19,7 @@
#include <linux/mfd/syscon.h>
#include <linux/pinctrl/consumer.h>
/**
/*
* If the temperature over a period of time High,
* the resulting TSHUT gave CRU module,let it reset the entire chip,
* or via GPIO give PMIC.
@ -29,7 +29,7 @@ enum tshut_mode {
TSHUT_MODE_GPIO,
};
/**
/*
* The system Temperature Sensors tshut(tshut) polarity
* the bit 8 is tshut polarity.
* 0: low active, 1: high active
@ -39,7 +39,7 @@ enum tshut_polarity {
TSHUT_HIGH_ACTIVE,
};
/**
/*
* The system has two Temperature Sensors.
* sensor0 is for CPU, and sensor1 is for GPU.
*/
@ -48,7 +48,7 @@ enum sensor_id {
SENSOR_GPU,
};
/**
/*
* The conversion table has the adc value and temperature.
* ADC_DECREMENT: the adc value is of diminishing.(e.g. rk3288_code_table)
* ADC_INCREMENT: the adc value is incremental.(e.g. rk3368_code_table)
@ -58,6 +58,8 @@ enum adc_sort_mode {
ADC_INCREMENT,
};
#include "thermal_hwmon.h"
/**
* The max sensors is two in rockchip SoCs.
* Two sensors: CPU and GPU sensor.
@ -80,13 +82,14 @@ struct chip_tsadc_table {
/**
* struct rockchip_tsadc_chip - hold the private data of tsadc chip
* @chn_id[SOC_MAX_SENSORS]: the sensor id of chip correspond to the channel
* @chn_id: array of sensor ids of chip corresponding to the channel
* @chn_num: the channel number of tsadc chip
* @tshut_temp: the hardware-controlled shutdown temperature value
* @tshut_mode: the hardware-controlled shutdown mode (0:CRU 1:GPIO)
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
* @initialize: SoC special initialize tsadc controller method
* @irq_ack: clear the interrupt
* @control: enable/disable method for the tsadc controller
* @get_temp: get the temperature
* @set_alarm_temp: set the high temperature interrupt
* @set_tshut_temp: set the hardware-controlled shutdown temperature
@ -139,7 +142,7 @@ struct rockchip_thermal_sensor {
* @chip: pointer to the platform/configuration data
* @pdev: platform device of thermal
* @reset: the reset controller of tsadc
* @sensors[SOC_MAX_SENSORS]: the thermal sensor
* @sensors: array of thermal sensors
* @clk: the controller clock is divided by the exteral 24MHz
* @pclk: the advanced peripherals bus clock
* @grf: the general register file will be used to do static set by software
@ -590,6 +593,9 @@ static int rk_tsadcv2_code_to_temp(const struct chip_tsadc_table *table,
/**
* rk_tsadcv2_initialize - initialize TASDC Controller.
* @grf: the general register file will be used to do static set by software
* @regs: the base address of tsadc controller
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
*
* (1) Set TSADC_V2_AUTO_PERIOD:
* Configure the interleave between every two accessing of
@ -624,6 +630,9 @@ static void rk_tsadcv2_initialize(struct regmap *grf, void __iomem *regs,
/**
* rk_tsadcv3_initialize - initialize TASDC Controller.
* @grf: the general register file will be used to do static set by software
* @regs: the base address of tsadc controller
* @tshut_polarity: the hardware-controlled active polarity (0:LOW 1:HIGH)
*
* (1) The tsadc control power sequence.
*
@ -723,6 +732,8 @@ static void rk_tsadcv2_control(void __iomem *regs, bool enable)
/**
* rk_tsadcv3_control - the tsadc controller is enabled or disabled.
* @regs: the base address of tsadc controller
* @enable: boolean flag to enable the controller
*
* NOTE: TSADC controller works at auto mode, and some SoCs need set the
* tsadc_q_sel bit on TSADCV2_AUTO_CON[1]. The (1024 - tsadc_q) as output
@ -1206,6 +1217,7 @@ rockchip_thermal_register_sensor(struct platform_device *pdev,
/**
* Reset TSADC Controller, reset all tsadc registers.
* @reset: the reset controller of tsadc
*/
static void rockchip_thermal_reset_controller(struct reset_control *reset)
{
@ -1321,8 +1333,15 @@ static int rockchip_thermal_probe(struct platform_device *pdev)
thermal->chip->control(thermal->regs, true);
for (i = 0; i < thermal->chip->chn_num; i++)
for (i = 0; i < thermal->chip->chn_num; i++) {
rockchip_thermal_toggle_sensor(&thermal->sensors[i], true);
thermal->sensors[i].tzd->tzp->no_hwmon = false;
error = thermal_add_hwmon_sysfs(thermal->sensors[i].tzd);
if (error)
dev_warn(&pdev->dev,
"failed to register sensor %d with hwmon: %d\n",
i, error);
}
platform_set_drvdata(pdev, thermal);
@ -1344,6 +1363,7 @@ static int rockchip_thermal_remove(struct platform_device *pdev)
for (i = 0; i < thermal->chip->chn_num; i++) {
struct rockchip_thermal_sensor *sensor = &thermal->sensors[i];
thermal_remove_hwmon_sysfs(sensor->tzd);
rockchip_thermal_toggle_sensor(sensor, false);
}

View File

@ -5,7 +5,7 @@ config EXYNOS_THERMAL
depends on HAS_IOMEM
help
If you say yes here you get support for the TMU (Thermal Management
Unit) driver for SAMSUNG EXYNOS series of SoCs. This driver initialises
Unit) driver for Samsung Exynos series of SoCs. This driver initialises
the TMU, reports temperature and handles cooling action if defined.
This driver uses the Exynos core thermal APIs and TMU configuration
data from the supported SoCs.

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit)
* exynos_tmu.c - Samsung Exynos TMU (Thermal Management Unit)
*
* Copyright (C) 2014 Samsung Electronics
* Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
@ -138,7 +138,7 @@ enum soc_type {
/**
* struct exynos_tmu_data : A structure to hold the private data of the TMU
driver
* driver
* @id: identifier of the one instance of the TMU controller.
* @base: base address of the single instance of the TMU controller.
* @base_second: base address of the common registers of the TMU controller.
@ -162,8 +162,11 @@ enum soc_type {
* 0 < reference_voltage <= 31
* @regulator: pointer to the TMU regulator structure.
* @reg_conf: pointer to structure to register with core thermal.
* @tzd: pointer to thermal_zone_device structure
* @ntrip: number of supported trip points.
* @enabled: current status of TMU device
* @tmu_set_trip_temp: SoC specific method to set trip (rising threshold)
* @tmu_set_trip_hyst: SoC specific to set hysteresis (falling threshold)
* @tmu_initialize: SoC specific TMU initialization method
* @tmu_control: SoC specific TMU control method
* @tmu_read: SoC specific TMU temperature read method
@ -1183,7 +1186,7 @@ static struct platform_driver exynos_tmu_driver = {
module_platform_driver(exynos_tmu_driver);
MODULE_DESCRIPTION("EXYNOS TMU Driver");
MODULE_DESCRIPTION("Exynos TMU Driver");
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:exynos-tmu");

View File

@ -30,7 +30,7 @@
#define DTS_DR_OFFSET 0x1C
#define DTS_SR_OFFSET 0x20
#define DTS_ITENR_OFFSET 0x24
#define DTS_CIFR_OFFSET 0x28
#define DTS_ICIFR_OFFSET 0x28
/* DTS_CFGR1 register mask definitions */
#define HSREF_CLK_DIV_MASK GENMASK(30, 24)
@ -51,10 +51,16 @@
/* DTS_DR register mask definitions */
#define TS1_MFREQ_MASK GENMASK(15, 0)
/* DTS_ITENR register mask definitions */
#define ITENR_MASK (GENMASK(2, 0) | GENMASK(6, 4))
/* DTS_ICIFR register mask definitions */
#define ICIFR_MASK (GENMASK(2, 0) | GENMASK(6, 4))
/* Less significant bit position definitions */
#define TS1_T0_POS 16
#define TS1_SMP_TIME_POS 16
#define TS1_HITTHD_POS 16
#define TS1_LITTHD_POS 0
#define HSREF_CLK_DIV_POS 24
/* DTS_CFGR1 bit definitions */
@ -76,59 +82,60 @@
#define ONE_MHZ 1000000
#define POLL_TIMEOUT 5000
#define STARTUP_TIME 40
#define TS1_T0_VAL0 30
#define TS1_T0_VAL1 130
#define TS1_T0_VAL0 30000 /* 30 celsius */
#define TS1_T0_VAL1 130000 /* 130 celsius */
#define NO_HW_TRIG 0
/* The Thermal Framework expects millidegrees */
#define mcelsius(temp) ((temp) * 1000)
/* The Sensor expects oC degrees */
#define celsius(temp) ((temp) / 1000)
#define SAMPLING_TIME 15
struct stm_thermal_sensor {
struct device *dev;
struct thermal_zone_device *th_dev;
enum thermal_device_mode mode;
struct clk *clk;
int high_temp;
int low_temp;
int temp_critical;
int temp_passive;
unsigned int low_temp_enabled;
int num_trips;
unsigned int high_temp_enabled;
int irq;
unsigned int irq_enabled;
void __iomem *base;
int t0, fmt0, ramp_coeff;
};
static irqreturn_t stm_thermal_alarm_irq(int irq, void *sdata)
{
struct stm_thermal_sensor *sensor = sdata;
disable_irq_nosync(irq);
sensor->irq_enabled = false;
return IRQ_WAKE_THREAD;
}
static irqreturn_t stm_thermal_alarm_irq_thread(int irq, void *sdata)
static int stm_enable_irq(struct stm_thermal_sensor *sensor)
{
u32 value;
dev_dbg(sensor->dev, "low:%d high:%d\n", sensor->low_temp_enabled,
sensor->high_temp_enabled);
/* Disable IT generation for low and high thresholds */
value = readl_relaxed(sensor->base + DTS_ITENR_OFFSET);
value &= ~(LOW_THRESHOLD | HIGH_THRESHOLD);
if (sensor->low_temp_enabled)
value |= HIGH_THRESHOLD;
if (sensor->high_temp_enabled)
value |= LOW_THRESHOLD;
/* Enable interrupts */
writel_relaxed(value, sensor->base + DTS_ITENR_OFFSET);
return 0;
}
static irqreturn_t stm_thermal_irq_handler(int irq, void *sdata)
{
struct stm_thermal_sensor *sensor = sdata;
/* read IT reason in SR and clear flags */
value = readl_relaxed(sensor->base + DTS_SR_OFFSET);
if ((value & LOW_THRESHOLD) == LOW_THRESHOLD)
writel_relaxed(LOW_THRESHOLD, sensor->base + DTS_CIFR_OFFSET);
if ((value & HIGH_THRESHOLD) == HIGH_THRESHOLD)
writel_relaxed(HIGH_THRESHOLD, sensor->base + DTS_CIFR_OFFSET);
dev_dbg(sensor->dev, "sr:%d\n",
readl_relaxed(sensor->base + DTS_SR_OFFSET));
thermal_zone_device_update(sensor->th_dev, THERMAL_EVENT_UNSPECIFIED);
stm_enable_irq(sensor);
/* Acknoledge all DTS irqs */
writel_relaxed(ICIFR_MASK, sensor->base + DTS_ICIFR_OFFSET);
return IRQ_HANDLED;
}
@ -160,6 +167,8 @@ static int stm_sensor_power_on(struct stm_thermal_sensor *sensor)
writel_relaxed(value, sensor->base +
DTS_CFGR1_OFFSET);
sensor->mode = THERMAL_DEVICE_ENABLED;
return 0;
}
@ -167,6 +176,8 @@ static int stm_sensor_power_off(struct stm_thermal_sensor *sensor)
{
u32 value;
sensor->mode = THERMAL_DEVICE_DISABLED;
/* Stop measuring */
value = readl_relaxed(sensor->base + DTS_CFGR1_OFFSET);
value &= ~TS1_START;
@ -263,60 +274,17 @@ static int stm_thermal_calculate_threshold(struct stm_thermal_sensor *sensor,
int temp, u32 *th)
{
int freqM;
u32 sampling_time;
/* Retrieve the number of periods to sample */
sampling_time = (readl_relaxed(sensor->base + DTS_CFGR1_OFFSET) &
TS1_SMP_TIME_MASK) >> TS1_SMP_TIME_POS;
/* Figure out the CLK_PTAT frequency for a given temperature */
freqM = ((temp - sensor->t0) * sensor->ramp_coeff)
+ sensor->fmt0;
dev_dbg(sensor->dev, "%s: freqM for threshold = %d Hz",
__func__, freqM);
freqM = ((temp - sensor->t0) * sensor->ramp_coeff) / 1000 +
sensor->fmt0;
/* Figure out the threshold sample number */
*th = clk_get_rate(sensor->clk);
*th = clk_get_rate(sensor->clk) * SAMPLING_TIME / freqM;
if (!*th)
return -EINVAL;
*th = *th / freqM;
*th *= sampling_time;
return 0;
}
static int stm_thermal_set_threshold(struct stm_thermal_sensor *sensor)
{
u32 value, th;
int ret;
value = readl_relaxed(sensor->base + DTS_ITR1_OFFSET);
/* Erase threshold content */
value &= ~(TS1_LITTHD_MASK | TS1_HITTHD_MASK);
/* Retrieve the sample threshold number th for a given temperature */
ret = stm_thermal_calculate_threshold(sensor, sensor->high_temp, &th);
if (ret)
return ret;
value |= th & TS1_LITTHD_MASK;
if (sensor->low_temp_enabled) {
/* Retrieve the sample threshold */
ret = stm_thermal_calculate_threshold(sensor, sensor->low_temp,
&th);
if (ret)
return ret;
value |= (TS1_HITTHD_MASK & (th << TS1_HITTHD_POS));
}
/* Write value on the Low interrupt threshold */
writel_relaxed(value, sensor->base + DTS_ITR1_OFFSET);
dev_dbg(sensor->dev, "freqM=%d Hz, threshold=0x%x", freqM, *th);
return 0;
}
@ -326,77 +294,57 @@ static int stm_disable_irq(struct stm_thermal_sensor *sensor)
{
u32 value;
/* Disable IT generation for low and high thresholds */
/* Disable IT generation */
value = readl_relaxed(sensor->base + DTS_ITENR_OFFSET);
writel_relaxed(value & ~(LOW_THRESHOLD | HIGH_THRESHOLD),
sensor->base + DTS_ITENR_OFFSET);
dev_dbg(sensor->dev, "%s: IT disabled on sensor side", __func__);
return 0;
}
/* Enable temperature interrupt */
static int stm_enable_irq(struct stm_thermal_sensor *sensor)
{
u32 value;
/*
* Code below enables High temperature threshold using a low threshold
* sampling value
*/
/* Make sure LOW_THRESHOLD IT is clear before enabling */
writel_relaxed(LOW_THRESHOLD, sensor->base + DTS_CIFR_OFFSET);
/* Enable IT generation for low threshold */
value = readl_relaxed(sensor->base + DTS_ITENR_OFFSET);
value |= LOW_THRESHOLD;
/* Enable the low temperature threshold if needed */
if (sensor->low_temp_enabled) {
/* Make sure HIGH_THRESHOLD IT is clear before enabling */
writel_relaxed(HIGH_THRESHOLD, sensor->base + DTS_CIFR_OFFSET);
/* Enable IT generation for high threshold */
value |= HIGH_THRESHOLD;
}
/* Enable thresholds */
value &= ~ITENR_MASK;
writel_relaxed(value, sensor->base + DTS_ITENR_OFFSET);
dev_dbg(sensor->dev, "%s: IT enabled on sensor side", __func__);
return 0;
}
static int stm_thermal_update_threshold(struct stm_thermal_sensor *sensor)
static int stm_thermal_set_trips(void *data, int low, int high)
{
struct stm_thermal_sensor *sensor = data;
u32 itr1, th;
int ret;
sensor->mode = THERMAL_DEVICE_DISABLED;
dev_dbg(sensor->dev, "set trips %d <--> %d\n", low, high);
ret = stm_sensor_power_off(sensor);
if (ret)
return ret;
/* Erase threshold content */
itr1 = readl_relaxed(sensor->base + DTS_ITR1_OFFSET);
itr1 &= ~(TS1_LITTHD_MASK | TS1_HITTHD_MASK);
ret = stm_disable_irq(sensor);
if (ret)
return ret;
/*
* Disable low-temp if "low" is too small. As per thermal framework
* API, we use -INT_MAX rather than INT_MIN.
*/
ret = stm_thermal_set_threshold(sensor);
if (ret)
return ret;
if (low > -INT_MAX) {
sensor->low_temp_enabled = 1;
/* add 0.5 of hysteresis due to measurement error */
ret = stm_thermal_calculate_threshold(sensor, low - 500, &th);
if (ret)
return ret;
ret = stm_enable_irq(sensor);
if (ret)
return ret;
itr1 |= (TS1_HITTHD_MASK & (th << TS1_HITTHD_POS));
} else {
sensor->low_temp_enabled = 0;
}
ret = stm_sensor_power_on(sensor);
if (ret)
return ret;
/* Disable high-temp if "high" is too big. */
if (high < INT_MAX) {
sensor->high_temp_enabled = 1;
ret = stm_thermal_calculate_threshold(sensor, high, &th);
if (ret)
return ret;
sensor->mode = THERMAL_DEVICE_ENABLED;
itr1 |= (TS1_LITTHD_MASK & (th << TS1_LITTHD_POS));
} else {
sensor->high_temp_enabled = 0;
}
/* Write new threshod values*/
writel_relaxed(itr1, sensor->base + DTS_ITR1_OFFSET);
return 0;
}
@ -405,76 +353,26 @@ static int stm_thermal_update_threshold(struct stm_thermal_sensor *sensor)
static int stm_thermal_get_temp(void *data, int *temp)
{
struct stm_thermal_sensor *sensor = data;
u32 sampling_time;
u32 periods;
int freqM, ret;
if (sensor->mode != THERMAL_DEVICE_ENABLED)
return -EAGAIN;
/* Retrieve the number of samples */
ret = readl_poll_timeout(sensor->base + DTS_DR_OFFSET, freqM,
(freqM & TS1_MFREQ_MASK), STARTUP_TIME,
POLL_TIMEOUT);
/* Retrieve the number of periods sampled */
ret = readl_relaxed_poll_timeout(sensor->base + DTS_DR_OFFSET, periods,
(periods & TS1_MFREQ_MASK),
STARTUP_TIME, POLL_TIMEOUT);
if (ret)
return ret;
if (!freqM)
return -ENODATA;
/* Retrieve the number of periods sampled */
sampling_time = (readl_relaxed(sensor->base + DTS_CFGR1_OFFSET) &
TS1_SMP_TIME_MASK) >> TS1_SMP_TIME_POS;
/* Figure out the number of samples per period */
freqM /= sampling_time;
/* Figure out the CLK_PTAT frequency */
freqM = clk_get_rate(sensor->clk) / freqM;
freqM = (clk_get_rate(sensor->clk) * SAMPLING_TIME) / periods;
if (!freqM)
return -EINVAL;
dev_dbg(sensor->dev, "%s: freqM=%d\n", __func__, freqM);
/* Figure out the temperature in mili celsius */
*temp = mcelsius(sensor->t0 + ((freqM - sensor->fmt0) /
sensor->ramp_coeff));
dev_dbg(sensor->dev, "%s: temperature = %d millicelsius",
__func__, *temp);
/* Update thresholds */
if (sensor->num_trips > 1) {
/* Update alarm threshold value to next higher trip point */
if (sensor->high_temp == sensor->temp_passive &&
celsius(*temp) >= sensor->temp_passive) {
sensor->high_temp = sensor->temp_critical;
sensor->low_temp = sensor->temp_passive;
sensor->low_temp_enabled = true;
ret = stm_thermal_update_threshold(sensor);
if (ret)
return ret;
}
if (sensor->high_temp == sensor->temp_critical &&
celsius(*temp) < sensor->temp_passive) {
sensor->high_temp = sensor->temp_passive;
sensor->low_temp_enabled = false;
ret = stm_thermal_update_threshold(sensor);
if (ret)
return ret;
}
/*
* Re-enable alarm IRQ if temperature below critical
* temperature
*/
if (!sensor->irq_enabled &&
(celsius(*temp) < sensor->temp_critical)) {
sensor->irq_enabled = true;
enable_irq(sensor->irq);
}
}
*temp = (freqM - sensor->fmt0) * 1000 / sensor->ramp_coeff + sensor->t0;
return 0;
}
@ -493,8 +391,8 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor)
}
ret = devm_request_threaded_irq(dev, sensor->irq,
stm_thermal_alarm_irq,
stm_thermal_alarm_irq_thread,
NULL,
stm_thermal_irq_handler,
IRQF_ONESHOT,
dev->driver->name, sensor);
if (ret) {
@ -503,8 +401,6 @@ static int stm_register_irq(struct stm_thermal_sensor *sensor)
return ret;
}
sensor->irq_enabled = true;
dev_dbg(dev, "%s: thermal IRQ registered", __func__);
return 0;
@ -514,6 +410,8 @@ static int stm_thermal_sensor_off(struct stm_thermal_sensor *sensor)
{
int ret;
stm_disable_irq(sensor);
ret = stm_sensor_power_off(sensor);
if (ret)
return ret;
@ -526,7 +424,6 @@ static int stm_thermal_sensor_off(struct stm_thermal_sensor *sensor)
static int stm_thermal_prepare(struct stm_thermal_sensor *sensor)
{
int ret;
struct device *dev = sensor->dev;
ret = clk_prepare_enable(sensor->clk);
if (ret)
@ -540,26 +437,8 @@ static int stm_thermal_prepare(struct stm_thermal_sensor *sensor)
if (ret)
goto thermal_unprepare;
/* Set threshold(s) for IRQ */
ret = stm_thermal_set_threshold(sensor);
if (ret)
goto thermal_unprepare;
ret = stm_enable_irq(sensor);
if (ret)
goto thermal_unprepare;
ret = stm_sensor_power_on(sensor);
if (ret) {
dev_err(dev, "%s: failed to power on sensor\n", __func__);
goto irq_disable;
}
return 0;
irq_disable:
stm_disable_irq(sensor);
thermal_unprepare:
clk_disable_unprepare(sensor->clk);
@ -576,8 +455,6 @@ static int stm_thermal_suspend(struct device *dev)
if (ret)
return ret;
sensor->mode = THERMAL_DEVICE_DISABLED;
return 0;
}
@ -590,7 +467,12 @@ static int stm_thermal_resume(struct device *dev)
if (ret)
return ret;
sensor->mode = THERMAL_DEVICE_ENABLED;
ret = stm_sensor_power_on(sensor);
if (ret)
return ret;
thermal_zone_device_update(sensor->th_dev, THERMAL_EVENT_UNSPECIFIED);
stm_enable_irq(sensor);
return 0;
}
@ -600,6 +482,7 @@ SIMPLE_DEV_PM_OPS(stm_thermal_pm_ops, stm_thermal_suspend, stm_thermal_resume);
static const struct thermal_zone_of_device_ops stm_tz_ops = {
.get_temp = stm_thermal_get_temp,
.set_trips = stm_thermal_set_trips,
};
static const struct of_device_id stm_thermal_of_match[] = {
@ -612,9 +495,8 @@ static int stm_thermal_probe(struct platform_device *pdev)
{
struct stm_thermal_sensor *sensor;
struct resource *res;
const struct thermal_trip *trip;
void __iomem *base;
int ret, i;
int ret;
if (!pdev->dev.of_node) {
dev_err(&pdev->dev, "%s: device tree node not found\n",
@ -645,10 +527,23 @@ static int stm_thermal_probe(struct platform_device *pdev)
return PTR_ERR(sensor->clk);
}
/* Register IRQ into GIC */
ret = stm_register_irq(sensor);
if (ret)
stm_disable_irq(sensor);
/* Clear irq flags */
writel_relaxed(ICIFR_MASK, sensor->base + DTS_ICIFR_OFFSET);
/* Configure and enable HW sensor */
ret = stm_thermal_prepare(sensor);
if (ret) {
dev_err(&pdev->dev, "Error preprare sensor: %d\n", ret);
return ret;
}
ret = stm_sensor_power_on(sensor);
if (ret) {
dev_err(&pdev->dev, "Error power on sensor: %d\n", ret);
return ret;
}
sensor->th_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0,
sensor,
@ -661,53 +556,12 @@ static int stm_thermal_probe(struct platform_device *pdev)
return ret;
}
if (!sensor->th_dev->ops->get_crit_temp) {
/* Critical point must be provided */
ret = -EINVAL;
/* Register IRQ into GIC */
ret = stm_register_irq(sensor);
if (ret)
goto err_tz;
}
ret = sensor->th_dev->ops->get_crit_temp(sensor->th_dev,
&sensor->temp_critical);
if (ret) {
dev_err(&pdev->dev,
"Not able to read critical_temp: %d\n", ret);
goto err_tz;
}
sensor->temp_critical = celsius(sensor->temp_critical);
/* Set thresholds for IRQ */
sensor->high_temp = sensor->temp_critical;
trip = of_thermal_get_trip_points(sensor->th_dev);
sensor->num_trips = of_thermal_get_ntrips(sensor->th_dev);
/* Find out passive temperature if it exists */
for (i = (sensor->num_trips - 1); i >= 0; i--) {
if (trip[i].type == THERMAL_TRIP_PASSIVE) {
sensor->temp_passive = celsius(trip[i].temperature);
/* Update high temperature threshold */
sensor->high_temp = sensor->temp_passive;
}
}
/*
* Ensure low_temp_enabled flag is disabled.
* By disabling low_temp_enabled, low threshold IT will not be
* configured neither enabled because it is not needed as high
* threshold is set on the lowest temperature trip point after
* probe.
*/
sensor->low_temp_enabled = false;
/* Configure and enable HW sensor */
ret = stm_thermal_prepare(sensor);
if (ret) {
dev_err(&pdev->dev,
"Not able to enable sensor: %d\n", ret);
goto err_tz;
}
stm_enable_irq(sensor);
/*
* Thermal_zone doesn't enable hwmon as default,
@ -718,8 +572,6 @@ static int stm_thermal_probe(struct platform_device *pdev)
if (ret)
goto err_tz;
sensor->mode = THERMAL_DEVICE_ENABLED;
dev_info(&pdev->dev, "%s: Driver initialized successfully\n",
__func__);

View File

@ -174,8 +174,8 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip)
/**
* step_wise_throttle - throttles devices associated with the given zone
* @tz - thermal_zone_device
* @trip - trip point index
* @tz: thermal_zone_device
* @trip: trip point index
*
* Throttling Logic: This uses the trend of the thermal zone to throttle.
* If the thermal zone is 'heating up' this throttles all the cooling

View File

@ -0,0 +1,639 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Thermal sensor driver for Allwinner SOC
* Copyright (C) 2019 Yangtao Li
*
* Based on the work of Icenowy Zheng <icenowy@aosc.io>
* Based on the work of Ondrej Jirman <megous@megous.com>
* Based on the work of Josef Gajdusek <atx@atx.name>
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/nvmem-consumer.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include "thermal_hwmon.h"
#define MAX_SENSOR_NUM 4
#define FT_TEMP_MASK GENMASK(11, 0)
#define TEMP_CALIB_MASK GENMASK(11, 0)
#define CALIBRATE_DEFAULT 0x800
#define SUN8I_THS_CTRL0 0x00
#define SUN8I_THS_CTRL2 0x40
#define SUN8I_THS_IC 0x44
#define SUN8I_THS_IS 0x48
#define SUN8I_THS_MFC 0x70
#define SUN8I_THS_TEMP_CALIB 0x74
#define SUN8I_THS_TEMP_DATA 0x80
#define SUN50I_THS_CTRL0 0x00
#define SUN50I_H6_THS_ENABLE 0x04
#define SUN50I_H6_THS_PC 0x08
#define SUN50I_H6_THS_DIC 0x10
#define SUN50I_H6_THS_DIS 0x20
#define SUN50I_H6_THS_MFC 0x30
#define SUN50I_H6_THS_TEMP_CALIB 0xa0
#define SUN50I_H6_THS_TEMP_DATA 0xc0
#define SUN8I_THS_CTRL0_T_ACQ0(x) (GENMASK(15, 0) & (x))
#define SUN8I_THS_CTRL2_T_ACQ1(x) ((GENMASK(15, 0) & (x)) << 16)
#define SUN8I_THS_DATA_IRQ_STS(x) BIT(x + 8)
#define SUN50I_THS_CTRL0_T_ACQ(x) ((GENMASK(15, 0) & (x)) << 16)
#define SUN50I_THS_FILTER_EN BIT(2)
#define SUN50I_THS_FILTER_TYPE(x) (GENMASK(1, 0) & (x))
#define SUN50I_H6_THS_PC_TEMP_PERIOD(x) ((GENMASK(19, 0) & (x)) << 12)
#define SUN50I_H6_THS_DATA_IRQ_STS(x) BIT(x)
/* millidegree celsius */
struct tsensor {
struct ths_device *tmdev;
struct thermal_zone_device *tzd;
int id;
};
struct ths_thermal_chip {
bool has_mod_clk;
bool has_bus_clk_reset;
int sensor_num;
int offset;
int scale;
int ft_deviation;
int temp_data_base;
int (*calibrate)(struct ths_device *tmdev,
u16 *caldata, int callen);
int (*init)(struct ths_device *tmdev);
int (*irq_ack)(struct ths_device *tmdev);
int (*calc_temp)(struct ths_device *tmdev,
int id, int reg);
};
struct ths_device {
const struct ths_thermal_chip *chip;
struct device *dev;
struct regmap *regmap;
struct reset_control *reset;
struct clk *bus_clk;
struct clk *mod_clk;
struct tsensor sensor[MAX_SENSOR_NUM];
};
/* Temp Unit: millidegree Celsius */
static int sun8i_ths_calc_temp(struct ths_device *tmdev,
int id, int reg)
{
return tmdev->chip->offset - (reg * tmdev->chip->scale / 10);
}
static int sun50i_h5_calc_temp(struct ths_device *tmdev,
int id, int reg)
{
if (reg >= 0x500)
return -1191 * reg / 10 + 223000;
else if (!id)
return -1452 * reg / 10 + 259000;
else
return -1590 * reg / 10 + 276000;
}
static int sun8i_ths_get_temp(void *data, int *temp)
{
struct tsensor *s = data;
struct ths_device *tmdev = s->tmdev;
int val = 0;
regmap_read(tmdev->regmap, tmdev->chip->temp_data_base +
0x4 * s->id, &val);
/* ths have no data yet */
if (!val)
return -EAGAIN;
*temp = tmdev->chip->calc_temp(tmdev, s->id, val);
/*
* According to the original sdk, there are some platforms(rarely)
* that add a fixed offset value after calculating the temperature
* value. We can't simply put it on the formula for calculating the
* temperature above, because the formula for calculating the
* temperature above is also used when the sensor is calibrated. If
* do this, the correct calibration formula is hard to know.
*/
*temp += tmdev->chip->ft_deviation;
return 0;
}
static const struct thermal_zone_of_device_ops ths_ops = {
.get_temp = sun8i_ths_get_temp,
};
static const struct regmap_config config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
.fast_io = true,
.max_register = 0xfc,
};
static int sun8i_h3_irq_ack(struct ths_device *tmdev)
{
int i, state, ret = 0;
regmap_read(tmdev->regmap, SUN8I_THS_IS, &state);
for (i = 0; i < tmdev->chip->sensor_num; i++) {
if (state & SUN8I_THS_DATA_IRQ_STS(i)) {
regmap_write(tmdev->regmap, SUN8I_THS_IS,
SUN8I_THS_DATA_IRQ_STS(i));
ret |= BIT(i);
}
}
return ret;
}
static int sun50i_h6_irq_ack(struct ths_device *tmdev)
{
int i, state, ret = 0;
regmap_read(tmdev->regmap, SUN50I_H6_THS_DIS, &state);
for (i = 0; i < tmdev->chip->sensor_num; i++) {
if (state & SUN50I_H6_THS_DATA_IRQ_STS(i)) {
regmap_write(tmdev->regmap, SUN50I_H6_THS_DIS,
SUN50I_H6_THS_DATA_IRQ_STS(i));
ret |= BIT(i);
}
}
return ret;
}
static irqreturn_t sun8i_irq_thread(int irq, void *data)
{
struct ths_device *tmdev = data;
int i, state;
state = tmdev->chip->irq_ack(tmdev);
for (i = 0; i < tmdev->chip->sensor_num; i++) {
if (state & BIT(i))
thermal_zone_device_update(tmdev->sensor[i].tzd,
THERMAL_EVENT_UNSPECIFIED);
}
return IRQ_HANDLED;
}
static int sun8i_h3_ths_calibrate(struct ths_device *tmdev,
u16 *caldata, int callen)
{
int i;
if (!caldata[0] || callen < 2 * tmdev->chip->sensor_num)
return -EINVAL;
for (i = 0; i < tmdev->chip->sensor_num; i++) {
int offset = (i % 2) << 4;
regmap_update_bits(tmdev->regmap,
SUN8I_THS_TEMP_CALIB + (4 * (i >> 1)),
0xfff << offset,
caldata[i] << offset);
}
return 0;
}
static int sun50i_h6_ths_calibrate(struct ths_device *tmdev,
u16 *caldata, int callen)
{
struct device *dev = tmdev->dev;
int i, ft_temp;
if (!caldata[0] || callen < 2 + 2 * tmdev->chip->sensor_num)
return -EINVAL;
/*
* efuse layout:
*
* 0 11 16 32
* +-------+-------+-------+
* |temp| |sensor0|sensor1|
* +-------+-------+-------+
*
* The calibration data on the H6 is the ambient temperature and
* sensor values that are filled during the factory test stage.
*
* The unit of stored FT temperature is 0.1 degreee celusis.
*
* We need to calculate a delta between measured and caluclated
* register values and this will become a calibration offset.
*/
ft_temp = (caldata[0] & FT_TEMP_MASK) * 100;
for (i = 0; i < tmdev->chip->sensor_num; i++) {
int sensor_reg = caldata[i + 1];
int cdata, offset;
int sensor_temp = tmdev->chip->calc_temp(tmdev, i, sensor_reg);
/*
* Calibration data is CALIBRATE_DEFAULT - (calculated
* temperature from sensor reading at factory temperature
* minus actual factory temperature) * 14.88 (scale from
* temperature to register values)
*/
cdata = CALIBRATE_DEFAULT -
((sensor_temp - ft_temp) * 10 / tmdev->chip->scale);
if (cdata & ~TEMP_CALIB_MASK) {
/*
* Calibration value more than 12-bit, but calibration
* register is 12-bit. In this case, ths hardware can
* still work without calibration, although the data
* won't be so accurate.
*/
dev_warn(dev, "sensor%d is not calibrated.\n", i);
continue;
}
offset = (i % 2) * 16;
regmap_update_bits(tmdev->regmap,
SUN50I_H6_THS_TEMP_CALIB + (i / 2 * 4),
0xfff << offset,
cdata << offset);
}
return 0;
}
static int sun8i_ths_calibrate(struct ths_device *tmdev)
{
struct nvmem_cell *calcell;
struct device *dev = tmdev->dev;
u16 *caldata;
size_t callen;
int ret = 0;
calcell = devm_nvmem_cell_get(dev, "calibration");
if (IS_ERR(calcell)) {
if (PTR_ERR(calcell) == -EPROBE_DEFER)
return -EPROBE_DEFER;
/*
* Even if the external calibration data stored in sid is
* not accessible, the THS hardware can still work, although
* the data won't be so accurate.
*
* The default value of calibration register is 0x800 for
* every sensor, and the calibration value is usually 0x7xx
* or 0x8xx, so they won't be away from the default value
* for a lot.
*
* So here we do not return error if the calibartion data is
* not available, except the probe needs deferring.
*/
goto out;
}
caldata = nvmem_cell_read(calcell, &callen);
if (IS_ERR(caldata)) {
ret = PTR_ERR(caldata);
goto out;
}
tmdev->chip->calibrate(tmdev, caldata, callen);
kfree(caldata);
out:
return ret;
}
static int sun8i_ths_resource_init(struct ths_device *tmdev)
{
struct device *dev = tmdev->dev;
struct platform_device *pdev = to_platform_device(dev);
void __iomem *base;
int ret;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
tmdev->regmap = devm_regmap_init_mmio(dev, base, &config);
if (IS_ERR(tmdev->regmap))
return PTR_ERR(tmdev->regmap);
if (tmdev->chip->has_bus_clk_reset) {
tmdev->reset = devm_reset_control_get(dev, NULL);
if (IS_ERR(tmdev->reset))
return PTR_ERR(tmdev->reset);
tmdev->bus_clk = devm_clk_get(&pdev->dev, "bus");
if (IS_ERR(tmdev->bus_clk))
return PTR_ERR(tmdev->bus_clk);
}
if (tmdev->chip->has_mod_clk) {
tmdev->mod_clk = devm_clk_get(&pdev->dev, "mod");
if (IS_ERR(tmdev->mod_clk))
return PTR_ERR(tmdev->mod_clk);
}
ret = reset_control_deassert(tmdev->reset);
if (ret)
return ret;
ret = clk_prepare_enable(tmdev->bus_clk);
if (ret)
goto assert_reset;
ret = clk_set_rate(tmdev->mod_clk, 24000000);
if (ret)
goto bus_disable;
ret = clk_prepare_enable(tmdev->mod_clk);
if (ret)
goto bus_disable;
ret = sun8i_ths_calibrate(tmdev);
if (ret)
goto mod_disable;
return 0;
mod_disable:
clk_disable_unprepare(tmdev->mod_clk);
bus_disable:
clk_disable_unprepare(tmdev->bus_clk);
assert_reset:
reset_control_assert(tmdev->reset);
return ret;
}
static int sun8i_h3_thermal_init(struct ths_device *tmdev)
{
int val;
/* average over 4 samples */
regmap_write(tmdev->regmap, SUN8I_THS_MFC,
SUN50I_THS_FILTER_EN |
SUN50I_THS_FILTER_TYPE(1));
/*
* clkin = 24MHz
* filter_samples = 4
* period = 0.25s
*
* x = period * clkin / 4096 / filter_samples - 1
* = 365
*/
val = GENMASK(7 + tmdev->chip->sensor_num, 8);
regmap_write(tmdev->regmap, SUN8I_THS_IC,
SUN50I_H6_THS_PC_TEMP_PERIOD(365) | val);
/*
* T_acq = 20us
* clkin = 24MHz
*
* x = T_acq * clkin - 1
* = 479
*/
regmap_write(tmdev->regmap, SUN8I_THS_CTRL0,
SUN8I_THS_CTRL0_T_ACQ0(479));
val = GENMASK(tmdev->chip->sensor_num - 1, 0);
regmap_write(tmdev->regmap, SUN8I_THS_CTRL2,
SUN8I_THS_CTRL2_T_ACQ1(479) | val);
return 0;
}
/*
* Without this undocummented value, the returned temperatures would
* be higher than real ones by about 20C.
*/
#define SUN50I_H6_CTRL0_UNK 0x0000002f
static int sun50i_h6_thermal_init(struct ths_device *tmdev)
{
int val;
/*
* T_acq = 20us
* clkin = 24MHz
*
* x = T_acq * clkin - 1
* = 479
*/
regmap_write(tmdev->regmap, SUN50I_THS_CTRL0,
SUN50I_H6_CTRL0_UNK | SUN50I_THS_CTRL0_T_ACQ(479));
/* average over 4 samples */
regmap_write(tmdev->regmap, SUN50I_H6_THS_MFC,
SUN50I_THS_FILTER_EN |
SUN50I_THS_FILTER_TYPE(1));
/*
* clkin = 24MHz
* filter_samples = 4
* period = 0.25s
*
* x = period * clkin / 4096 / filter_samples - 1
* = 365
*/
regmap_write(tmdev->regmap, SUN50I_H6_THS_PC,
SUN50I_H6_THS_PC_TEMP_PERIOD(365));
/* enable sensor */
val = GENMASK(tmdev->chip->sensor_num - 1, 0);
regmap_write(tmdev->regmap, SUN50I_H6_THS_ENABLE, val);
/* thermal data interrupt enable */
val = GENMASK(tmdev->chip->sensor_num - 1, 0);
regmap_write(tmdev->regmap, SUN50I_H6_THS_DIC, val);
return 0;
}
static int sun8i_ths_register(struct ths_device *tmdev)
{
int i;
for (i = 0; i < tmdev->chip->sensor_num; i++) {
tmdev->sensor[i].tmdev = tmdev;
tmdev->sensor[i].id = i;
tmdev->sensor[i].tzd =
devm_thermal_zone_of_sensor_register(tmdev->dev,
i,
&tmdev->sensor[i],
&ths_ops);
if (IS_ERR(tmdev->sensor[i].tzd))
return PTR_ERR(tmdev->sensor[i].tzd);
if (devm_thermal_add_hwmon_sysfs(tmdev->sensor[i].tzd))
dev_warn(tmdev->dev,
"Failed to add hwmon sysfs attributes\n");
}
return 0;
}
static int sun8i_ths_probe(struct platform_device *pdev)
{
struct ths_device *tmdev;
struct device *dev = &pdev->dev;
int ret, irq;
tmdev = devm_kzalloc(dev, sizeof(*tmdev), GFP_KERNEL);
if (!tmdev)
return -ENOMEM;
tmdev->dev = dev;
tmdev->chip = of_device_get_match_data(&pdev->dev);
if (!tmdev->chip)
return -EINVAL;
platform_set_drvdata(pdev, tmdev);
ret = sun8i_ths_resource_init(tmdev);
if (ret)
return ret;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = tmdev->chip->init(tmdev);
if (ret)
return ret;
ret = sun8i_ths_register(tmdev);
if (ret)
return ret;
/*
* Avoid entering the interrupt handler, the thermal device is not
* registered yet, we deffer the registration of the interrupt to
* the end.
*/
ret = devm_request_threaded_irq(dev, irq, NULL,
sun8i_irq_thread,
IRQF_ONESHOT, "ths", tmdev);
if (ret)
return ret;
return 0;
}
static int sun8i_ths_remove(struct platform_device *pdev)
{
struct ths_device *tmdev = platform_get_drvdata(pdev);
clk_disable_unprepare(tmdev->mod_clk);
clk_disable_unprepare(tmdev->bus_clk);
reset_control_assert(tmdev->reset);
return 0;
}
static const struct ths_thermal_chip sun8i_a83t_ths = {
.sensor_num = 3,
.scale = 705,
.offset = 191668,
.temp_data_base = SUN8I_THS_TEMP_DATA,
.calibrate = sun8i_h3_ths_calibrate,
.init = sun8i_h3_thermal_init,
.irq_ack = sun8i_h3_irq_ack,
.calc_temp = sun8i_ths_calc_temp,
};
static const struct ths_thermal_chip sun8i_h3_ths = {
.sensor_num = 1,
.scale = 1211,
.offset = 217000,
.has_mod_clk = true,
.has_bus_clk_reset = true,
.temp_data_base = SUN8I_THS_TEMP_DATA,
.calibrate = sun8i_h3_ths_calibrate,
.init = sun8i_h3_thermal_init,
.irq_ack = sun8i_h3_irq_ack,
.calc_temp = sun8i_ths_calc_temp,
};
static const struct ths_thermal_chip sun8i_r40_ths = {
.sensor_num = 2,
.offset = 251086,
.scale = 1130,
.has_mod_clk = true,
.has_bus_clk_reset = true,
.temp_data_base = SUN8I_THS_TEMP_DATA,
.calibrate = sun8i_h3_ths_calibrate,
.init = sun8i_h3_thermal_init,
.irq_ack = sun8i_h3_irq_ack,
.calc_temp = sun8i_ths_calc_temp,
};
static const struct ths_thermal_chip sun50i_a64_ths = {
.sensor_num = 3,
.offset = 260890,
.scale = 1170,
.has_mod_clk = true,
.has_bus_clk_reset = true,
.temp_data_base = SUN8I_THS_TEMP_DATA,
.calibrate = sun8i_h3_ths_calibrate,
.init = sun8i_h3_thermal_init,
.irq_ack = sun8i_h3_irq_ack,
.calc_temp = sun8i_ths_calc_temp,
};
static const struct ths_thermal_chip sun50i_h5_ths = {
.sensor_num = 2,
.has_mod_clk = true,
.has_bus_clk_reset = true,
.temp_data_base = SUN8I_THS_TEMP_DATA,
.calibrate = sun8i_h3_ths_calibrate,
.init = sun8i_h3_thermal_init,
.irq_ack = sun8i_h3_irq_ack,
.calc_temp = sun50i_h5_calc_temp,
};
static const struct ths_thermal_chip sun50i_h6_ths = {
.sensor_num = 2,
.has_bus_clk_reset = true,
.ft_deviation = 7000,
.offset = 187744,
.scale = 672,
.temp_data_base = SUN50I_H6_THS_TEMP_DATA,
.calibrate = sun50i_h6_ths_calibrate,
.init = sun50i_h6_thermal_init,
.irq_ack = sun50i_h6_irq_ack,
.calc_temp = sun8i_ths_calc_temp,
};
static const struct of_device_id of_ths_match[] = {
{ .compatible = "allwinner,sun8i-a83t-ths", .data = &sun8i_a83t_ths },
{ .compatible = "allwinner,sun8i-h3-ths", .data = &sun8i_h3_ths },
{ .compatible = "allwinner,sun8i-r40-ths", .data = &sun8i_r40_ths },
{ .compatible = "allwinner,sun50i-a64-ths", .data = &sun50i_a64_ths },
{ .compatible = "allwinner,sun50i-h5-ths", .data = &sun50i_h5_ths },
{ .compatible = "allwinner,sun50i-h6-ths", .data = &sun50i_h6_ths },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, of_ths_match);
static struct platform_driver ths_driver = {
.probe = sun8i_ths_probe,
.remove = sun8i_ths_remove,
.driver = {
.name = "sun8i-thermal",
.of_match_table = of_ths_match,
},
};
module_platform_driver(ths_driver);
MODULE_DESCRIPTION("Thermal sensor driver for Allwinner SOC");
MODULE_LICENSE("GPL v2");

View File

@ -360,7 +360,7 @@ static struct soctherm_oc_irq_chip_data soc_irq_cdata;
/**
* ccroc_writel() - writes a value to a CCROC register
* @ts: pointer to a struct tegra_soctherm
* @v: the value to write
* @value: the value to write
* @reg: the register offset
*
* Writes @v to @reg. No return value.
@ -435,6 +435,7 @@ static int tegra_thermctl_get_temp(void *data, int *out_temp)
/**
* enforce_temp_range() - check and enforce temperature range [min, max]
* @dev: struct device * of the SOC_THERM instance
* @trip_temp: the trip temperature to check
*
* Checks and enforces the permitted temperature range that SOC_THERM
@ -747,6 +748,8 @@ static int get_hot_temp(struct thermal_zone_device *tz, int *trip, int *temp)
/**
* tegra_soctherm_set_hwtrips() - set HW trip point from DT data
* @dev: struct device * of the SOC_THERM instance
* @sg: pointer to the sensor group to set the thermtrip temperature for
* @tz: struct thermal_zone_device *
*
* Configure the SOC_THERM HW trip points, setting "THERMTRIP"
* "THROTTLE" trip points , using "thermtrips", "critical" or "hot"
@ -931,6 +934,7 @@ static irqreturn_t soctherm_thermal_isr_thread(int irq, void *dev_id)
/**
* soctherm_oc_intr_enable() - Enables the soctherm over-current interrupt
* @ts: pointer to a struct tegra_soctherm
* @alarm: The soctherm throttle id
* @enable: Flag indicating enable the soctherm over-current
* interrupt or disable it
@ -1156,7 +1160,7 @@ static void soctherm_oc_irq_enable(struct irq_data *data)
/**
* soctherm_oc_irq_disable() - Disables overcurrent interrupt requests
* @irq_data: The interrupt request information
* @data: The interrupt request information
*
* Clears the interrupt request enable bit of the overcurrent
* interrupt request chip data.
@ -1206,6 +1210,7 @@ static int soctherm_oc_irq_map(struct irq_domain *h, unsigned int virq,
/**
* soctherm_irq_domain_xlate_twocell() - xlate for soctherm interrupts
* @d: Interrupt request domain
* @ctrlr: Controller device tree node
* @intspec: Array of u32s from DTs "interrupt" property
* @intsize: Number of values inside the intspec array
* @out_hwirq: HW IRQ value associated with this interrupt
@ -1681,6 +1686,7 @@ err:
/**
* soctherm_init_hw_throt_cdev() - Parse the HW throttle configurations
* and register them as cooling devices.
* @pdev: Pointer to platform_device struct
*/
static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
{
@ -1751,6 +1757,7 @@ static void soctherm_init_hw_throt_cdev(struct platform_device *pdev)
/**
* throttlectl_cpu_level_cfg() - programs CCROC NV_THERM level config
* @ts: pointer to a struct tegra_soctherm
* @level: describing the level LOW/MED/HIGH of throttling
*
* It's necessary to set up the CPU-local CCROC NV_THERM instance with
@ -1798,6 +1805,7 @@ static void throttlectl_cpu_level_cfg(struct tegra_soctherm *ts, int level)
/**
* throttlectl_cpu_level_select() - program CPU pulse skipper config
* @ts: pointer to a struct tegra_soctherm
* @throt: the LIGHT/HEAVY of throttle event id
*
* Pulse skippers are used to throttle clock frequencies. This
@ -1841,6 +1849,7 @@ static void throttlectl_cpu_level_select(struct tegra_soctherm *ts,
/**
* throttlectl_cpu_mn() - program CPU pulse skipper configuration
* @ts: pointer to a struct tegra_soctherm
* @throt: the LIGHT/HEAVY of throttle event id
*
* Pulse skippers are used to throttle clock frequencies. This
@ -1874,6 +1883,7 @@ static void throttlectl_cpu_mn(struct tegra_soctherm *ts,
/**
* throttlectl_gpu_level_select() - selects throttling level for GPU
* @ts: pointer to a struct tegra_soctherm
* @throt: the LIGHT/HEAVY of throttle event id
*
* This function programs soctherm's interface to GK20a NV_THERM to select
@ -1918,6 +1928,7 @@ static int soctherm_oc_cfg_program(struct tegra_soctherm *ts,
/**
* soctherm_throttle_program() - programs pulse skippers' configuration
* @ts: pointer to a struct tegra_soctherm
* @throt: the LIGHT/HEAVY of the throttle event id.
*
* Pulse skippers are used to throttle clock frequencies.

View File

@ -76,13 +76,17 @@ static int gadc_thermal_read_linear_lookup_table(struct device *dev,
struct gadc_thermal_info *gti)
{
struct device_node *np = dev->of_node;
enum iio_chan_type chan_type;
int ntable;
int ret;
ntable = of_property_count_elems_of_size(np, "temperature-lookup-table",
sizeof(u32));
if (ntable <= 0) {
dev_notice(dev, "no lookup table, assuming DAC channel returns milliCelcius\n");
ret = iio_get_channel_type(gti->channel, &chan_type);
if (ret || chan_type != IIO_TEMP)
dev_notice(dev,
"no lookup table, assuming DAC channel returns milliCelcius\n");
return 0;
}
@ -124,13 +128,6 @@ static int gadc_thermal_probe(struct platform_device *pdev)
if (!gti)
return -ENOMEM;
ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti);
if (ret < 0)
return ret;
gti->dev = &pdev->dev;
platform_set_drvdata(pdev, gti);
gti->channel = devm_iio_channel_get(&pdev->dev, "sensor-channel");
if (IS_ERR(gti->channel)) {
ret = PTR_ERR(gti->channel);
@ -139,6 +136,13 @@ static int gadc_thermal_probe(struct platform_device *pdev)
return ret;
}
ret = gadc_thermal_read_linear_lookup_table(&pdev->dev, gti);
if (ret < 0)
return ret;
gti->dev = &pdev->dev;
platform_set_drvdata(pdev, gti);
gti->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, gti,
&gadc_thermal_ops);
if (IS_ERR(gti->tz_dev)) {

View File

@ -92,14 +92,12 @@ thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
/* device tree support */
#ifdef CONFIG_THERMAL_OF
int of_parse_thermal_zones(void);
void of_thermal_destroy_zones(void);
int of_thermal_get_ntrips(struct thermal_zone_device *);
bool of_thermal_is_trip_valid(struct thermal_zone_device *, int);
const struct thermal_trip *
of_thermal_get_trip_points(struct thermal_zone_device *);
#else
static inline int of_parse_thermal_zones(void) { return 0; }
static inline void of_thermal_destroy_zones(void) { }
static inline int of_thermal_get_ntrips(struct thermal_zone_device *tz)
{
return 0;

View File

@ -248,3 +248,31 @@ void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
kfree(hwmon);
}
EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
static void devm_thermal_hwmon_release(struct device *dev, void *res)
{
thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res);
}
int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
{
struct thermal_zone_device **ptr;
int ret;
ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = thermal_add_hwmon_sysfs(tz);
if (ret) {
devres_free(ptr);
return ret;
}
*ptr = tz;
devres_add(&tz->device, ptr);
return ret;
}
EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs);

View File

@ -17,6 +17,7 @@
#ifdef CONFIG_THERMAL_HWMON
int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz);
int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz);
void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz);
#else
static inline int
@ -25,6 +26,12 @@ thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
return 0;
}
static inline int
devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
{
return 0;
}
static inline void
thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
{

View File

@ -17,8 +17,8 @@
/**
* notify_user_space - Notifies user space about thermal events
* @tz - thermal_zone_device
* @trip - trip point index
* @tz: thermal_zone_device
* @trip: trip point index
*
* This function notifies the user space through UEvents.
*/

View File

@ -45,6 +45,7 @@
* @clk_topcrm: topcrm clk structure
* @clk_apb: apb clk structure
* @regs: pointer to base address of the thermal sensor
* @dev: struct device pointer
*/
struct zx2967_thermal_priv {

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* thermal_exynos.h - Samsung EXYNOS TMU device tree definitions
* thermal_exynos.h - Samsung Exynos TMU device tree definitions
*
* Copyright (C) 2014 Samsung Electronics
* Lukasz Majewski <l.majewski@samsung.com>

View File

@ -7,7 +7,7 @@
* Copyright (C) 2013 Texas Instruments Inc.
* Contact: Eduardo Valentin <eduardo.valentin@ti.com>
*
* Highly based on cpu_cooling.c.
* Highly based on cpufreq_cooling.c.
* Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com)
* Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org>
*/

View File

@ -19,7 +19,7 @@
struct cpufreq_policy;
#ifdef CONFIG_CPU_THERMAL
#ifdef CONFIG_CPU_FREQ_THERMAL
/**
* cpufreq_cooling_register - function to create cpufreq cooling device.
* @policy: cpufreq policy.
@ -40,7 +40,7 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
struct thermal_cooling_device *
of_cpufreq_cooling_register(struct cpufreq_policy *policy);
#else /* !CONFIG_CPU_THERMAL */
#else /* !CONFIG_CPU_FREQ_THERMAL */
static inline struct thermal_cooling_device *
cpufreq_cooling_register(struct cpufreq_policy *policy)
{
@ -58,6 +58,24 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy)
{
return NULL;
}
#endif /* CONFIG_CPU_THERMAL */
#endif /* CONFIG_CPU_FREQ_THERMAL */
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);
#else /* CONFIG_CPU_IDLE_THERMAL */
static inline int 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 */
#endif /* __CPU_COOLING_H__ */