forked from Minki/linux
5a64f77569
The Energy Model (EM) can store power values in milli-Watts or in abstract scale. This might cause issues in the subsystems which use the EM for estimating the device power, such as: - mixing of different scales in a subsystem which uses multiple (cooling) devices (e.g. thermal Intelligent Power Allocation (IPA)) - assuming that energy [milli-Joules] can be derived from the EM power values which might not be possible since the power scale doesn't have to be in milli-Watts To avoid misconfiguration add the requisite documentation to the EM and related subsystems: EAS and IPA. Signed-off-by: Lukasz Luba <lukasz.luba@arm.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
282 lines
11 KiB
ReStructuredText
282 lines
11 KiB
ReStructuredText
=================================
|
|
Power allocator governor tunables
|
|
=================================
|
|
|
|
Trip points
|
|
-----------
|
|
|
|
The governor works optimally with 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). It is possible to have the power value
|
|
expressed in an abstract scale. The sustained power should be aligned
|
|
to the scale used by the related cooling devices.
|
|
|
|
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
|
|
while 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.
|
|
|
|
Energy Model requirements
|
|
=========================
|
|
|
|
Another important thing is the consistent scale of the power values
|
|
provided by the cooling devices. All of the cooling devices in a single
|
|
thermal zone should have power values reported either in milli-Watts
|
|
or scaled to the same 'abstract scale'.
|