You've already forked linux-apfs
mirror of
https://github.com/linux-apfs/linux-apfs.git
synced 2026-05-01 15:00:59 -07:00
Merge branch 'linus' of git://git.kernel.org/pub/scm/linux/kernel/git/evalenti/linux-soc-thermal into thermal-soc
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
* Temperature Sensor on hisilicon SoCs
|
||||
|
||||
** Required properties :
|
||||
|
||||
- compatible: "hisilicon,tsensor".
|
||||
- reg: physical base address of thermal sensor and length of memory mapped
|
||||
region.
|
||||
- interrupt: The interrupt number to the cpu. Defines the interrupt used
|
||||
by /SOCTHERM/tsensor.
|
||||
- clock-names: Input clock name, should be 'thermal_clk'.
|
||||
- clocks: phandles for clock specified in "clock-names" property.
|
||||
- #thermal-sensor-cells: Should be 1. See ./thermal.txt for a description.
|
||||
|
||||
Example :
|
||||
|
||||
tsensor: tsensor@0,f7030700 {
|
||||
compatible = "hisilicon,tsensor";
|
||||
reg = <0x0 0xf7030700 0x0 0x1000>;
|
||||
interrupts = <0 7 0x4>;
|
||||
clocks = <&sys_ctrl HI6220_TSENSOR_CLK>;
|
||||
clock-names = "thermal_clk";
|
||||
#thermal-sensor-cells = <1>;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
Qualcomm QPNP PMIC Temperature Alarm
|
||||
|
||||
QPNP temperature alarm peripherals are found inside of Qualcomm PMIC chips
|
||||
that utilize the Qualcomm SPMI implementation. These peripherals provide an
|
||||
interrupt signal and status register to identify high PMIC die temperature.
|
||||
|
||||
Required properties:
|
||||
- compatible: Should contain "qcom,spmi-temp-alarm".
|
||||
- reg: Specifies the SPMI address and length of the controller's
|
||||
registers.
|
||||
- interrupts: PMIC temperature alarm interrupt.
|
||||
- #thermal-sensor-cells: Should be 0. See thermal.txt for a description.
|
||||
|
||||
Optional properties:
|
||||
- io-channels: Should contain IIO channel specifier for the ADC channel,
|
||||
which report chip die temperature.
|
||||
- io-channel-names: Should contain "thermal".
|
||||
|
||||
Example:
|
||||
|
||||
pm8941_temp: thermal-alarm@2400 {
|
||||
compatible = "qcom,spmi-temp-alarm";
|
||||
reg = <0x2400 0x100>;
|
||||
interrupts = <0 0x24 0 IRQ_TYPE_EDGE_RISING>;
|
||||
#thermal-sensor-cells = <0>;
|
||||
|
||||
io-channels = <&pm8941_vadc VADC_DIE_TEMP>;
|
||||
io-channel-names = "thermal";
|
||||
};
|
||||
|
||||
thermal-zones {
|
||||
pm8941 {
|
||||
polling-delay-passive = <250>;
|
||||
polling-delay = <1000>;
|
||||
|
||||
thermal-sensors = <&pm8941_temp>;
|
||||
|
||||
trips {
|
||||
passive {
|
||||
temperature = <1050000>;
|
||||
hysteresis = <2000>;
|
||||
type = "passive";
|
||||
};
|
||||
alert {
|
||||
temperature = <125000>;
|
||||
hysteresis = <2000>;
|
||||
type = "hot";
|
||||
};
|
||||
crit {
|
||||
temperature = <145000>;
|
||||
hysteresis = <2000>;
|
||||
type = "critical";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -167,6 +167,13 @@ Optional property:
|
||||
by means of sensor ID. Additional coefficients are
|
||||
interpreted as constant offset.
|
||||
|
||||
- sustainable-power: An estimate of the sustainable power (in mW) that the
|
||||
Type: unsigned thermal zone can dissipate at the desired
|
||||
Size: one cell control temperature. For reference, the
|
||||
sustainable power of a 4'' phone is typically
|
||||
2000mW, while on a 10'' tablet is around
|
||||
4500mW.
|
||||
|
||||
Note: The delay properties are bound to the maximum dT/dt (temperature
|
||||
derivative over time) in two situations for a thermal zone:
|
||||
(i) - when passive cooling is activated (polling-delay-passive); and
|
||||
@@ -546,6 +553,8 @@ thermal-zones {
|
||||
*/
|
||||
coefficients = <1200 -345 890>;
|
||||
|
||||
sustainable-power = <2500>;
|
||||
|
||||
trips {
|
||||
/* Trips are based on resulting linear equation */
|
||||
cpu_trip: cpu-trip {
|
||||
|
||||
@@ -36,8 +36,162 @@ the user. The registration APIs returns the cooling device pointer.
|
||||
np: pointer to the cooling device device tree node
|
||||
clip_cpus: cpumask of cpus where the frequency constraints will happen.
|
||||
|
||||
1.1.3 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
1.1.3 struct thermal_cooling_device *cpufreq_power_cooling_register(
|
||||
const struct cpumask *clip_cpus, u32 capacitance,
|
||||
get_static_t plat_static_func)
|
||||
|
||||
Similar to cpufreq_cooling_register, this function registers a cpufreq
|
||||
cooling device. Using this function, the cooling device will
|
||||
implement the power extensions by using a simple cpu power model. The
|
||||
cpus must have registered their OPPs using the OPP library.
|
||||
|
||||
The additional parameters are needed for the power model (See 2. Power
|
||||
models). "capacitance" is the dynamic power coefficient (See 2.1
|
||||
Dynamic power). "plat_static_func" is a function to calculate the
|
||||
static power consumed by these cpus (See 2.2 Static power).
|
||||
|
||||
1.1.4 struct thermal_cooling_device *of_cpufreq_power_cooling_register(
|
||||
struct device_node *np, const struct cpumask *clip_cpus, u32 capacitance,
|
||||
get_static_t plat_static_func)
|
||||
|
||||
Similar to cpufreq_power_cooling_register, this function register a
|
||||
cpufreq cooling device with power extensions using the device tree
|
||||
information supplied by the np parameter.
|
||||
|
||||
1.1.5 void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
|
||||
|
||||
This interface function unregisters the "thermal-cpufreq-%x" cooling device.
|
||||
|
||||
cdev: Cooling device pointer which has to be unregistered.
|
||||
|
||||
2. Power models
|
||||
|
||||
The power API registration functions provide a simple power model for
|
||||
CPUs. The current power is calculated as dynamic + (optionally)
|
||||
static power. This power model requires that the operating-points of
|
||||
the CPUs are registered using the kernel's opp library and the
|
||||
`cpufreq_frequency_table` is assigned to the `struct device` of the
|
||||
cpu. If you are using CONFIG_CPUFREQ_DT then the
|
||||
`cpufreq_frequency_table` should already be assigned to the cpu
|
||||
device.
|
||||
|
||||
The `plat_static_func` parameter of `cpufreq_power_cooling_register()`
|
||||
and `of_cpufreq_power_cooling_register()` is optional. If you don't
|
||||
provide it, only dynamic power will be considered.
|
||||
|
||||
2.1 Dynamic power
|
||||
|
||||
The dynamic power consumption of a processor depends on many factors.
|
||||
For a given processor implementation the primary factors are:
|
||||
|
||||
- The time the processor spends running, consuming dynamic power, as
|
||||
compared to the time in idle states where dynamic consumption is
|
||||
negligible. Herein we refer to this as 'utilisation'.
|
||||
- The voltage and frequency levels as a result of DVFS. The DVFS
|
||||
level is a dominant factor governing power consumption.
|
||||
- In running time the 'execution' behaviour (instruction types, memory
|
||||
access patterns and so forth) causes, in most cases, a second order
|
||||
variation. In pathological cases this variation can be significant,
|
||||
but typically it is of a much lesser impact than the factors above.
|
||||
|
||||
A high level dynamic power consumption model may then be represented as:
|
||||
|
||||
Pdyn = f(run) * Voltage^2 * Frequency * Utilisation
|
||||
|
||||
f(run) here represents the described execution behaviour and its
|
||||
result has a units of Watts/Hz/Volt^2 (this often expressed in
|
||||
mW/MHz/uVolt^2)
|
||||
|
||||
The detailed behaviour for f(run) could be modelled on-line. However,
|
||||
in practice, such an on-line model has dependencies on a number of
|
||||
implementation specific processor support and characterisation
|
||||
factors. Therefore, in initial implementation that contribution is
|
||||
represented as a constant coefficient. This is a simplification
|
||||
consistent with the relative contribution to overall power variation.
|
||||
|
||||
In this simplified representation our model becomes:
|
||||
|
||||
Pdyn = Capacitance * Voltage^2 * Frequency * Utilisation
|
||||
|
||||
Where `capacitance` is a constant that represents an indicative
|
||||
running time dynamic power coefficient in fundamental units of
|
||||
mW/MHz/uVolt^2. Typical values for mobile CPUs might lie in range
|
||||
from 100 to 500. For reference, the approximate values for the SoC in
|
||||
ARM's Juno Development Platform are 530 for the Cortex-A57 cluster and
|
||||
140 for the Cortex-A53 cluster.
|
||||
|
||||
|
||||
2.2 Static power
|
||||
|
||||
Static leakage power consumption depends on a number of factors. For a
|
||||
given circuit implementation the primary factors are:
|
||||
|
||||
- Time the circuit spends in each 'power state'
|
||||
- Temperature
|
||||
- Operating voltage
|
||||
- Process grade
|
||||
|
||||
The time the circuit spends in each 'power state' for a given
|
||||
evaluation period at first order means OFF or ON. However,
|
||||
'retention' states can also be supported that reduce power during
|
||||
inactive periods without loss of context.
|
||||
|
||||
Note: The visibility of state entries to the OS can vary, according to
|
||||
platform specifics, and this can then impact the accuracy of a model
|
||||
based on OS state information alone. It might be possible in some
|
||||
cases to extract more accurate information from system resources.
|
||||
|
||||
The temperature, operating voltage and process 'grade' (slow to fast)
|
||||
of the circuit are all significant factors in static leakage power
|
||||
consumption. All of these have complex relationships to static power.
|
||||
|
||||
Circuit implementation specific factors include the chosen silicon
|
||||
process as well as the type, number and size of transistors in both
|
||||
the logic gates and any RAM elements included.
|
||||
|
||||
The static power consumption modelling must take into account the
|
||||
power managed regions that are implemented. Taking the example of an
|
||||
ARM processor cluster, the modelling would take into account whether
|
||||
each CPU can be powered OFF separately or if only a single power
|
||||
region is implemented for the complete cluster.
|
||||
|
||||
In one view, there are others, a static power consumption model can
|
||||
then start from a set of reference values for each power managed
|
||||
region (e.g. CPU, Cluster/L2) in each state (e.g. ON, OFF) at an
|
||||
arbitrary process grade, voltage and temperature point. These values
|
||||
are then scaled for all of the following: the time in each state, the
|
||||
process grade, the current temperature and the operating voltage.
|
||||
However, since both implementation specific and complex relationships
|
||||
dominate the estimate, the appropriate interface to the model from the
|
||||
cpu cooling device is to provide a function callback that calculates
|
||||
the static power in this platform. When registering the cpu cooling
|
||||
device pass a function pointer that follows the `get_static_t`
|
||||
prototype:
|
||||
|
||||
int plat_get_static(cpumask_t *cpumask, int interval,
|
||||
unsigned long voltage, u32 &power);
|
||||
|
||||
`cpumask` is the cpumask of the cpus involved in the calculation.
|
||||
`voltage` is the voltage at which they are operating. The function
|
||||
should calculate the average static power for the last `interval`
|
||||
milliseconds. It returns 0 on success, -E* on error. If it
|
||||
succeeds, it should store the static power in `power`. Reading the
|
||||
temperature of the cpus described by `cpumask` is left for
|
||||
plat_get_static() to do as the platform knows best which thermal
|
||||
sensor is closest to the cpu.
|
||||
|
||||
If `plat_static_func` is NULL, static power is considered to be
|
||||
negligible for this platform and only dynamic power is considered.
|
||||
|
||||
The platform specific callback can then use any combination of tables
|
||||
and/or equations to permute the estimated value. Process grade
|
||||
information is not passed to the model since access to such data, from
|
||||
on-chip measurement capability or manufacture time data, is platform
|
||||
specific.
|
||||
|
||||
Note: the significance of static power for CPUs in comparison to
|
||||
dynamic power is highly dependent on implementation. Given the
|
||||
potential complexity in implementation, the importance and accuracy of
|
||||
its inclusion when using cpu cooling devices should be assessed on a
|
||||
case by case basis.
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
Power allocator governor tunables
|
||||
=================================
|
||||
|
||||
Trip points
|
||||
-----------
|
||||
|
||||
The governor requires the following two passive trip points:
|
||||
|
||||
1. "switch on" trip point: temperature above which the governor
|
||||
control loop starts operating. This is the first passive trip
|
||||
point of the thermal zone.
|
||||
|
||||
2. "desired temperature" trip point: it should be higher than the
|
||||
"switch on" trip point. This the target temperature the governor
|
||||
is controlling for. This is the last passive trip point of the
|
||||
thermal zone.
|
||||
|
||||
PID Controller
|
||||
--------------
|
||||
|
||||
The power allocator governor implements a
|
||||
Proportional-Integral-Derivative controller (PID controller) with
|
||||
temperature as the control input and power as the controlled output:
|
||||
|
||||
P_max = k_p * e + k_i * err_integral + k_d * diff_err + sustainable_power
|
||||
|
||||
where
|
||||
e = desired_temperature - current_temperature
|
||||
err_integral is the sum of previous errors
|
||||
diff_err = e - previous_error
|
||||
|
||||
It is similar to the one depicted below:
|
||||
|
||||
k_d
|
||||
|
|
||||
current_temp |
|
||||
| v
|
||||
| +----------+ +---+
|
||||
| +----->| diff_err |-->| X |------+
|
||||
| | +----------+ +---+ |
|
||||
| | | tdp actor
|
||||
| | k_i | | get_requested_power()
|
||||
| | | | | | |
|
||||
| | | | | | | ...
|
||||
v | v v v v v
|
||||
+---+ | +-------+ +---+ +---+ +---+ +----------+
|
||||
| S |-------+----->| sum e |----->| X |--->| S |-->| S |-->|power |
|
||||
+---+ | +-------+ +---+ +---+ +---+ |allocation|
|
||||
^ | ^ +----------+
|
||||
| | | | |
|
||||
| | +---+ | | |
|
||||
| +------->| X |-------------------+ v v
|
||||
| +---+ granted performance
|
||||
desired_temperature ^
|
||||
|
|
||||
|
|
||||
k_po/k_pu
|
||||
|
||||
Sustainable power
|
||||
-----------------
|
||||
|
||||
An estimate of the sustainable dissipatable power (in mW) should be
|
||||
provided while registering the thermal zone. This estimates the
|
||||
sustained power that can be dissipated at the desired control
|
||||
temperature. This is the maximum sustained power for allocation at
|
||||
the desired maximum temperature. The actual sustained power can vary
|
||||
for a number of reasons. The closed loop controller will take care of
|
||||
variations such as environmental conditions, and some factors related
|
||||
to the speed-grade of the silicon. `sustainable_power` is therefore
|
||||
simply an estimate, and may be tuned to affect the aggressiveness of
|
||||
the thermal ramp. For reference, the sustainable power of a 4" phone
|
||||
is typically 2000mW, while on a 10" tablet is around 4500mW (may vary
|
||||
depending on screen size).
|
||||
|
||||
If you are using device tree, do add it as a property of the
|
||||
thermal-zone. For example:
|
||||
|
||||
thermal-zones {
|
||||
soc_thermal {
|
||||
polling-delay = <1000>;
|
||||
polling-delay-passive = <100>;
|
||||
sustainable-power = <2500>;
|
||||
...
|
||||
|
||||
Instead, if the thermal zone is registered from the platform code, pass a
|
||||
`thermal_zone_params` that has a `sustainable_power`. If no
|
||||
`thermal_zone_params` were being passed, then something like below
|
||||
will suffice:
|
||||
|
||||
static const struct thermal_zone_params tz_params = {
|
||||
.sustainable_power = 3500,
|
||||
};
|
||||
|
||||
and then pass `tz_params` as the 5th parameter to
|
||||
`thermal_zone_device_register()`
|
||||
|
||||
k_po and k_pu
|
||||
-------------
|
||||
|
||||
The implementation of the PID controller in the power allocator
|
||||
thermal governor allows the configuration of two proportional term
|
||||
constants: `k_po` and `k_pu`. `k_po` is the proportional term
|
||||
constant during temperature overshoot periods (current temperature is
|
||||
above "desired temperature" trip point). Conversely, `k_pu` is the
|
||||
proportional term constant during temperature undershoot periods
|
||||
(current temperature below "desired temperature" trip point).
|
||||
|
||||
These controls are intended as the primary mechanism for configuring
|
||||
the permitted thermal "ramp" of the system. For instance, a lower
|
||||
`k_pu` value will provide a slower ramp, at the cost of capping
|
||||
available capacity at a low temperature. On the other hand, a high
|
||||
value of `k_pu` will result in the governor granting very high power
|
||||
whilst temperature is low, and may lead to temperature overshooting.
|
||||
|
||||
The default value for `k_pu` is:
|
||||
|
||||
2 * sustainable_power / (desired_temperature - switch_on_temp)
|
||||
|
||||
This means that at `switch_on_temp` the output of the controller's
|
||||
proportional term will be 2 * `sustainable_power`. The default value
|
||||
for `k_po` is:
|
||||
|
||||
sustainable_power / (desired_temperature - switch_on_temp)
|
||||
|
||||
Focusing on the proportional and feed forward values of the PID
|
||||
controller equation we have:
|
||||
|
||||
P_max = k_p * e + sustainable_power
|
||||
|
||||
The proportional term is proportional to the difference between the
|
||||
desired temperature and the current one. When the current temperature
|
||||
is the desired one, then the proportional component is zero and
|
||||
`P_max` = `sustainable_power`. That is, the system should operate in
|
||||
thermal equilibrium under constant load. `sustainable_power` is only
|
||||
an estimate, which is the reason for closed-loop control such as this.
|
||||
|
||||
Expanding `k_pu` we get:
|
||||
P_max = 2 * sustainable_power * (T_set - T) / (T_set - T_on) +
|
||||
sustainable_power
|
||||
|
||||
where
|
||||
T_set is the desired temperature
|
||||
T is the current temperature
|
||||
T_on is the switch on temperature
|
||||
|
||||
When the current temperature is the switch_on temperature, the above
|
||||
formula becomes:
|
||||
|
||||
P_max = 2 * sustainable_power * (T_set - T_on) / (T_set - T_on) +
|
||||
sustainable_power = 2 * sustainable_power + sustainable_power =
|
||||
3 * sustainable_power
|
||||
|
||||
Therefore, the proportional term alone linearly decreases power from
|
||||
3 * `sustainable_power` to `sustainable_power` as the temperature
|
||||
rises from the switch on temperature to the desired temperature.
|
||||
|
||||
k_i and integral_cutoff
|
||||
-----------------------
|
||||
|
||||
`k_i` configures the PID loop's integral term constant. This term
|
||||
allows the PID controller to compensate for long term drift and for
|
||||
the quantized nature of the output control: cooling devices can't set
|
||||
the exact power that the governor requests. When the temperature
|
||||
error is below `integral_cutoff`, errors are accumulated in the
|
||||
integral term. This term is then multiplied by `k_i` and the result
|
||||
added to the output of the controller. Typically `k_i` is set low (1
|
||||
or 2) and `integral_cutoff` is 0.
|
||||
|
||||
k_d
|
||||
---
|
||||
|
||||
`k_d` configures the PID loop's derivative term constant. It's
|
||||
recommended to leave it as the default: 0.
|
||||
|
||||
Cooling device power API
|
||||
========================
|
||||
|
||||
Cooling devices controlled by this governor must supply the additional
|
||||
"power" API in their `cooling_device_ops`. It consists on three ops:
|
||||
|
||||
1. int get_requested_power(struct thermal_cooling_device *cdev,
|
||||
struct thermal_zone_device *tz, u32 *power);
|
||||
@cdev: The `struct thermal_cooling_device` pointer
|
||||
@tz: thermal zone in which we are currently operating
|
||||
@power: pointer in which to store the calculated power
|
||||
|
||||
`get_requested_power()` calculates the power requested by the device
|
||||
in milliwatts and stores it in @power . It should return 0 on
|
||||
success, -E* on failure. This is currently used by the power
|
||||
allocator governor to calculate how much power to give to each cooling
|
||||
device.
|
||||
|
||||
2. int state2power(struct thermal_cooling_device *cdev, struct
|
||||
thermal_zone_device *tz, unsigned long state, u32 *power);
|
||||
@cdev: The `struct thermal_cooling_device` pointer
|
||||
@tz: thermal zone in which we are currently operating
|
||||
@state: A cooling device state
|
||||
@power: pointer in which to store the equivalent power
|
||||
|
||||
Convert cooling device state @state into power consumption in
|
||||
milliwatts and store it in @power. It should return 0 on success, -E*
|
||||
on failure. This is currently used by thermal core to calculate the
|
||||
maximum power that an actor can consume.
|
||||
|
||||
3. int power2state(struct thermal_cooling_device *cdev, u32 power,
|
||||
unsigned long *state);
|
||||
@cdev: The `struct thermal_cooling_device` pointer
|
||||
@power: power in milliwatts
|
||||
@state: pointer in which to store the resulting state
|
||||
|
||||
Calculate a cooling device state that would make the device consume at
|
||||
most @power mW and store it in @state. It should return 0 on success,
|
||||
-E* on failure. This is currently used by the thermal core to convert
|
||||
a given power set by the power allocator governor to a state that the
|
||||
cooling device can set. It is a function because this conversion may
|
||||
depend on external factors that may change so this function should the
|
||||
best conversion given "current circumstances".
|
||||
|
||||
Cooling device weights
|
||||
----------------------
|
||||
|
||||
Weights are a mechanism to bias the allocation among cooling
|
||||
devices. They express the relative power efficiency of different
|
||||
cooling devices. Higher weight can be used to express higher power
|
||||
efficiency. Weighting is relative such that if each cooling device
|
||||
has a weight of one they are considered equal. This is particularly
|
||||
useful in heterogeneous systems where two cooling devices may perform
|
||||
the same kind of compute, but with different efficiency. For example,
|
||||
a system with two different types of processors.
|
||||
|
||||
If the thermal zone is registered using
|
||||
`thermal_zone_device_register()` (i.e., platform code), then weights
|
||||
are passed as part of the thermal zone's `thermal_bind_parameters`.
|
||||
If the platform is registered using device tree, then they are passed
|
||||
as the `contribution` property of each map in the `cooling-maps` node.
|
||||
|
||||
Limitations of the power allocator governor
|
||||
===========================================
|
||||
|
||||
The power allocator governor's PID controller works best if there is a
|
||||
periodic tick. If you have a driver that calls
|
||||
`thermal_zone_device_update()` (or anything that ends up calling the
|
||||
governor's `throttle()` function) repetitively, the governor response
|
||||
won't be very good. Note that this is not particular to this
|
||||
governor, step-wise will also misbehave if you call its throttle()
|
||||
faster than the normal thermal framework tick (due to interrupts for
|
||||
example) as it will overreact.
|
||||
@@ -95,7 +95,7 @@ temperature) and throttle appropriate devices.
|
||||
1.3 interface for binding a thermal zone device with a thermal cooling device
|
||||
1.3.1 int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz,
|
||||
int trip, struct thermal_cooling_device *cdev,
|
||||
unsigned long upper, unsigned long lower);
|
||||
unsigned long upper, unsigned long lower, unsigned int weight);
|
||||
|
||||
This interface function bind a thermal cooling device to the certain trip
|
||||
point of a thermal zone device.
|
||||
@@ -110,6 +110,8 @@ temperature) and throttle appropriate devices.
|
||||
lower:the Minimum cooling state can be used for this trip point.
|
||||
THERMAL_NO_LIMIT means no lower limit,
|
||||
and the cooling device can be in cooling state 0.
|
||||
weight: the influence of this cooling device in this thermal
|
||||
zone. See 1.4.1 below for more information.
|
||||
|
||||
1.3.2 int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz,
|
||||
int trip, struct thermal_cooling_device *cdev);
|
||||
@@ -127,9 +129,15 @@ temperature) and throttle appropriate devices.
|
||||
This structure defines the following parameters that are used to bind
|
||||
a zone with a cooling device for a particular trip point.
|
||||
.cdev: The cooling device pointer
|
||||
.weight: The 'influence' of a particular cooling device on this zone.
|
||||
This is on a percentage scale. The sum of all these weights
|
||||
(for a particular zone) cannot exceed 100.
|
||||
.weight: The 'influence' of a particular cooling device on this
|
||||
zone. This is relative to the rest of the cooling
|
||||
devices. For example, if all cooling devices have a
|
||||
weight of 1, then they all contribute the same. You can
|
||||
use percentages if you want, but it's not mandatory. A
|
||||
weight of 0 means that this cooling device doesn't
|
||||
contribute to the cooling of this zone unless all cooling
|
||||
devices have a weight of 0. If all weights are 0, then
|
||||
they all contribute the same.
|
||||
.trip_mask:This is a bit mask that gives the binding relation between
|
||||
this thermal zone and cdev, for a particular trip point.
|
||||
If nth bit is set, then the cdev and thermal zone are bound
|
||||
@@ -176,6 +184,14 @@ Thermal zone device sys I/F, created once it's registered:
|
||||
|---trip_point_[0-*]_type: Trip point type
|
||||
|---trip_point_[0-*]_hyst: Hysteresis value for this trip point
|
||||
|---emul_temp: Emulated temperature set node
|
||||
|---sustainable_power: Sustainable dissipatable power
|
||||
|---k_po: Proportional term during temperature overshoot
|
||||
|---k_pu: Proportional term during temperature undershoot
|
||||
|---k_i: PID's integral term in the power allocator gov
|
||||
|---k_d: PID's derivative term in the power allocator
|
||||
|---integral_cutoff: Offset above which errors are accumulated
|
||||
|---slope: Slope constant applied as linear extrapolation
|
||||
|---offset: Offset constant applied as linear extrapolation
|
||||
|
||||
Thermal cooling device sys I/F, created once it's registered:
|
||||
/sys/class/thermal/cooling_device[0-*]:
|
||||
@@ -192,6 +208,8 @@ thermal_zone_bind_cooling_device/thermal_zone_unbind_cooling_device.
|
||||
/sys/class/thermal/thermal_zone[0-*]:
|
||||
|---cdev[0-*]: [0-*]th cooling device in current thermal zone
|
||||
|---cdev[0-*]_trip_point: Trip point that cdev[0-*] is associated with
|
||||
|---cdev[0-*]_weight: Influence of the cooling device in
|
||||
this thermal zone
|
||||
|
||||
Besides the thermal zone device sysfs I/F and cooling device sysfs I/F,
|
||||
the generic thermal driver also creates a hwmon sysfs I/F for each _type_
|
||||
@@ -265,6 +283,14 @@ cdev[0-*]_trip_point
|
||||
point.
|
||||
RO, Optional
|
||||
|
||||
cdev[0-*]_weight
|
||||
The influence of cdev[0-*] in this thermal zone. This value
|
||||
is relative to the rest of cooling devices in the thermal
|
||||
zone. For example, if a cooling device has a weight double
|
||||
than that of other, it's twice as effective in cooling the
|
||||
thermal zone.
|
||||
RW, Optional
|
||||
|
||||
passive
|
||||
Attribute is only present for zones in which the passive cooling
|
||||
policy is not supported by native thermal driver. Default is zero
|
||||
@@ -289,6 +315,66 @@ emul_temp
|
||||
because userland can easily disable the thermal policy by simply
|
||||
flooding this sysfs node with low temperature values.
|
||||
|
||||
sustainable_power
|
||||
An estimate of the sustained power that can be dissipated by
|
||||
the thermal zone. Used by the power allocator governor. For
|
||||
more information see Documentation/thermal/power_allocator.txt
|
||||
Unit: milliwatts
|
||||
RW, Optional
|
||||
|
||||
k_po
|
||||
The proportional term of the power allocator governor's PID
|
||||
controller during temperature overshoot. Temperature overshoot
|
||||
is when the current temperature is above the "desired
|
||||
temperature" trip point. For more information see
|
||||
Documentation/thermal/power_allocator.txt
|
||||
RW, Optional
|
||||
|
||||
k_pu
|
||||
The proportional term of the power allocator governor's PID
|
||||
controller during temperature undershoot. Temperature undershoot
|
||||
is when the current temperature is below the "desired
|
||||
temperature" trip point. For more information see
|
||||
Documentation/thermal/power_allocator.txt
|
||||
RW, Optional
|
||||
|
||||
k_i
|
||||
The integral term of the power allocator governor's PID
|
||||
controller. This term allows the PID controller to compensate
|
||||
for long term drift. For more information see
|
||||
Documentation/thermal/power_allocator.txt
|
||||
RW, Optional
|
||||
|
||||
k_d
|
||||
The derivative term of the power allocator governor's PID
|
||||
controller. For more information see
|
||||
Documentation/thermal/power_allocator.txt
|
||||
RW, Optional
|
||||
|
||||
integral_cutoff
|
||||
Temperature offset from the desired temperature trip point
|
||||
above which the integral term of the power allocator
|
||||
governor's PID controller starts accumulating errors. For
|
||||
example, if integral_cutoff is 0, then the integral term only
|
||||
accumulates error when temperature is above the desired
|
||||
temperature trip point. For more information see
|
||||
Documentation/thermal/power_allocator.txt
|
||||
RW, Optional
|
||||
|
||||
slope
|
||||
The slope constant used in a linear extrapolation model
|
||||
to determine a hotspot temperature based off the sensor's
|
||||
raw readings. It is up to the device driver to determine
|
||||
the usage of these values.
|
||||
RW, Optional
|
||||
|
||||
offset
|
||||
The offset constant used in a linear extrapolation model
|
||||
to determine a hotspot temperature based off the sensor's
|
||||
raw readings. It is up to the device driver to determine
|
||||
the usage of these values.
|
||||
RW, Optional
|
||||
|
||||
*****************************
|
||||
* Cooling device attributes *
|
||||
*****************************
|
||||
@@ -318,7 +404,8 @@ passive, active. If an ACPI thermal zone supports critical, passive,
|
||||
active[0] and active[1] at the same time, it may register itself as a
|
||||
thermal_zone_device (thermal_zone1) with 4 trip points in all.
|
||||
It has one processor and one fan, which are both registered as
|
||||
thermal_cooling_device.
|
||||
thermal_cooling_device. Both are considered to have the same
|
||||
effectiveness in cooling the thermal zone.
|
||||
|
||||
If the processor is listed in _PSL method, and the fan is listed in _AL0
|
||||
method, the sys I/F structure will be built like this:
|
||||
@@ -340,8 +427,10 @@ method, the sys I/F structure will be built like this:
|
||||
|---trip_point_3_type: active1
|
||||
|---cdev0: --->/sys/class/thermal/cooling_device0
|
||||
|---cdev0_trip_point: 1 /* cdev0 can be used for passive */
|
||||
|---cdev0_weight: 1024
|
||||
|---cdev1: --->/sys/class/thermal/cooling_device3
|
||||
|---cdev1_trip_point: 2 /* cdev1 can be used for active[0]*/
|
||||
|---cdev1_weight: 1024
|
||||
|
||||
|cooling_device0:
|
||||
|---type: Processor
|
||||
|
||||
@@ -800,7 +800,8 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal,
|
||||
result =
|
||||
thermal_zone_bind_cooling_device
|
||||
(thermal, trip, cdev,
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
|
||||
THERMAL_WEIGHT_DEFAULT);
|
||||
else
|
||||
result =
|
||||
thermal_zone_unbind_cooling_device
|
||||
@@ -824,7 +825,8 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal,
|
||||
if (bind)
|
||||
result = thermal_zone_bind_cooling_device
|
||||
(thermal, trip, cdev,
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT);
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
|
||||
THERMAL_WEIGHT_DEFAULT);
|
||||
else
|
||||
result = thermal_zone_unbind_cooling_device
|
||||
(thermal, trip, cdev);
|
||||
@@ -841,7 +843,8 @@ static int acpi_thermal_cooling_device_cb(struct thermal_zone_device *thermal,
|
||||
result = thermal_zone_bind_cooling_device
|
||||
(thermal, THERMAL_TRIPS_NONE,
|
||||
cdev, THERMAL_NO_LIMIT,
|
||||
THERMAL_NO_LIMIT);
|
||||
THERMAL_NO_LIMIT,
|
||||
THERMAL_WEIGHT_DEFAULT);
|
||||
else
|
||||
result = thermal_zone_unbind_cooling_device
|
||||
(thermal, THERMAL_TRIPS_NONE,
|
||||
|
||||
@@ -372,7 +372,8 @@ static int acerhdf_bind(struct thermal_zone_device *thermal,
|
||||
return 0;
|
||||
|
||||
if (thermal_zone_bind_cooling_device(thermal, 0, cdev,
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT)) {
|
||||
THERMAL_NO_LIMIT, THERMAL_NO_LIMIT,
|
||||
THERMAL_WEIGHT_DEFAULT)) {
|
||||
pr_err("error binding cooling dev\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,17 @@ config THERMAL_OF
|
||||
Say 'Y' here if you need to build thermal infrastructure
|
||||
based on device tree.
|
||||
|
||||
config THERMAL_WRITABLE_TRIPS
|
||||
bool "Enable writable trip points"
|
||||
help
|
||||
This option allows the system integrator to choose whether
|
||||
trip temperatures can be changed from userspace. The
|
||||
writable trips need to be specified when setting up the
|
||||
thermal zone but the choice here takes precedence.
|
||||
|
||||
Say 'Y' here if you would like to allow userspace tools to
|
||||
change trip temperatures.
|
||||
|
||||
choice
|
||||
prompt "Default Thermal governor"
|
||||
default THERMAL_DEFAULT_GOV_STEP_WISE
|
||||
@@ -71,6 +82,14 @@ config THERMAL_DEFAULT_GOV_USER_SPACE
|
||||
Select this if you want to let the user space manage the
|
||||
platform thermals.
|
||||
|
||||
config THERMAL_DEFAULT_GOV_POWER_ALLOCATOR
|
||||
bool "power_allocator"
|
||||
select THERMAL_GOV_POWER_ALLOCATOR
|
||||
help
|
||||
Select this if you want to control temperature based on
|
||||
system and device power allocation. This governor can only
|
||||
operate on cooling devices that implement the power API.
|
||||
|
||||
endchoice
|
||||
|
||||
config THERMAL_GOV_FAIR_SHARE
|
||||
@@ -99,6 +118,12 @@ config THERMAL_GOV_USER_SPACE
|
||||
help
|
||||
Enable this to let the user space manage the platform thermals.
|
||||
|
||||
config THERMAL_GOV_POWER_ALLOCATOR
|
||||
bool "Power allocator thermal governor"
|
||||
help
|
||||
Enable this to manage platform thermals by dynamically
|
||||
allocating and limiting power to devices.
|
||||
|
||||
config CPU_THERMAL
|
||||
bool "generic cpu cooling support"
|
||||
depends on CPU_FREQ
|
||||
@@ -136,6 +161,14 @@ config THERMAL_EMULATION
|
||||
because userland can easily disable the thermal policy by simply
|
||||
flooding this sysfs node with low temperature values.
|
||||
|
||||
config HISI_THERMAL
|
||||
tristate "Hisilicon thermal driver"
|
||||
depends on ARCH_HISI && CPU_THERMAL && OF
|
||||
help
|
||||
Enable this to plug hisilicon's thermal sensor driver into the Linux
|
||||
thermal framework. cpufreq is used as the cooling device to throttle
|
||||
CPUs when the passive trip is crossed.
|
||||
|
||||
config IMX_THERMAL
|
||||
tristate "Temperature sensor driver for Freescale i.MX SoCs"
|
||||
depends on CPU_THERMAL
|
||||
@@ -299,4 +332,15 @@ depends on ARCH_STI && OF
|
||||
source "drivers/thermal/st/Kconfig"
|
||||
endmenu
|
||||
|
||||
config QCOM_SPMI_TEMP_ALARM
|
||||
tristate "Qualcomm SPMI PMIC Temperature Alarm"
|
||||
depends on OF && SPMI && IIO
|
||||
select REGMAP_SPMI
|
||||
help
|
||||
This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP)
|
||||
PMIC devices. It shows up in sysfs as a thermal sensor with multiple
|
||||
trip points. The temperature reported by the thermal sensor reflects the
|
||||
real time die temperature if an ADC is present or an estimate of the
|
||||
temperature based upon the over temperature stage value.
|
||||
|
||||
endif
|
||||
|
||||
@@ -14,6 +14,7 @@ thermal_sys-$(CONFIG_THERMAL_GOV_FAIR_SHARE) += fair_share.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_BANG_BANG) += gov_bang_bang.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_STEP_WISE) += step_wise.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o
|
||||
thermal_sys-$(CONFIG_THERMAL_GOV_POWER_ALLOCATOR) += power_allocator.o
|
||||
|
||||
# cpufreq cooling
|
||||
thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
|
||||
@@ -22,6 +23,7 @@ thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
|
||||
thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o
|
||||
|
||||
# platform thermal drivers
|
||||
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
|
||||
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
|
||||
obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o
|
||||
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
|
||||
@@ -39,3 +41,4 @@ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
|
||||
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
|
||||
obj-$(CONFIG_ST_THERMAL) += st/
|
||||
obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o
|
||||
obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o
|
||||
|
||||
+569
-18
File diff suppressed because it is too large
Load Diff
@@ -76,7 +76,7 @@ static int db8500_cdev_bind(struct thermal_zone_device *thermal,
|
||||
upper = lower = i > max_state ? max_state : i;
|
||||
|
||||
ret = thermal_zone_bind_cooling_device(thermal, i, cdev,
|
||||
upper, lower);
|
||||
upper, lower, THERMAL_WEIGHT_DEFAULT);
|
||||
|
||||
dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type,
|
||||
i, ret, ret ? "fail" : "succeed");
|
||||
|
||||
@@ -59,17 +59,17 @@ static int get_trip_level(struct thermal_zone_device *tz)
|
||||
}
|
||||
|
||||
static long get_target_state(struct thermal_zone_device *tz,
|
||||
struct thermal_cooling_device *cdev, int weight, int level)
|
||||
struct thermal_cooling_device *cdev, int percentage, int level)
|
||||
{
|
||||
unsigned long max_state;
|
||||
|
||||
cdev->ops->get_max_state(cdev, &max_state);
|
||||
|
||||
return (long)(weight * level * max_state) / (100 * tz->trips);
|
||||
return (long)(percentage * level * max_state) / (100 * tz->trips);
|
||||
}
|
||||
|
||||
/**
|
||||
* fair_share_throttle - throttles devices asscciated with the given zone
|
||||
* fair_share_throttle - throttles devices associated with the given zone
|
||||
* @tz - thermal_zone_device
|
||||
*
|
||||
* Throttling Logic: This uses three parameters to calculate the new
|
||||
@@ -77,7 +77,7 @@ static long get_target_state(struct thermal_zone_device *tz,
|
||||
*
|
||||
* Parameters used for Throttling:
|
||||
* P1. max_state: Maximum throttle state exposed by the cooling device.
|
||||
* P2. weight[i]/100:
|
||||
* P2. percentage[i]/100:
|
||||
* How 'effective' the 'i'th device is, in cooling the given zone.
|
||||
* P3. cur_trip_level/max_no_of_trips:
|
||||
* This describes the extent to which the devices should be throttled.
|
||||
@@ -88,28 +88,33 @@ static long get_target_state(struct thermal_zone_device *tz,
|
||||
*/
|
||||
static int fair_share_throttle(struct thermal_zone_device *tz, int trip)
|
||||
{
|
||||
const struct thermal_zone_params *tzp;
|
||||
struct thermal_cooling_device *cdev;
|
||||
struct thermal_instance *instance;
|
||||
int i;
|
||||
int total_weight = 0;
|
||||
int total_instance = 0;
|
||||
int cur_trip_level = get_trip_level(tz);
|
||||
|
||||
if (!tz->tzp || !tz->tzp->tbp)
|
||||
return -EINVAL;
|
||||
|
||||
tzp = tz->tzp;
|
||||
|
||||
for (i = 0; i < tzp->num_tbps; i++) {
|
||||
if (!tzp->tbp[i].cdev)
|
||||
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
|
||||
if (instance->trip != trip)
|
||||
continue;
|
||||
|
||||
cdev = tzp->tbp[i].cdev;
|
||||
instance = get_thermal_instance(tz, cdev, trip);
|
||||
if (!instance)
|
||||
total_weight += instance->weight;
|
||||
total_instance++;
|
||||
}
|
||||
|
||||
list_for_each_entry(instance, &tz->thermal_instances, tz_node) {
|
||||
int percentage;
|
||||
struct thermal_cooling_device *cdev = instance->cdev;
|
||||
|
||||
if (instance->trip != trip)
|
||||
continue;
|
||||
|
||||
instance->target = get_target_state(tz, cdev,
|
||||
tzp->tbp[i].weight, cur_trip_level);
|
||||
if (!total_weight)
|
||||
percentage = 100 / total_instance;
|
||||
else
|
||||
percentage = (instance->weight * 100) / total_weight;
|
||||
|
||||
instance->target = get_target_state(tz, cdev, percentage,
|
||||
cur_trip_level);
|
||||
|
||||
instance->cdev->updated = false;
|
||||
thermal_cdev_update(cdev);
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Hisilicon thermal sensor driver
|
||||
*
|
||||
* Copyright (c) 2014-2015 Hisilicon Limited.
|
||||
* Copyright (c) 2014-2015 Linaro Limited.
|
||||
*
|
||||
* Xinwei Kong <kong.kongxinwei@hisilicon.com>
|
||||
* Leo Yan <leo.yan@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
||||
* kind, whether express or implied; without even the implied warranty
|
||||
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
|
||||
#define TEMP0_TH (0x4)
|
||||
#define TEMP0_RST_TH (0x8)
|
||||
#define TEMP0_CFG (0xC)
|
||||
#define TEMP0_EN (0x10)
|
||||
#define TEMP0_INT_EN (0x14)
|
||||
#define TEMP0_INT_CLR (0x18)
|
||||
#define TEMP0_RST_MSK (0x1C)
|
||||
#define TEMP0_VALUE (0x28)
|
||||
|
||||
#define HISI_TEMP_BASE (-60)
|
||||
#define HISI_TEMP_RESET (100000)
|
||||
|
||||
#define HISI_MAX_SENSORS 4
|
||||
|
||||
struct hisi_thermal_sensor {
|
||||
struct hisi_thermal_data *thermal;
|
||||
struct thermal_zone_device *tzd;
|
||||
|
||||
long sensor_temp;
|
||||
uint32_t id;
|
||||
uint32_t thres_temp;
|
||||
};
|
||||
|
||||
struct hisi_thermal_data {
|
||||
struct mutex thermal_lock; /* protects register data */
|
||||
struct platform_device *pdev;
|
||||
struct clk *clk;
|
||||
struct hisi_thermal_sensor sensors[HISI_MAX_SENSORS];
|
||||
|
||||
int irq, irq_bind_sensor;
|
||||
bool irq_enabled;
|
||||
|
||||
void __iomem *regs;
|
||||
};
|
||||
|
||||
/* in millicelsius */
|
||||
static inline int _step_to_temp(int step)
|
||||
{
|
||||
/*
|
||||
* Every step equals (1 * 200) / 255 celsius, and finally
|
||||
* need convert to millicelsius.
|
||||
*/
|
||||
return (HISI_TEMP_BASE + (step * 200 / 255)) * 1000;
|
||||
}
|
||||
|
||||
static inline long _temp_to_step(long temp)
|
||||
{
|
||||
return ((temp / 1000 - HISI_TEMP_BASE) * 255 / 200);
|
||||
}
|
||||
|
||||
static long hisi_thermal_get_sensor_temp(struct hisi_thermal_data *data,
|
||||
struct hisi_thermal_sensor *sensor)
|
||||
{
|
||||
long val;
|
||||
|
||||
mutex_lock(&data->thermal_lock);
|
||||
|
||||
/* disable interrupt */
|
||||
writel(0x0, data->regs + TEMP0_INT_EN);
|
||||
writel(0x1, data->regs + TEMP0_INT_CLR);
|
||||
|
||||
/* disable module firstly */
|
||||
writel(0x0, data->regs + TEMP0_EN);
|
||||
|
||||
/* select sensor id */
|
||||
writel((sensor->id << 12), data->regs + TEMP0_CFG);
|
||||
|
||||
/* enable module */
|
||||
writel(0x1, data->regs + TEMP0_EN);
|
||||
|
||||
usleep_range(3000, 5000);
|
||||
|
||||
val = readl(data->regs + TEMP0_VALUE);
|
||||
val = _step_to_temp(val);
|
||||
|
||||
mutex_unlock(&data->thermal_lock);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void hisi_thermal_enable_bind_irq_sensor
|
||||
(struct hisi_thermal_data *data)
|
||||
{
|
||||
struct hisi_thermal_sensor *sensor;
|
||||
|
||||
mutex_lock(&data->thermal_lock);
|
||||
|
||||
sensor = &data->sensors[data->irq_bind_sensor];
|
||||
|
||||
/* setting the hdak time */
|
||||
writel(0x0, data->regs + TEMP0_CFG);
|
||||
|
||||
/* disable module firstly */
|
||||
writel(0x0, data->regs + TEMP0_RST_MSK);
|
||||
writel(0x0, data->regs + TEMP0_EN);
|
||||
|
||||
/* select sensor id */
|
||||
writel((sensor->id << 12), data->regs + TEMP0_CFG);
|
||||
|
||||
/* enable for interrupt */
|
||||
writel(_temp_to_step(sensor->thres_temp) | 0x0FFFFFF00,
|
||||
data->regs + TEMP0_TH);
|
||||
|
||||
writel(_temp_to_step(HISI_TEMP_RESET), data->regs + TEMP0_RST_TH);
|
||||
|
||||
/* enable module */
|
||||
writel(0x1, data->regs + TEMP0_RST_MSK);
|
||||
writel(0x1, data->regs + TEMP0_EN);
|
||||
|
||||
writel(0x0, data->regs + TEMP0_INT_CLR);
|
||||
writel(0x1, data->regs + TEMP0_INT_EN);
|
||||
|
||||
usleep_range(3000, 5000);
|
||||
|
||||
mutex_unlock(&data->thermal_lock);
|
||||
}
|
||||
|
||||
static void hisi_thermal_disable_sensor(struct hisi_thermal_data *data)
|
||||
{
|
||||
mutex_lock(&data->thermal_lock);
|
||||
|
||||
/* disable sensor module */
|
||||
writel(0x0, data->regs + TEMP0_INT_EN);
|
||||
writel(0x0, data->regs + TEMP0_RST_MSK);
|
||||
writel(0x0, data->regs + TEMP0_EN);
|
||||
|
||||
mutex_unlock(&data->thermal_lock);
|
||||
}
|
||||
|
||||
static int hisi_thermal_get_temp(void *_sensor, long *temp)
|
||||
{
|
||||
struct hisi_thermal_sensor *sensor = _sensor;
|
||||
struct hisi_thermal_data *data = sensor->thermal;
|
||||
|
||||
int sensor_id = 0, i;
|
||||
long max_temp = 0;
|
||||
|
||||
*temp = hisi_thermal_get_sensor_temp(data, sensor);
|
||||
|
||||
sensor->sensor_temp = *temp;
|
||||
|
||||
for (i = 0; i < HISI_MAX_SENSORS; i++) {
|
||||
if (data->sensors[i].sensor_temp >= max_temp) {
|
||||
max_temp = data->sensors[i].sensor_temp;
|
||||
sensor_id = i;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&data->thermal_lock);
|
||||
data->irq_bind_sensor = sensor_id;
|
||||
mutex_unlock(&data->thermal_lock);
|
||||
|
||||
dev_dbg(&data->pdev->dev, "id=%d, irq=%d, temp=%ld, thres=%d\n",
|
||||
sensor->id, data->irq_enabled, *temp, sensor->thres_temp);
|
||||
/*
|
||||
* Bind irq to sensor for two cases:
|
||||
* Reenable alarm IRQ if temperature below threshold;
|
||||
* if irq has been enabled, always set it;
|
||||
*/
|
||||
if (data->irq_enabled) {
|
||||
hisi_thermal_enable_bind_irq_sensor(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (max_temp < sensor->thres_temp) {
|
||||
data->irq_enabled = true;
|
||||
hisi_thermal_enable_bind_irq_sensor(data);
|
||||
enable_irq(data->irq);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct thermal_zone_of_device_ops hisi_of_thermal_ops = {
|
||||
.get_temp = hisi_thermal_get_temp,
|
||||
};
|
||||
|
||||
static irqreturn_t hisi_thermal_alarm_irq(int irq, void *dev)
|
||||
{
|
||||
struct hisi_thermal_data *data = dev;
|
||||
|
||||
disable_irq_nosync(irq);
|
||||
data->irq_enabled = false;
|
||||
|
||||
return IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev)
|
||||
{
|
||||
struct hisi_thermal_data *data = dev;
|
||||
struct hisi_thermal_sensor *sensor;
|
||||
int i;
|
||||
|
||||
mutex_lock(&data->thermal_lock);
|
||||
sensor = &data->sensors[data->irq_bind_sensor];
|
||||
|
||||
dev_crit(&data->pdev->dev, "THERMAL ALARM: T > %d\n",
|
||||
sensor->thres_temp / 1000);
|
||||
mutex_unlock(&data->thermal_lock);
|
||||
|
||||
for (i = 0; i < HISI_MAX_SENSORS; i++)
|
||||
thermal_zone_device_update(data->sensors[i].tzd);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int hisi_thermal_register_sensor(struct platform_device *pdev,
|
||||
struct hisi_thermal_data *data,
|
||||
struct hisi_thermal_sensor *sensor,
|
||||
int index)
|
||||
{
|
||||
int ret, i;
|
||||
const struct thermal_trip *trip;
|
||||
|
||||
sensor->id = index;
|
||||
sensor->thermal = data;
|
||||
|
||||
sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, sensor->id,
|
||||
sensor, &hisi_of_thermal_ops);
|
||||
if (IS_ERR(sensor->tzd)) {
|
||||
ret = PTR_ERR(sensor->tzd);
|
||||
dev_err(&pdev->dev, "failed to register sensor id %d: %d\n",
|
||||
sensor->id, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
trip = of_thermal_get_trip_points(sensor->tzd);
|
||||
|
||||
for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) {
|
||||
if (trip[i].type == THERMAL_TRIP_PASSIVE) {
|
||||
sensor->thres_temp = trip[i].temperature;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_hisi_thermal_match[] = {
|
||||
{ .compatible = "hisilicon,tsensor" },
|
||||
{ /* end */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_hisi_thermal_match);
|
||||
|
||||
static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor,
|
||||
bool on)
|
||||
{
|
||||
struct thermal_zone_device *tzd = sensor->tzd;
|
||||
|
||||
tzd->ops->set_mode(tzd,
|
||||
on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED);
|
||||
}
|
||||
|
||||
static int hisi_thermal_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_thermal_data *data;
|
||||
struct resource *res;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&data->thermal_lock);
|
||||
data->pdev = pdev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(data->regs)) {
|
||||
dev_err(&pdev->dev, "failed to get io address\n");
|
||||
return PTR_ERR(data->regs);
|
||||
}
|
||||
|
||||
data->irq = platform_get_irq(pdev, 0);
|
||||
if (data->irq < 0)
|
||||
return data->irq;
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, data->irq,
|
||||
hisi_thermal_alarm_irq,
|
||||
hisi_thermal_alarm_irq_thread,
|
||||
0, "hisi_thermal", data);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
data->clk = devm_clk_get(&pdev->dev, "thermal_clk");
|
||||
if (IS_ERR(data->clk)) {
|
||||
ret = PTR_ERR(data->clk);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(&pdev->dev,
|
||||
"failed to get thermal clk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* enable clock for thermal */
|
||||
ret = clk_prepare_enable(data->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < HISI_MAX_SENSORS; ++i) {
|
||||
ret = hisi_thermal_register_sensor(pdev, data,
|
||||
&data->sensors[i], i);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed to register thermal sensor: %d\n", ret);
|
||||
goto err_get_sensor_data;
|
||||
}
|
||||
}
|
||||
|
||||
hisi_thermal_enable_bind_irq_sensor(data);
|
||||
data->irq_enabled = true;
|
||||
|
||||
for (i = 0; i < HISI_MAX_SENSORS; i++)
|
||||
hisi_thermal_toggle_sensor(&data->sensors[i], true);
|
||||
|
||||
return 0;
|
||||
|
||||
err_get_sensor_data:
|
||||
clk_disable_unprepare(data->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hisi_thermal_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hisi_thermal_data *data = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < HISI_MAX_SENSORS; i++) {
|
||||
struct hisi_thermal_sensor *sensor = &data->sensors[i];
|
||||
|
||||
hisi_thermal_toggle_sensor(sensor, false);
|
||||
thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd);
|
||||
}
|
||||
|
||||
hisi_thermal_disable_sensor(data);
|
||||
clk_disable_unprepare(data->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int hisi_thermal_suspend(struct device *dev)
|
||||
{
|
||||
struct hisi_thermal_data *data = dev_get_drvdata(dev);
|
||||
|
||||
hisi_thermal_disable_sensor(data);
|
||||
data->irq_enabled = false;
|
||||
|
||||
clk_disable_unprepare(data->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hisi_thermal_resume(struct device *dev)
|
||||
{
|
||||
struct hisi_thermal_data *data = dev_get_drvdata(dev);
|
||||
|
||||
clk_prepare_enable(data->clk);
|
||||
|
||||
data->irq_enabled = true;
|
||||
hisi_thermal_enable_bind_irq_sensor(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops,
|
||||
hisi_thermal_suspend, hisi_thermal_resume);
|
||||
|
||||
static struct platform_driver hisi_thermal_driver = {
|
||||
.driver = {
|
||||
.name = "hisi_thermal",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &hisi_thermal_pm_ops,
|
||||
.of_match_table = of_hisi_thermal_match,
|
||||
},
|
||||
.probe = hisi_thermal_probe,
|
||||
.remove = hisi_thermal_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(hisi_thermal_driver);
|
||||
|
||||
MODULE_AUTHOR("Xinwei Kong <kong.kongxinwei@hisilicon.com>");
|
||||
MODULE_AUTHOR("Leo Yan <leo.yan@linaro.org>");
|
||||
MODULE_DESCRIPTION("Hisilicon thermal driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -306,7 +306,8 @@ static int imx_bind(struct thermal_zone_device *tz,
|
||||
|
||||
ret = thermal_zone_bind_cooling_device(tz, IMX_TRIP_PASSIVE, cdev,
|
||||
THERMAL_NO_LIMIT,
|
||||
THERMAL_NO_LIMIT);
|
||||
THERMAL_NO_LIMIT,
|
||||
THERMAL_WEIGHT_DEFAULT);
|
||||
if (ret) {
|
||||
dev_err(&tz->device,
|
||||
"binding zone %s with cdev %s failed:%d\n",
|
||||
|
||||
@@ -58,6 +58,8 @@ struct __thermal_bind_params {
|
||||
* @mode: current thermal zone device mode (enabled/disabled)
|
||||
* @passive_delay: polling interval while passive cooling is activated
|
||||
* @polling_delay: zone polling interval
|
||||
* @slope: slope of the temperature adjustment curve
|
||||
* @offset: offset of the temperature adjustment curve
|
||||
* @ntrips: number of trip points
|
||||
* @trips: an array of trip points (0..ntrips - 1)
|
||||
* @num_tbps: number of thermal bind params
|
||||
@@ -70,6 +72,8 @@ struct __thermal_zone {
|
||||
enum thermal_device_mode mode;
|
||||
int passive_delay;
|
||||
int polling_delay;
|
||||
int slope;
|
||||
int offset;
|
||||
|
||||
/* trip data */
|
||||
int ntrips;
|
||||
@@ -227,7 +231,8 @@ static int of_thermal_bind(struct thermal_zone_device *thermal,
|
||||
ret = thermal_zone_bind_cooling_device(thermal,
|
||||
tbp->trip_id, cdev,
|
||||
tbp->max,
|
||||
tbp->min);
|
||||
tbp->min,
|
||||
tbp->usage);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@@ -581,7 +586,7 @@ static int thermal_of_populate_bind_params(struct device_node *np,
|
||||
u32 prop;
|
||||
|
||||
/* Default weight. Usage is optional */
|
||||
__tbp->usage = 0;
|
||||
__tbp->usage = THERMAL_WEIGHT_DEFAULT;
|
||||
ret = of_property_read_u32(np, "contribution", &prop);
|
||||
if (ret == 0)
|
||||
__tbp->usage = prop;
|
||||
@@ -715,7 +720,7 @@ static int thermal_of_populate_trip(struct device_node *np,
|
||||
* @np parameter and fills the read data into a __thermal_zone data structure
|
||||
* and return this pointer.
|
||||
*
|
||||
* TODO: Missing properties to parse: thermal-sensor-names and coefficients
|
||||
* TODO: Missing properties to parse: thermal-sensor-names
|
||||
*
|
||||
* Return: On success returns a valid struct __thermal_zone,
|
||||
* otherwise, it returns a corresponding ERR_PTR(). Caller must
|
||||
@@ -727,7 +732,7 @@ thermal_of_build_thermal_zone(struct device_node *np)
|
||||
struct device_node *child = NULL, *gchild;
|
||||
struct __thermal_zone *tz;
|
||||
int ret, i;
|
||||
u32 prop;
|
||||
u32 prop, coef[2];
|
||||
|
||||
if (!np) {
|
||||
pr_err("no thermal zone np\n");
|
||||
@@ -752,6 +757,20 @@ thermal_of_build_thermal_zone(struct device_node *np)
|
||||
}
|
||||
tz->polling_delay = prop;
|
||||
|
||||
/*
|
||||
* REVIST: for now, the thermal framework supports only
|
||||
* one sensor per thermal zone. Thus, we are considering
|
||||
* only the first two values as slope and offset.
|
||||
*/
|
||||
ret = of_property_read_u32_array(np, "coefficients", coef, 2);
|
||||
if (ret == 0) {
|
||||
tz->slope = coef[0];
|
||||
tz->offset = coef[1];
|
||||
} else {
|
||||
tz->slope = 1;
|
||||
tz->offset = 0;
|
||||
}
|
||||
|
||||
/* trips */
|
||||
child = of_get_child_by_name(np, "trips");
|
||||
|
||||
@@ -865,6 +884,8 @@ int __init of_parse_thermal_zones(void)
|
||||
for_each_child_of_node(np, child) {
|
||||
struct thermal_zone_device *zone;
|
||||
struct thermal_zone_params *tzp;
|
||||
int i, mask = 0;
|
||||
u32 prop;
|
||||
|
||||
/* Check whether child is enabled or not */
|
||||
if (!of_device_is_available(child))
|
||||
@@ -891,8 +912,18 @@ int __init of_parse_thermal_zones(void)
|
||||
/* No hwmon because there might be hwmon drivers registering */
|
||||
tzp->no_hwmon = true;
|
||||
|
||||
if (!of_property_read_u32(child, "sustainable-power", &prop))
|
||||
tzp->sustainable_power = prop;
|
||||
|
||||
for (i = 0; i < tz->ntrips; i++)
|
||||
mask |= 1 << i;
|
||||
|
||||
/* these two are left for temperature drivers to use */
|
||||
tzp->slope = tz->slope;
|
||||
tzp->offset = tz->offset;
|
||||
|
||||
zone = thermal_zone_device_register(child->name, tz->ntrips,
|
||||
0, tz,
|
||||
mask, tz,
|
||||
ops, tzp,
|
||||
tz->passive_delay,
|
||||
tz->polling_delay);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
* only version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/iio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/thermal.h>
|
||||
|
||||
#define QPNP_TM_REG_TYPE 0x04
|
||||
#define QPNP_TM_REG_SUBTYPE 0x05
|
||||
#define QPNP_TM_REG_STATUS 0x08
|
||||
#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40
|
||||
#define QPNP_TM_REG_ALARM_CTRL 0x46
|
||||
|
||||
#define QPNP_TM_TYPE 0x09
|
||||
#define QPNP_TM_SUBTYPE 0x08
|
||||
|
||||
#define STATUS_STAGE_MASK 0x03
|
||||
|
||||
#define SHUTDOWN_CTRL1_THRESHOLD_MASK 0x03
|
||||
|
||||
#define ALARM_CTRL_FORCE_ENABLE 0x80
|
||||
|
||||
/*
|
||||
* Trip point values based on threshold control
|
||||
* 0 = {105 C, 125 C, 145 C}
|
||||
* 1 = {110 C, 130 C, 150 C}
|
||||
* 2 = {115 C, 135 C, 155 C}
|
||||
* 3 = {120 C, 140 C, 160 C}
|
||||
*/
|
||||
#define TEMP_STAGE_STEP 20000 /* Stage step: 20.000 C */
|
||||
#define TEMP_STAGE_HYSTERESIS 2000
|
||||
|
||||
#define TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */
|
||||
#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
|
||||
|
||||
#define THRESH_MIN 0
|
||||
|
||||
/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
|
||||
#define DEFAULT_TEMP 37000
|
||||
|
||||
struct qpnp_tm_chip {
|
||||
struct regmap *map;
|
||||
struct thermal_zone_device *tz_dev;
|
||||
long temp;
|
||||
unsigned int thresh;
|
||||
unsigned int stage;
|
||||
unsigned int prev_stage;
|
||||
unsigned int base;
|
||||
struct iio_channel *adc;
|
||||
};
|
||||
|
||||
static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(chip->map, chip->base + addr, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
*data = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
|
||||
{
|
||||
return regmap_write(chip->map, chip->base + addr, data);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function updates the internal temp value based on the
|
||||
* current thermal stage and threshold as well as the previous stage
|
||||
*/
|
||||
static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
|
||||
{
|
||||
unsigned int stage;
|
||||
int ret;
|
||||
u8 reg = 0;
|
||||
|
||||
ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
stage = reg & STATUS_STAGE_MASK;
|
||||
|
||||
if (stage > chip->stage) {
|
||||
/* increasing stage, use lower bound */
|
||||
chip->temp = (stage - 1) * TEMP_STAGE_STEP +
|
||||
chip->thresh * TEMP_THRESH_STEP +
|
||||
TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
|
||||
} else if (stage < chip->stage) {
|
||||
/* decreasing stage, use upper bound */
|
||||
chip->temp = stage * TEMP_STAGE_STEP +
|
||||
chip->thresh * TEMP_THRESH_STEP -
|
||||
TEMP_STAGE_HYSTERESIS + TEMP_THRESH_MIN;
|
||||
}
|
||||
|
||||
chip->stage = stage;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qpnp_tm_get_temp(void *data, long *temp)
|
||||
{
|
||||
struct qpnp_tm_chip *chip = data;
|
||||
int ret, mili_celsius;
|
||||
|
||||
if (!temp)
|
||||
return -EINVAL;
|
||||
|
||||
if (IS_ERR(chip->adc)) {
|
||||
ret = qpnp_tm_update_temp_no_adc(chip);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
ret = iio_read_channel_processed(chip->adc, &mili_celsius);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
chip->temp = mili_celsius;
|
||||
}
|
||||
|
||||
*temp = chip->temp < 0 ? 0 : chip->temp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct thermal_zone_of_device_ops qpnp_tm_sensor_ops = {
|
||||
.get_temp = qpnp_tm_get_temp,
|
||||
};
|
||||
|
||||
static irqreturn_t qpnp_tm_isr(int irq, void *data)
|
||||
{
|
||||
struct qpnp_tm_chip *chip = data;
|
||||
|
||||
thermal_zone_device_update(chip->tz_dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function initializes the internal temp value based on only the
|
||||
* current thermal stage and threshold. Setup threshold control and
|
||||
* disable shutdown override.
|
||||
*/
|
||||
static int qpnp_tm_init(struct qpnp_tm_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
u8 reg;
|
||||
|
||||
chip->thresh = THRESH_MIN;
|
||||
chip->temp = DEFAULT_TEMP;
|
||||
|
||||
ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, ®);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
chip->stage = reg & STATUS_STAGE_MASK;
|
||||
|
||||
if (chip->stage)
|
||||
chip->temp = chip->thresh * TEMP_THRESH_STEP +
|
||||
(chip->stage - 1) * TEMP_STAGE_STEP +
|
||||
TEMP_THRESH_MIN;
|
||||
|
||||
/*
|
||||
* Set threshold and disable software override of stage 2 and 3
|
||||
* shutdowns.
|
||||
*/
|
||||
reg = chip->thresh & SHUTDOWN_CTRL1_THRESHOLD_MASK;
|
||||
ret = qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Enable the thermal alarm PMIC module in always-on mode. */
|
||||
reg = ALARM_CTRL_FORCE_ENABLE;
|
||||
ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_tm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct qpnp_tm_chip *chip;
|
||||
struct device_node *node;
|
||||
u8 type, subtype;
|
||||
u32 res[2];
|
||||
int ret, irq;
|
||||
|
||||
node = pdev->dev.of_node;
|
||||
|
||||
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, chip);
|
||||
|
||||
chip->map = dev_get_regmap(pdev->dev.parent, NULL);
|
||||
if (!chip->map)
|
||||
return -ENXIO;
|
||||
|
||||
ret = of_property_read_u32_array(node, "reg", res, 2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
/* ADC based measurements are optional */
|
||||
chip->adc = iio_channel_get(&pdev->dev, "thermal");
|
||||
if (PTR_ERR(chip->adc) == -EPROBE_DEFER)
|
||||
return PTR_ERR(chip->adc);
|
||||
|
||||
chip->base = res[0];
|
||||
|
||||
ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "could not read type\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "could not read subtype\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (type != QPNP_TM_TYPE || subtype != QPNP_TM_SUBTYPE) {
|
||||
dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
|
||||
type, subtype);
|
||||
ret = -ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = qpnp_tm_init(chip);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "init failed\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
|
||||
IRQF_ONESHOT, node->name, chip);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
chip->tz_dev = thermal_zone_of_sensor_register(&pdev->dev, 0, chip,
|
||||
&qpnp_tm_sensor_ops);
|
||||
if (IS_ERR(chip->tz_dev)) {
|
||||
dev_err(&pdev->dev, "failed to register sensor\n");
|
||||
ret = PTR_ERR(chip->tz_dev);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (!IS_ERR(chip->adc))
|
||||
iio_channel_release(chip->adc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int qpnp_tm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct qpnp_tm_chip *chip = dev_get_drvdata(&pdev->dev);
|
||||
|
||||
thermal_zone_of_sensor_unregister(&pdev->dev, chip->tz_dev);
|
||||
if (!IS_ERR(chip->adc))
|
||||
iio_channel_release(chip->adc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id qpnp_tm_match_table[] = {
|
||||
{ .compatible = "qcom,spmi-temp-alarm" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qpnp_tm_match_table);
|
||||
|
||||
static struct platform_driver qpnp_tm_driver = {
|
||||
.driver = {
|
||||
.name = "spmi-temp-alarm",
|
||||
.of_match_table = qpnp_tm_match_table,
|
||||
},
|
||||
.probe = qpnp_tm_probe,
|
||||
.remove = qpnp_tm_remove,
|
||||
};
|
||||
module_platform_driver(qpnp_tm_driver);
|
||||
|
||||
MODULE_ALIAS("platform:spmi-temp-alarm");
|
||||
MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
@@ -97,6 +97,32 @@
|
||||
#define EXYNOS4412_MUX_ADDR_VALUE 6
|
||||
#define EXYNOS4412_MUX_ADDR_SHIFT 20
|
||||
|
||||
/* Exynos5433 specific registers */
|
||||
#define EXYNOS5433_TMU_REG_CONTROL1 0x024
|
||||
#define EXYNOS5433_TMU_SAMPLING_INTERVAL 0x02c
|
||||
#define EXYNOS5433_TMU_COUNTER_VALUE0 0x030
|
||||
#define EXYNOS5433_TMU_COUNTER_VALUE1 0x034
|
||||
#define EXYNOS5433_TMU_REG_CURRENT_TEMP1 0x044
|
||||
#define EXYNOS5433_THD_TEMP_RISE3_0 0x050
|
||||
#define EXYNOS5433_THD_TEMP_RISE7_4 0x054
|
||||
#define EXYNOS5433_THD_TEMP_FALL3_0 0x060
|
||||
#define EXYNOS5433_THD_TEMP_FALL7_4 0x064
|
||||
#define EXYNOS5433_TMU_REG_INTEN 0x0c0
|
||||
#define EXYNOS5433_TMU_REG_INTPEND 0x0c8
|
||||
#define EXYNOS5433_TMU_EMUL_CON 0x110
|
||||
#define EXYNOS5433_TMU_PD_DET_EN 0x130
|
||||
|
||||
#define EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT 16
|
||||
#define EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT 23
|
||||
#define EXYNOS5433_TRIMINFO_SENSOR_ID_MASK \
|
||||
(0xf << EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT)
|
||||
#define EXYNOS5433_TRIMINFO_CALIB_SEL_MASK BIT(23)
|
||||
|
||||
#define EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING 0
|
||||
#define EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING 1
|
||||
|
||||
#define EXYNOS5433_PD_DET_EN 1
|
||||
|
||||
/*exynos5440 specific registers*/
|
||||
#define EXYNOS5440_TMU_S0_7_TRIM 0x000
|
||||
#define EXYNOS5440_TMU_S0_7_CTRL 0x020
|
||||
@@ -484,6 +510,101 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos5433_tmu_initialize(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct exynos_tmu_platform_data *pdata = data->pdata;
|
||||
struct thermal_zone_device *tz = data->tzd;
|
||||
unsigned int status, trim_info;
|
||||
unsigned int rising_threshold = 0, falling_threshold = 0;
|
||||
unsigned long temp, temp_hist;
|
||||
int ret = 0, threshold_code, i, sensor_id, cal_type;
|
||||
|
||||
status = readb(data->base + EXYNOS_TMU_REG_STATUS);
|
||||
if (!status) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
|
||||
sanitize_temp_error(data, trim_info);
|
||||
|
||||
/* Read the temperature sensor id */
|
||||
sensor_id = (trim_info & EXYNOS5433_TRIMINFO_SENSOR_ID_MASK)
|
||||
>> EXYNOS5433_TRIMINFO_SENSOR_ID_SHIFT;
|
||||
dev_info(&pdev->dev, "Temperature sensor ID: 0x%x\n", sensor_id);
|
||||
|
||||
/* Read the calibration mode */
|
||||
writel(trim_info, data->base + EXYNOS_TMU_REG_TRIMINFO);
|
||||
cal_type = (trim_info & EXYNOS5433_TRIMINFO_CALIB_SEL_MASK)
|
||||
>> EXYNOS5433_TRIMINFO_CALIB_SEL_SHIFT;
|
||||
|
||||
switch (cal_type) {
|
||||
case EXYNOS5433_TRIMINFO_ONE_POINT_TRIMMING:
|
||||
pdata->cal_type = TYPE_ONE_POINT_TRIMMING;
|
||||
break;
|
||||
case EXYNOS5433_TRIMINFO_TWO_POINT_TRIMMING:
|
||||
pdata->cal_type = TYPE_TWO_POINT_TRIMMING;
|
||||
break;
|
||||
default:
|
||||
pdata->cal_type = TYPE_ONE_POINT_TRIMMING;
|
||||
break;
|
||||
};
|
||||
|
||||
dev_info(&pdev->dev, "Calibration type is %d-point calibration\n",
|
||||
cal_type ? 2 : 1);
|
||||
|
||||
/* Write temperature code for rising and falling threshold */
|
||||
for (i = 0; i < of_thermal_get_ntrips(tz); i++) {
|
||||
int rising_reg_offset, falling_reg_offset;
|
||||
int j = 0;
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
rising_reg_offset = EXYNOS5433_THD_TEMP_RISE3_0;
|
||||
falling_reg_offset = EXYNOS5433_THD_TEMP_FALL3_0;
|
||||
j = i;
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
rising_reg_offset = EXYNOS5433_THD_TEMP_RISE7_4;
|
||||
falling_reg_offset = EXYNOS5433_THD_TEMP_FALL7_4;
|
||||
j = i - 4;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Write temperature code for rising threshold */
|
||||
tz->ops->get_trip_temp(tz, i, &temp);
|
||||
temp /= MCELSIUS;
|
||||
threshold_code = temp_to_code(data, temp);
|
||||
|
||||
rising_threshold = readl(data->base + rising_reg_offset);
|
||||
rising_threshold |= (threshold_code << j * 8);
|
||||
writel(rising_threshold, data->base + rising_reg_offset);
|
||||
|
||||
/* Write temperature code for falling threshold */
|
||||
tz->ops->get_trip_hyst(tz, i, &temp_hist);
|
||||
temp_hist = temp - (temp_hist / MCELSIUS);
|
||||
threshold_code = temp_to_code(data, temp_hist);
|
||||
|
||||
falling_threshold = readl(data->base + falling_reg_offset);
|
||||
falling_threshold &= ~(0xff << j * 8);
|
||||
falling_threshold |= (threshold_code << j * 8);
|
||||
writel(falling_threshold, data->base + falling_reg_offset);
|
||||
}
|
||||
|
||||
data->tmu_clear_irqs(data);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int exynos5440_tmu_initialize(struct platform_device *pdev)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
@@ -643,6 +764,48 @@ static void exynos4210_tmu_control(struct platform_device *pdev, bool on)
|
||||
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
|
||||
}
|
||||
|
||||
static void exynos5433_tmu_control(struct platform_device *pdev, bool on)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
struct thermal_zone_device *tz = data->tzd;
|
||||
unsigned int con, interrupt_en, pd_det_en;
|
||||
|
||||
con = get_con_reg(data, readl(data->base + EXYNOS_TMU_REG_CONTROL));
|
||||
|
||||
if (on) {
|
||||
con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
|
||||
interrupt_en =
|
||||
(of_thermal_is_trip_valid(tz, 7)
|
||||
<< EXYNOS7_TMU_INTEN_RISE7_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 6)
|
||||
<< EXYNOS7_TMU_INTEN_RISE6_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 5)
|
||||
<< EXYNOS7_TMU_INTEN_RISE5_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 4)
|
||||
<< EXYNOS7_TMU_INTEN_RISE4_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 3)
|
||||
<< EXYNOS7_TMU_INTEN_RISE3_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 2)
|
||||
<< EXYNOS7_TMU_INTEN_RISE2_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 1)
|
||||
<< EXYNOS7_TMU_INTEN_RISE1_SHIFT) |
|
||||
(of_thermal_is_trip_valid(tz, 0)
|
||||
<< EXYNOS7_TMU_INTEN_RISE0_SHIFT);
|
||||
|
||||
interrupt_en |=
|
||||
interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
|
||||
} else {
|
||||
con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
|
||||
interrupt_en = 0; /* Disable all interrupts */
|
||||
}
|
||||
|
||||
pd_det_en = on ? EXYNOS5433_PD_DET_EN : 0;
|
||||
|
||||
writel(pd_det_en, data->base + EXYNOS5433_TMU_PD_DET_EN);
|
||||
writel(interrupt_en, data->base + EXYNOS5433_TMU_REG_INTEN);
|
||||
writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
|
||||
}
|
||||
|
||||
static void exynos5440_tmu_control(struct platform_device *pdev, bool on)
|
||||
{
|
||||
struct exynos_tmu_data *data = platform_get_drvdata(pdev);
|
||||
@@ -770,6 +933,8 @@ static void exynos4412_tmu_set_emulation(struct exynos_tmu_data *data,
|
||||
|
||||
if (data->soc == SOC_ARCH_EXYNOS5260)
|
||||
emul_con = EXYNOS5260_EMUL_CON;
|
||||
if (data->soc == SOC_ARCH_EXYNOS5433)
|
||||
emul_con = EXYNOS5433_TMU_EMUL_CON;
|
||||
else if (data->soc == SOC_ARCH_EXYNOS7)
|
||||
emul_con = EXYNOS7_TMU_REG_EMUL_CON;
|
||||
else
|
||||
@@ -882,6 +1047,9 @@ static void exynos4210_tmu_clear_irqs(struct exynos_tmu_data *data)
|
||||
} else if (data->soc == SOC_ARCH_EXYNOS7) {
|
||||
tmu_intstat = EXYNOS7_TMU_REG_INTPEND;
|
||||
tmu_intclear = EXYNOS7_TMU_REG_INTPEND;
|
||||
} else if (data->soc == SOC_ARCH_EXYNOS5433) {
|
||||
tmu_intstat = EXYNOS5433_TMU_REG_INTPEND;
|
||||
tmu_intclear = EXYNOS5433_TMU_REG_INTPEND;
|
||||
} else {
|
||||
tmu_intstat = EXYNOS_TMU_REG_INTSTAT;
|
||||
tmu_intclear = EXYNOS_TMU_REG_INTCLEAR;
|
||||
@@ -926,6 +1094,7 @@ static const struct of_device_id exynos_tmu_match[] = {
|
||||
{ .compatible = "samsung,exynos5260-tmu", },
|
||||
{ .compatible = "samsung,exynos5420-tmu", },
|
||||
{ .compatible = "samsung,exynos5420-tmu-ext-triminfo", },
|
||||
{ .compatible = "samsung,exynos5433-tmu", },
|
||||
{ .compatible = "samsung,exynos5440-tmu", },
|
||||
{ .compatible = "samsung,exynos7-tmu", },
|
||||
{ /* sentinel */ },
|
||||
@@ -949,6 +1118,8 @@ static int exynos_of_get_soc_type(struct device_node *np)
|
||||
else if (of_device_is_compatible(np,
|
||||
"samsung,exynos5420-tmu-ext-triminfo"))
|
||||
return SOC_ARCH_EXYNOS5420_TRIMINFO;
|
||||
else if (of_device_is_compatible(np, "samsung,exynos5433-tmu"))
|
||||
return SOC_ARCH_EXYNOS5433;
|
||||
else if (of_device_is_compatible(np, "samsung,exynos5440-tmu"))
|
||||
return SOC_ARCH_EXYNOS5440;
|
||||
else if (of_device_is_compatible(np, "samsung,exynos7-tmu"))
|
||||
@@ -1069,6 +1240,13 @@ static int exynos_map_dt_data(struct platform_device *pdev)
|
||||
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
|
||||
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
|
||||
break;
|
||||
case SOC_ARCH_EXYNOS5433:
|
||||
data->tmu_initialize = exynos5433_tmu_initialize;
|
||||
data->tmu_control = exynos5433_tmu_control;
|
||||
data->tmu_read = exynos4412_tmu_read;
|
||||
data->tmu_set_emulation = exynos4412_tmu_set_emulation;
|
||||
data->tmu_clear_irqs = exynos4210_tmu_clear_irqs;
|
||||
break;
|
||||
case SOC_ARCH_EXYNOS5440:
|
||||
data->tmu_initialize = exynos5440_tmu_initialize;
|
||||
data->tmu_control = exynos5440_tmu_control;
|
||||
@@ -1172,7 +1350,9 @@ static int exynos_tmu_probe(struct platform_device *pdev)
|
||||
goto err_clk_sec;
|
||||
}
|
||||
|
||||
if (data->soc == SOC_ARCH_EXYNOS7) {
|
||||
switch (data->soc) {
|
||||
case SOC_ARCH_EXYNOS5433:
|
||||
case SOC_ARCH_EXYNOS7:
|
||||
data->sclk = devm_clk_get(&pdev->dev, "tmu_sclk");
|
||||
if (IS_ERR(data->sclk)) {
|
||||
dev_err(&pdev->dev, "Failed to get sclk\n");
|
||||
@@ -1184,7 +1364,10 @@ static int exynos_tmu_probe(struct platform_device *pdev)
|
||||
goto err_clk;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
ret = exynos_tmu_initialize(pdev);
|
||||
if (ret) {
|
||||
|
||||
@@ -33,6 +33,7 @@ enum soc_type {
|
||||
SOC_ARCH_EXYNOS5260,
|
||||
SOC_ARCH_EXYNOS5420,
|
||||
SOC_ARCH_EXYNOS5420_TRIMINFO,
|
||||
SOC_ARCH_EXYNOS5433,
|
||||
SOC_ARCH_EXYNOS5440,
|
||||
SOC_ARCH_EXYNOS7,
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user