|  | 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). | 
|  |  | 
|  | 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. |