Javi Merino | 6b775e8 | 2015-03-02 17:17:19 +0000 | [diff] [blame] | 1 | Power allocator governor tunables |
| 2 | ================================= |
| 3 | |
| 4 | Trip points |
| 5 | ----------- |
| 6 | |
Javi Merino | 8b7b390 | 2015-09-14 14:23:52 +0100 | [diff] [blame] | 7 | The governor works optimally with the following two passive trip points: |
Javi Merino | 6b775e8 | 2015-03-02 17:17:19 +0000 | [diff] [blame] | 8 | |
| 9 | 1. "switch on" trip point: temperature above which the governor |
| 10 | control loop starts operating. This is the first passive trip |
| 11 | point of the thermal zone. |
| 12 | |
| 13 | 2. "desired temperature" trip point: it should be higher than the |
| 14 | "switch on" trip point. This the target temperature the governor |
| 15 | is controlling for. This is the last passive trip point of the |
| 16 | thermal zone. |
| 17 | |
| 18 | PID Controller |
| 19 | -------------- |
| 20 | |
| 21 | The power allocator governor implements a |
| 22 | Proportional-Integral-Derivative controller (PID controller) with |
| 23 | temperature as the control input and power as the controlled output: |
| 24 | |
| 25 | P_max = k_p * e + k_i * err_integral + k_d * diff_err + sustainable_power |
| 26 | |
| 27 | where |
| 28 | e = desired_temperature - current_temperature |
| 29 | err_integral is the sum of previous errors |
| 30 | diff_err = e - previous_error |
| 31 | |
| 32 | It is similar to the one depicted below: |
| 33 | |
| 34 | k_d |
| 35 | | |
| 36 | current_temp | |
| 37 | | v |
| 38 | | +----------+ +---+ |
| 39 | | +----->| diff_err |-->| X |------+ |
| 40 | | | +----------+ +---+ | |
| 41 | | | | tdp actor |
| 42 | | | k_i | | get_requested_power() |
| 43 | | | | | | | | |
| 44 | | | | | | | | ... |
| 45 | v | v v v v v |
| 46 | +---+ | +-------+ +---+ +---+ +---+ +----------+ |
| 47 | | S |-------+----->| sum e |----->| X |--->| S |-->| S |-->|power | |
| 48 | +---+ | +-------+ +---+ +---+ +---+ |allocation| |
| 49 | ^ | ^ +----------+ |
| 50 | | | | | | |
| 51 | | | +---+ | | | |
| 52 | | +------->| X |-------------------+ v v |
| 53 | | +---+ granted performance |
| 54 | desired_temperature ^ |
| 55 | | |
| 56 | | |
| 57 | k_po/k_pu |
| 58 | |
| 59 | Sustainable power |
| 60 | ----------------- |
| 61 | |
| 62 | An estimate of the sustainable dissipatable power (in mW) should be |
| 63 | provided while registering the thermal zone. This estimates the |
| 64 | sustained power that can be dissipated at the desired control |
| 65 | temperature. This is the maximum sustained power for allocation at |
| 66 | the desired maximum temperature. The actual sustained power can vary |
| 67 | for a number of reasons. The closed loop controller will take care of |
| 68 | variations such as environmental conditions, and some factors related |
| 69 | to the speed-grade of the silicon. `sustainable_power` is therefore |
| 70 | simply an estimate, and may be tuned to affect the aggressiveness of |
| 71 | the thermal ramp. For reference, the sustainable power of a 4" phone |
| 72 | is typically 2000mW, while on a 10" tablet is around 4500mW (may vary |
| 73 | depending on screen size). |
| 74 | |
| 75 | If you are using device tree, do add it as a property of the |
| 76 | thermal-zone. For example: |
| 77 | |
| 78 | thermal-zones { |
| 79 | soc_thermal { |
| 80 | polling-delay = <1000>; |
| 81 | polling-delay-passive = <100>; |
| 82 | sustainable-power = <2500>; |
| 83 | ... |
| 84 | |
| 85 | Instead, if the thermal zone is registered from the platform code, pass a |
| 86 | `thermal_zone_params` that has a `sustainable_power`. If no |
| 87 | `thermal_zone_params` were being passed, then something like below |
| 88 | will suffice: |
| 89 | |
| 90 | static const struct thermal_zone_params tz_params = { |
| 91 | .sustainable_power = 3500, |
| 92 | }; |
| 93 | |
| 94 | and then pass `tz_params` as the 5th parameter to |
| 95 | `thermal_zone_device_register()` |
| 96 | |
| 97 | k_po and k_pu |
| 98 | ------------- |
| 99 | |
| 100 | The implementation of the PID controller in the power allocator |
| 101 | thermal governor allows the configuration of two proportional term |
| 102 | constants: `k_po` and `k_pu`. `k_po` is the proportional term |
| 103 | constant during temperature overshoot periods (current temperature is |
| 104 | above "desired temperature" trip point). Conversely, `k_pu` is the |
| 105 | proportional term constant during temperature undershoot periods |
| 106 | (current temperature below "desired temperature" trip point). |
| 107 | |
| 108 | These controls are intended as the primary mechanism for configuring |
| 109 | the permitted thermal "ramp" of the system. For instance, a lower |
| 110 | `k_pu` value will provide a slower ramp, at the cost of capping |
| 111 | available capacity at a low temperature. On the other hand, a high |
| 112 | value of `k_pu` will result in the governor granting very high power |
| 113 | whilst temperature is low, and may lead to temperature overshooting. |
| 114 | |
| 115 | The default value for `k_pu` is: |
| 116 | |
| 117 | 2 * sustainable_power / (desired_temperature - switch_on_temp) |
| 118 | |
| 119 | This means that at `switch_on_temp` the output of the controller's |
| 120 | proportional term will be 2 * `sustainable_power`. The default value |
| 121 | for `k_po` is: |
| 122 | |
| 123 | sustainable_power / (desired_temperature - switch_on_temp) |
| 124 | |
| 125 | Focusing on the proportional and feed forward values of the PID |
| 126 | controller equation we have: |
| 127 | |
| 128 | P_max = k_p * e + sustainable_power |
| 129 | |
| 130 | The proportional term is proportional to the difference between the |
| 131 | desired temperature and the current one. When the current temperature |
| 132 | is the desired one, then the proportional component is zero and |
| 133 | `P_max` = `sustainable_power`. That is, the system should operate in |
| 134 | thermal equilibrium under constant load. `sustainable_power` is only |
| 135 | an estimate, which is the reason for closed-loop control such as this. |
| 136 | |
| 137 | Expanding `k_pu` we get: |
| 138 | P_max = 2 * sustainable_power * (T_set - T) / (T_set - T_on) + |
| 139 | sustainable_power |
| 140 | |
| 141 | where |
| 142 | T_set is the desired temperature |
| 143 | T is the current temperature |
| 144 | T_on is the switch on temperature |
| 145 | |
| 146 | When the current temperature is the switch_on temperature, the above |
| 147 | formula becomes: |
| 148 | |
| 149 | P_max = 2 * sustainable_power * (T_set - T_on) / (T_set - T_on) + |
| 150 | sustainable_power = 2 * sustainable_power + sustainable_power = |
| 151 | 3 * sustainable_power |
| 152 | |
| 153 | Therefore, the proportional term alone linearly decreases power from |
| 154 | 3 * `sustainable_power` to `sustainable_power` as the temperature |
| 155 | rises from the switch on temperature to the desired temperature. |
| 156 | |
| 157 | k_i and integral_cutoff |
| 158 | ----------------------- |
| 159 | |
| 160 | `k_i` configures the PID loop's integral term constant. This term |
| 161 | allows the PID controller to compensate for long term drift and for |
| 162 | the quantized nature of the output control: cooling devices can't set |
| 163 | the exact power that the governor requests. When the temperature |
| 164 | error is below `integral_cutoff`, errors are accumulated in the |
| 165 | integral term. This term is then multiplied by `k_i` and the result |
| 166 | added to the output of the controller. Typically `k_i` is set low (1 |
| 167 | or 2) and `integral_cutoff` is 0. |
| 168 | |
| 169 | k_d |
| 170 | --- |
| 171 | |
| 172 | `k_d` configures the PID loop's derivative term constant. It's |
| 173 | recommended to leave it as the default: 0. |
| 174 | |
| 175 | Cooling device power API |
| 176 | ======================== |
| 177 | |
| 178 | Cooling devices controlled by this governor must supply the additional |
| 179 | "power" API in their `cooling_device_ops`. It consists on three ops: |
| 180 | |
| 181 | 1. int get_requested_power(struct thermal_cooling_device *cdev, |
| 182 | struct thermal_zone_device *tz, u32 *power); |
| 183 | @cdev: The `struct thermal_cooling_device` pointer |
| 184 | @tz: thermal zone in which we are currently operating |
| 185 | @power: pointer in which to store the calculated power |
| 186 | |
| 187 | `get_requested_power()` calculates the power requested by the device |
| 188 | in milliwatts and stores it in @power . It should return 0 on |
| 189 | success, -E* on failure. This is currently used by the power |
| 190 | allocator governor to calculate how much power to give to each cooling |
| 191 | device. |
| 192 | |
| 193 | 2. int state2power(struct thermal_cooling_device *cdev, struct |
| 194 | thermal_zone_device *tz, unsigned long state, u32 *power); |
| 195 | @cdev: The `struct thermal_cooling_device` pointer |
| 196 | @tz: thermal zone in which we are currently operating |
| 197 | @state: A cooling device state |
| 198 | @power: pointer in which to store the equivalent power |
| 199 | |
| 200 | Convert cooling device state @state into power consumption in |
| 201 | milliwatts and store it in @power. It should return 0 on success, -E* |
| 202 | on failure. This is currently used by thermal core to calculate the |
| 203 | maximum power that an actor can consume. |
| 204 | |
| 205 | 3. int power2state(struct thermal_cooling_device *cdev, u32 power, |
| 206 | unsigned long *state); |
| 207 | @cdev: The `struct thermal_cooling_device` pointer |
| 208 | @power: power in milliwatts |
| 209 | @state: pointer in which to store the resulting state |
| 210 | |
| 211 | Calculate a cooling device state that would make the device consume at |
| 212 | most @power mW and store it in @state. It should return 0 on success, |
| 213 | -E* on failure. This is currently used by the thermal core to convert |
| 214 | a given power set by the power allocator governor to a state that the |
| 215 | cooling device can set. It is a function because this conversion may |
| 216 | depend on external factors that may change so this function should the |
| 217 | best conversion given "current circumstances". |
| 218 | |
| 219 | Cooling device weights |
| 220 | ---------------------- |
| 221 | |
| 222 | Weights are a mechanism to bias the allocation among cooling |
| 223 | devices. They express the relative power efficiency of different |
| 224 | cooling devices. Higher weight can be used to express higher power |
| 225 | efficiency. Weighting is relative such that if each cooling device |
| 226 | has a weight of one they are considered equal. This is particularly |
| 227 | useful in heterogeneous systems where two cooling devices may perform |
| 228 | the same kind of compute, but with different efficiency. For example, |
| 229 | a system with two different types of processors. |
| 230 | |
| 231 | If the thermal zone is registered using |
| 232 | `thermal_zone_device_register()` (i.e., platform code), then weights |
| 233 | are passed as part of the thermal zone's `thermal_bind_parameters`. |
| 234 | If the platform is registered using device tree, then they are passed |
| 235 | as the `contribution` property of each map in the `cooling-maps` node. |
| 236 | |
| 237 | Limitations of the power allocator governor |
| 238 | =========================================== |
| 239 | |
| 240 | The power allocator governor's PID controller works best if there is a |
| 241 | periodic tick. If you have a driver that calls |
| 242 | `thermal_zone_device_update()` (or anything that ends up calling the |
| 243 | governor's `throttle()` function) repetitively, the governor response |
| 244 | won't be very good. Note that this is not particular to this |
| 245 | governor, step-wise will also misbehave if you call its throttle() |
| 246 | faster than the normal thermal framework tick (due to interrupts for |
| 247 | example) as it will overreact. |