- 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:
commit
abb22e44cf
@ -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>;
|
||||
};
|
||||
|
||||
...
|
@ -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>;
|
||||
};
|
||||
};
|
||||
...
|
@ -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:
|
||||
|
189
Documentation/driver-api/thermal/cpu-idle-cooling.rst
Normal file
189
Documentation/driver-api/thermal/cpu-idle-cooling.rst
Normal 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 OPP’s 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 state’s 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 don’t 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 )
|
@ -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)
|
||||
^
|
||||
|
|
||||
|
|
||||
|
13
MAINTAINERS
13
MAINTAINERS
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 &= ~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 = {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
123
drivers/thermal/broadcom/bcm2711_thermal.c
Normal file
123
drivers/thermal/broadcom/bcm2711_thermal.c
Normal 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");
|
@ -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);
|
||||
|
||||
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,17 +352,16 @@ 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;
|
||||
}
|
||||
if (irq >= 0) {
|
||||
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
||||
brcmstb_tmon_irq_thread, IRQF_ONESHOT,
|
||||
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");
|
||||
|
||||
|
@ -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>
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
232
drivers/thermal/cpuidle_cooling.c
Normal file
232
drivers/thermal/cpuidle_cooling.c
Normal 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);
|
||||
}
|
@ -152,7 +152,7 @@ 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,
|
||||
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 */
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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, },
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
@ -520,6 +519,7 @@ static const struct mtk_thermal_data mt8183_thermal_data = {
|
||||
/**
|
||||
* raw_to_mcelsius - convert a raw ADC value to mcelsius
|
||||
* @mt: The thermal controller
|
||||
* @sensno: sensor number
|
||||
* @raw: raw ADC value
|
||||
*
|
||||
* This converts the raw ADC value to mcelsius using the SoC specific
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
#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)
|
||||
|
||||
#define REGS_TMTMIR 0x008 /* Temperature measurement interval Register */
|
||||
#define TMTMIR_DEFAULT 0x0000000f
|
||||
|
||||
#define REGS_V2_TMSR 0x008 /* monitor site register */
|
||||
|
||||
#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
|
||||
*/
|
||||
struct qoriq_tmu_site_regs {
|
||||
u32 tritsr; /* Immediate Temperature Site Register */
|
||||
u32 tratsr; /* Average Temperature Site Register */
|
||||
u8 res0[0x8];
|
||||
};
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
struct qoriq_tmu_data;
|
||||
#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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
struct thermal_zone_device *tzd;
|
||||
struct qoriq_sensor *sensor = &qdata->sensor[id];
|
||||
int ret;
|
||||
|
||||
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)
|
||||
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;
|
||||
else
|
||||
return PTR_ERR(qdata->sensor[id]->tzd);
|
||||
|
||||
regmap_write(qdata->regmap, REGS_TMR, TMR_DISABLE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (qdata->ver == TMU_VER1)
|
||||
sites |= 0x1 << (15 - id);
|
||||
else
|
||||
sites |= 0x1 << id;
|
||||
}
|
||||
if (devm_thermal_add_hwmon_sysfs(tzd))
|
||||
dev_warn(dev,
|
||||
"Failed to add hwmon sysfs attributes\n");
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
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, ®map_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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
/* Erase threshold content */
|
||||
itr1 = readl_relaxed(sensor->base + DTS_ITR1_OFFSET);
|
||||
itr1 &= ~(TS1_LITTHD_MASK | TS1_HITTHD_MASK);
|
||||
|
||||
/*
|
||||
* Disable low-temp if "low" is too small. As per thermal framework
|
||||
* API, we use -INT_MAX rather than INT_MIN.
|
||||
*/
|
||||
|
||||
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_disable_irq(sensor);
|
||||
itr1 |= (TS1_HITTHD_MASK & (th << TS1_HITTHD_POS));
|
||||
} else {
|
||||
sensor->low_temp_enabled = 0;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
ret = stm_thermal_set_threshold(sensor);
|
||||
if (ret)
|
||||
return ret;
|
||||
itr1 |= (TS1_LITTHD_MASK & (th << TS1_LITTHD_POS));
|
||||
} else {
|
||||
sensor->high_temp_enabled = 0;
|
||||
}
|
||||
|
||||
ret = stm_enable_irq(sensor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = stm_sensor_power_on(sensor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sensor->mode = THERMAL_DEVICE_ENABLED;
|
||||
/* 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__);
|
||||
|
||||
|
@ -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
|
||||
|
639
drivers/thermal/sun8i_thermal.c
Normal file
639
drivers/thermal/sun8i_thermal.c
Normal 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");
|
@ -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.
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
*/
|
||||
|
@ -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__ */
|
||||
|
Loading…
Reference in New Issue
Block a user