Lukasz Luba | 0de967f | 2020-07-30 17:51:17 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 2 | /* |
| 3 | * devfreq_cooling: Thermal cooling device implementation for devices using |
| 4 | * devfreq |
| 5 | * |
| 6 | * Copyright (C) 2014-2015 ARM Limited |
| 7 | * |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 8 | * TODO: |
| 9 | * - If OPPs are added or removed after devfreq cooling has |
| 10 | * registered, the devfreq cooling won't react to it. |
| 11 | */ |
| 12 | |
| 13 | #include <linux/devfreq.h> |
| 14 | #include <linux/devfreq_cooling.h> |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 15 | #include <linux/energy_model.h> |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 16 | #include <linux/export.h> |
| 17 | #include <linux/slab.h> |
| 18 | #include <linux/pm_opp.h> |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 19 | #include <linux/pm_qos.h> |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 20 | #include <linux/thermal.h> |
Daniel Lezcano | 73b718c | 2021-09-07 19:57:51 -0700 | [diff] [blame] | 21 | #include <linux/units.h> |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 22 | |
Javi Merino | 9876b1a | 2015-09-10 18:09:31 +0100 | [diff] [blame] | 23 | #include <trace/events/thermal.h> |
| 24 | |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 25 | #define SCALE_ERROR_MITIGATION 100 |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 26 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 27 | /** |
| 28 | * struct devfreq_cooling_device - Devfreq cooling device |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 29 | * devfreq_cooling_device registered. |
| 30 | * @cdev: Pointer to associated thermal cooling device. |
| 31 | * @devfreq: Pointer to associated devfreq device. |
| 32 | * @cooling_state: Current cooling state. |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 33 | * @freq_table: Pointer to a table with the frequencies sorted in descending |
| 34 | * order. You can index the table by cooling device state |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 35 | * @max_state: It is the last index, that is, one less than the number of the |
| 36 | * OPPs |
| 37 | * @power_ops: Pointer to devfreq_cooling_power, a more precised model. |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 38 | * @res_util: Resource utilization scaling factor for the power. |
| 39 | * It is multiplied by 100 to minimize the error. It is used |
| 40 | * for estimation of the power budget instead of using |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 41 | * 'utilization' (which is 'busy_time' / 'total_time'). |
| 42 | * The 'res_util' range is from 100 to power * 100 for the |
| 43 | * corresponding 'state'. |
Amit Kucheria | 1b5cb95 | 2019-11-20 21:15:13 +0530 | [diff] [blame] | 44 | * @capped_state: index to cooling state with in dynamic power budget |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 45 | * @req_max_freq: PM QoS request for limiting the maximum frequency |
| 46 | * of the devfreq device. |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 47 | * @em_pd: Energy Model for the associated Devfreq device |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 48 | */ |
| 49 | struct devfreq_cooling_device { |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 50 | struct thermal_cooling_device *cdev; |
| 51 | struct devfreq *devfreq; |
| 52 | unsigned long cooling_state; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 53 | u32 *freq_table; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 54 | size_t max_state; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 55 | struct devfreq_cooling_power *power_ops; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 56 | u32 res_util; |
| 57 | int capped_state; |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 58 | struct dev_pm_qos_request req_max_freq; |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 59 | struct em_perf_domain *em_pd; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 60 | }; |
| 61 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 62 | static int devfreq_cooling_get_max_state(struct thermal_cooling_device *cdev, |
| 63 | unsigned long *state) |
| 64 | { |
| 65 | struct devfreq_cooling_device *dfc = cdev->devdata; |
| 66 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 67 | *state = dfc->max_state; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 68 | |
| 69 | return 0; |
| 70 | } |
| 71 | |
| 72 | static int devfreq_cooling_get_cur_state(struct thermal_cooling_device *cdev, |
| 73 | unsigned long *state) |
| 74 | { |
| 75 | struct devfreq_cooling_device *dfc = cdev->devdata; |
| 76 | |
| 77 | *state = dfc->cooling_state; |
| 78 | |
| 79 | return 0; |
| 80 | } |
| 81 | |
| 82 | static int devfreq_cooling_set_cur_state(struct thermal_cooling_device *cdev, |
| 83 | unsigned long state) |
| 84 | { |
| 85 | struct devfreq_cooling_device *dfc = cdev->devdata; |
| 86 | struct devfreq *df = dfc->devfreq; |
| 87 | struct device *dev = df->dev.parent; |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 88 | unsigned long freq; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 89 | int perf_idx; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 90 | |
| 91 | if (state == dfc->cooling_state) |
| 92 | return 0; |
| 93 | |
| 94 | dev_dbg(dev, "Setting cooling state %lu\n", state); |
| 95 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 96 | if (state > dfc->max_state) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 97 | return -EINVAL; |
| 98 | |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 99 | if (dfc->em_pd) { |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 100 | perf_idx = dfc->max_state - state; |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 101 | freq = dfc->em_pd->table[perf_idx].frequency * 1000; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 102 | } else { |
| 103 | freq = dfc->freq_table[state]; |
| 104 | } |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 105 | |
| 106 | dev_pm_qos_update_request(&dfc->req_max_freq, |
| 107 | DIV_ROUND_UP(freq, HZ_PER_KHZ)); |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 108 | |
| 109 | dfc->cooling_state = state; |
| 110 | |
| 111 | return 0; |
| 112 | } |
| 113 | |
| 114 | /** |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 115 | * get_perf_idx() - get the performance index corresponding to a frequency |
| 116 | * @em_pd: Pointer to device's Energy Model |
| 117 | * @freq: frequency in kHz |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 118 | * |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 119 | * Return: the performance index associated with the @freq, or |
| 120 | * -EINVAL if it wasn't found. |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 121 | */ |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 122 | static int get_perf_idx(struct em_perf_domain *em_pd, unsigned long freq) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 123 | { |
| 124 | int i; |
| 125 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 126 | for (i = 0; i < em_pd->nr_perf_states; i++) { |
| 127 | if (em_pd->table[i].frequency == freq) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 128 | return i; |
| 129 | } |
| 130 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 131 | return -EINVAL; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 132 | } |
| 133 | |
Lukasz Luba | e34cab4 | 2017-05-04 12:34:31 +0100 | [diff] [blame] | 134 | static unsigned long get_voltage(struct devfreq *df, unsigned long freq) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 135 | { |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 136 | struct device *dev = df->dev.parent; |
| 137 | unsigned long voltage; |
| 138 | struct dev_pm_opp *opp; |
| 139 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 140 | opp = dev_pm_opp_find_freq_exact(dev, freq, true); |
Viresh Kumar | a4e49c9 | 2017-02-07 09:40:01 +0530 | [diff] [blame] | 141 | if (PTR_ERR(opp) == -ERANGE) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 142 | opp = dev_pm_opp_find_freq_exact(dev, freq, false); |
| 143 | |
Viresh Kumar | afd1f4e | 2017-02-07 09:40:03 +0530 | [diff] [blame] | 144 | if (IS_ERR(opp)) { |
| 145 | dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n", |
| 146 | freq, PTR_ERR(opp)); |
| 147 | return 0; |
| 148 | } |
| 149 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 150 | voltage = dev_pm_opp_get_voltage(opp) / 1000; /* mV */ |
Viresh Kumar | 8a31d9d9 | 2017-01-23 10:11:47 +0530 | [diff] [blame] | 151 | dev_pm_opp_put(opp); |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 152 | |
| 153 | if (voltage == 0) { |
Viresh Kumar | 8327b83 | 2017-02-07 09:40:02 +0530 | [diff] [blame] | 154 | dev_err_ratelimited(dev, |
Viresh Kumar | afd1f4e | 2017-02-07 09:40:03 +0530 | [diff] [blame] | 155 | "Failed to get voltage for frequency %lu\n", |
| 156 | freq); |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 157 | } |
| 158 | |
Lukasz Luba | e34cab4 | 2017-05-04 12:34:31 +0100 | [diff] [blame] | 159 | return voltage; |
| 160 | } |
| 161 | |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 162 | static void _normalize_load(struct devfreq_dev_status *status) |
| 163 | { |
| 164 | if (status->total_time > 0xfffff) { |
| 165 | status->total_time >>= 10; |
| 166 | status->busy_time >>= 10; |
| 167 | } |
| 168 | |
| 169 | status->busy_time <<= 10; |
| 170 | status->busy_time /= status->total_time ? : 1; |
| 171 | |
| 172 | status->busy_time = status->busy_time ? : 1; |
| 173 | status->total_time = 1024; |
| 174 | } |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 175 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 176 | static int devfreq_cooling_get_requested_power(struct thermal_cooling_device *cdev, |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 177 | u32 *power) |
| 178 | { |
| 179 | struct devfreq_cooling_device *dfc = cdev->devdata; |
| 180 | struct devfreq *df = dfc->devfreq; |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 181 | struct devfreq_dev_status status; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 182 | unsigned long state; |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 183 | unsigned long freq; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 184 | unsigned long voltage; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 185 | int res, perf_idx; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 186 | |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 187 | mutex_lock(&df->lock); |
| 188 | status = df->last_status; |
| 189 | mutex_unlock(&df->lock); |
| 190 | |
| 191 | freq = status.current_frequency; |
| 192 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 193 | if (dfc->power_ops && dfc->power_ops->get_real_power) { |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 194 | voltage = get_voltage(df, freq); |
| 195 | if (voltage == 0) { |
| 196 | res = -EINVAL; |
| 197 | goto fail; |
| 198 | } |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 199 | |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 200 | res = dfc->power_ops->get_real_power(df, power, freq, voltage); |
| 201 | if (!res) { |
| 202 | state = dfc->capped_state; |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 203 | dfc->res_util = dfc->em_pd->table[state].power; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 204 | dfc->res_util *= SCALE_ERROR_MITIGATION; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 205 | |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 206 | if (*power > 1) |
| 207 | dfc->res_util /= *power; |
| 208 | } else { |
| 209 | goto fail; |
| 210 | } |
| 211 | } else { |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 212 | /* Energy Model frequencies are in kHz */ |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 213 | perf_idx = get_perf_idx(dfc->em_pd, freq / 1000); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 214 | if (perf_idx < 0) { |
| 215 | res = -EAGAIN; |
| 216 | goto fail; |
| 217 | } |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 218 | |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 219 | _normalize_load(&status); |
| 220 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 221 | /* Scale power for utilization */ |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 222 | *power = dfc->em_pd->table[perf_idx].power; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 223 | *power *= status.busy_time; |
| 224 | *power >>= 10; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 225 | } |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 226 | |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 227 | trace_thermal_power_devfreq_get_power(cdev, &status, freq, *power); |
Javi Merino | 9876b1a | 2015-09-10 18:09:31 +0100 | [diff] [blame] | 228 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 229 | return 0; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 230 | fail: |
| 231 | /* It is safe to set max in this case */ |
| 232 | dfc->res_util = SCALE_ERROR_MITIGATION; |
| 233 | return res; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 234 | } |
| 235 | |
| 236 | static int devfreq_cooling_state2power(struct thermal_cooling_device *cdev, |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 237 | unsigned long state, u32 *power) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 238 | { |
| 239 | struct devfreq_cooling_device *dfc = cdev->devdata; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 240 | int perf_idx; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 241 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 242 | if (state > dfc->max_state) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 243 | return -EINVAL; |
| 244 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 245 | perf_idx = dfc->max_state - state; |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 246 | *power = dfc->em_pd->table[perf_idx].power; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 247 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 248 | return 0; |
| 249 | } |
| 250 | |
| 251 | static int devfreq_cooling_power2state(struct thermal_cooling_device *cdev, |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 252 | u32 power, unsigned long *state) |
| 253 | { |
| 254 | struct devfreq_cooling_device *dfc = cdev->devdata; |
| 255 | struct devfreq *df = dfc->devfreq; |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 256 | struct devfreq_dev_status status; |
| 257 | unsigned long freq; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 258 | s32 est_power; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 259 | int i; |
| 260 | |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 261 | mutex_lock(&df->lock); |
| 262 | status = df->last_status; |
| 263 | mutex_unlock(&df->lock); |
| 264 | |
| 265 | freq = status.current_frequency; |
| 266 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 267 | if (dfc->power_ops && dfc->power_ops->get_real_power) { |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 268 | /* Scale for resource utilization */ |
| 269 | est_power = power * dfc->res_util; |
| 270 | est_power /= SCALE_ERROR_MITIGATION; |
| 271 | } else { |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 272 | /* Scale dynamic power for utilization */ |
Lukasz Luba | 229794e | 2020-12-10 14:30:11 +0000 | [diff] [blame] | 273 | _normalize_load(&status); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 274 | est_power = power << 10; |
| 275 | est_power /= status.busy_time; |
Lukasz Luba | 2be83da | 2017-05-04 12:34:32 +0100 | [diff] [blame] | 276 | } |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 277 | |
| 278 | /* |
| 279 | * Find the first cooling state that is within the power |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 280 | * budget. The EM power table is sorted ascending. |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 281 | */ |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 282 | for (i = dfc->max_state; i > 0; i--) |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 283 | if (est_power >= dfc->em_pd->table[i].power) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 284 | break; |
| 285 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 286 | *state = dfc->max_state - i; |
| 287 | dfc->capped_state = *state; |
| 288 | |
Javi Merino | 9876b1a | 2015-09-10 18:09:31 +0100 | [diff] [blame] | 289 | trace_thermal_power_devfreq_limit(cdev, freq, *state, power); |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 290 | return 0; |
| 291 | } |
| 292 | |
| 293 | static struct thermal_cooling_device_ops devfreq_cooling_ops = { |
| 294 | .get_max_state = devfreq_cooling_get_max_state, |
| 295 | .get_cur_state = devfreq_cooling_get_cur_state, |
| 296 | .set_cur_state = devfreq_cooling_set_cur_state, |
| 297 | }; |
| 298 | |
| 299 | /** |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 300 | * devfreq_cooling_gen_tables() - Generate frequency table. |
| 301 | * @dfc: Pointer to devfreq cooling device. |
| 302 | * @num_opps: Number of OPPs |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 303 | * |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 304 | * Generate frequency table which holds the frequencies in descending |
| 305 | * order. That way its indexed by cooling device state. This is for |
| 306 | * compatibility with drivers which do not register Energy Model. |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 307 | * |
| 308 | * Return: 0 on success, negative error code on failure. |
| 309 | */ |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 310 | static int devfreq_cooling_gen_tables(struct devfreq_cooling_device *dfc, |
| 311 | int num_opps) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 312 | { |
| 313 | struct devfreq *df = dfc->devfreq; |
| 314 | struct device *dev = df->dev.parent; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 315 | unsigned long freq; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 316 | int i; |
| 317 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 318 | dfc->freq_table = kcalloc(num_opps, sizeof(*dfc->freq_table), |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 319 | GFP_KERNEL); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 320 | if (!dfc->freq_table) |
| 321 | return -ENOMEM; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 322 | |
| 323 | for (i = 0, freq = ULONG_MAX; i < num_opps; i++, freq--) { |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 324 | struct dev_pm_opp *opp; |
| 325 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 326 | opp = dev_pm_opp_find_freq_floor(dev, &freq); |
| 327 | if (IS_ERR(opp)) { |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 328 | kfree(dfc->freq_table); |
| 329 | return PTR_ERR(opp); |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 330 | } |
| 331 | |
Viresh Kumar | 8a31d9d9 | 2017-01-23 10:11:47 +0530 | [diff] [blame] | 332 | dev_pm_opp_put(opp); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 333 | dfc->freq_table[i] = freq; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 334 | } |
| 335 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 336 | return 0; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 337 | } |
| 338 | |
| 339 | /** |
| 340 | * of_devfreq_cooling_register_power() - Register devfreq cooling device, |
| 341 | * with OF and power information. |
| 342 | * @np: Pointer to OF device_node. |
| 343 | * @df: Pointer to devfreq device. |
| 344 | * @dfc_power: Pointer to devfreq_cooling_power. |
| 345 | * |
| 346 | * Register a devfreq cooling device. The available OPPs must be |
| 347 | * registered on the device. |
| 348 | * |
| 349 | * If @dfc_power is provided, the cooling device is registered with the |
| 350 | * power extensions. For the power extensions to work correctly, |
| 351 | * devfreq should use the simple_ondemand governor, other governors |
| 352 | * are not currently supported. |
| 353 | */ |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 354 | struct thermal_cooling_device * |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 355 | of_devfreq_cooling_register_power(struct device_node *np, struct devfreq *df, |
| 356 | struct devfreq_cooling_power *dfc_power) |
| 357 | { |
| 358 | struct thermal_cooling_device *cdev; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 359 | struct device *dev = df->dev.parent; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 360 | struct devfreq_cooling_device *dfc; |
Daniel Lezcano | f8d354e | 2021-03-14 12:13:31 +0100 | [diff] [blame] | 361 | char *name; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 362 | int err, num_opps; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 363 | |
| 364 | dfc = kzalloc(sizeof(*dfc), GFP_KERNEL); |
| 365 | if (!dfc) |
| 366 | return ERR_PTR(-ENOMEM); |
| 367 | |
| 368 | dfc->devfreq = df; |
| 369 | |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 370 | dfc->em_pd = em_pd_get(dev); |
| 371 | if (dfc->em_pd) { |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 372 | devfreq_cooling_ops.get_requested_power = |
| 373 | devfreq_cooling_get_requested_power; |
| 374 | devfreq_cooling_ops.state2power = devfreq_cooling_state2power; |
| 375 | devfreq_cooling_ops.power2state = devfreq_cooling_power2state; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 376 | |
| 377 | dfc->power_ops = dfc_power; |
| 378 | |
Lukasz Luba | 4401117 | 2020-12-15 15:42:21 +0000 | [diff] [blame] | 379 | num_opps = em_pd_nr_perf_states(dfc->em_pd); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 380 | } else { |
| 381 | /* Backward compatibility for drivers which do not use IPA */ |
| 382 | dev_dbg(dev, "missing EM for cooling device\n"); |
| 383 | |
| 384 | num_opps = dev_pm_opp_get_opp_count(dev); |
| 385 | |
| 386 | err = devfreq_cooling_gen_tables(dfc, num_opps); |
| 387 | if (err) |
| 388 | goto free_dfc; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 389 | } |
| 390 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 391 | if (num_opps <= 0) { |
| 392 | err = -EINVAL; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 393 | goto free_dfc; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 394 | } |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 395 | |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 396 | /* max_state is an index, not a counter */ |
| 397 | dfc->max_state = num_opps - 1; |
| 398 | |
| 399 | err = dev_pm_qos_add_request(dev, &dfc->req_max_freq, |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 400 | DEV_PM_QOS_MAX_FREQUENCY, |
| 401 | PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); |
Matthew Wilcox | 2f96c03 | 2016-12-21 09:47:06 -0800 | [diff] [blame] | 402 | if (err < 0) |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 403 | goto free_table; |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 404 | |
Daniel Lezcano | 9aa80ab | 2021-03-19 21:24:23 +0100 | [diff] [blame] | 405 | err = -ENOMEM; |
Daniel Lezcano | f8d354e | 2021-03-14 12:13:31 +0100 | [diff] [blame] | 406 | name = kasprintf(GFP_KERNEL, "devfreq-%s", dev_name(dev)); |
| 407 | if (!name) |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 408 | goto remove_qos_req; |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 409 | |
Daniel Lezcano | f8d354e | 2021-03-14 12:13:31 +0100 | [diff] [blame] | 410 | cdev = thermal_of_cooling_device_register(np, name, dfc, |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 411 | &devfreq_cooling_ops); |
Daniel Lezcano | f8d354e | 2021-03-14 12:13:31 +0100 | [diff] [blame] | 412 | kfree(name); |
| 413 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 414 | if (IS_ERR(cdev)) { |
| 415 | err = PTR_ERR(cdev); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 416 | dev_err(dev, |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 417 | "Failed to register devfreq cooling device (%d)\n", |
| 418 | err); |
Daniel Lezcano | f8d354e | 2021-03-14 12:13:31 +0100 | [diff] [blame] | 419 | goto remove_qos_req; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 420 | } |
| 421 | |
| 422 | dfc->cdev = cdev; |
| 423 | |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 424 | return cdev; |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 425 | |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 426 | remove_qos_req: |
| 427 | dev_pm_qos_remove_request(&dfc->req_max_freq); |
Lukasz Luba | 615510f | 2020-12-10 14:30:13 +0000 | [diff] [blame] | 428 | free_table: |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 429 | kfree(dfc->freq_table); |
| 430 | free_dfc: |
| 431 | kfree(dfc); |
| 432 | |
| 433 | return ERR_PTR(err); |
| 434 | } |
| 435 | EXPORT_SYMBOL_GPL(of_devfreq_cooling_register_power); |
| 436 | |
| 437 | /** |
| 438 | * of_devfreq_cooling_register() - Register devfreq cooling device, |
| 439 | * with OF information. |
| 440 | * @np: Pointer to OF device_node. |
| 441 | * @df: Pointer to devfreq device. |
| 442 | */ |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 443 | struct thermal_cooling_device * |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 444 | of_devfreq_cooling_register(struct device_node *np, struct devfreq *df) |
| 445 | { |
| 446 | return of_devfreq_cooling_register_power(np, df, NULL); |
| 447 | } |
| 448 | EXPORT_SYMBOL_GPL(of_devfreq_cooling_register); |
| 449 | |
| 450 | /** |
| 451 | * devfreq_cooling_register() - Register devfreq cooling device. |
| 452 | * @df: Pointer to devfreq device. |
| 453 | */ |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 454 | struct thermal_cooling_device *devfreq_cooling_register(struct devfreq *df) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 455 | { |
| 456 | return of_devfreq_cooling_register(NULL, df); |
| 457 | } |
| 458 | EXPORT_SYMBOL_GPL(devfreq_cooling_register); |
| 459 | |
| 460 | /** |
Yang Li | 8b2ea89 | 2021-06-09 15:22:30 +0800 | [diff] [blame] | 461 | * devfreq_cooling_em_register() - Register devfreq cooling device with |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 462 | * power information and automatically register Energy Model (EM) |
| 463 | * @df: Pointer to devfreq device. |
| 464 | * @dfc_power: Pointer to devfreq_cooling_power. |
| 465 | * |
| 466 | * Register a devfreq cooling device and automatically register EM. The |
| 467 | * available OPPs must be registered for the device. |
| 468 | * |
| 469 | * If @dfc_power is provided, the cooling device is registered with the |
| 470 | * power extensions. It is using the simple Energy Model which requires |
| 471 | * "dynamic-power-coefficient" a devicetree property. To not break drivers |
| 472 | * which miss that DT property, the function won't bail out when the EM |
| 473 | * registration failed. The cooling device will be registered if everything |
| 474 | * else is OK. |
| 475 | */ |
| 476 | struct thermal_cooling_device * |
| 477 | devfreq_cooling_em_register(struct devfreq *df, |
| 478 | struct devfreq_cooling_power *dfc_power) |
| 479 | { |
| 480 | struct thermal_cooling_device *cdev; |
| 481 | struct device *dev; |
| 482 | int ret; |
| 483 | |
| 484 | if (IS_ERR_OR_NULL(df)) |
| 485 | return ERR_PTR(-EINVAL); |
| 486 | |
| 487 | dev = df->dev.parent; |
| 488 | |
| 489 | ret = dev_pm_opp_of_register_em(dev, NULL); |
| 490 | if (ret) |
| 491 | dev_dbg(dev, "Unable to register EM for devfreq cooling device (%d)\n", |
| 492 | ret); |
| 493 | |
| 494 | cdev = of_devfreq_cooling_register_power(dev->of_node, df, dfc_power); |
| 495 | |
| 496 | if (IS_ERR_OR_NULL(cdev)) |
| 497 | em_dev_unregister_perf_domain(dev); |
| 498 | |
| 499 | return cdev; |
| 500 | } |
| 501 | EXPORT_SYMBOL_GPL(devfreq_cooling_em_register); |
| 502 | |
| 503 | /** |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 504 | * devfreq_cooling_unregister() - Unregister devfreq cooling device. |
Amit Kucheria | 1b5cb95 | 2019-11-20 21:15:13 +0530 | [diff] [blame] | 505 | * @cdev: Pointer to devfreq cooling device to unregister. |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 506 | * |
| 507 | * Unregisters devfreq cooling device and related Energy Model if it was |
| 508 | * present. |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 509 | */ |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 510 | void devfreq_cooling_unregister(struct thermal_cooling_device *cdev) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 511 | { |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 512 | struct devfreq_cooling_device *dfc; |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 513 | struct device *dev; |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 514 | |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 515 | if (IS_ERR_OR_NULL(cdev)) |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 516 | return; |
| 517 | |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 518 | dfc = cdev->devdata; |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 519 | dev = dfc->devfreq->dev.parent; |
Javi Merino | 3c99c2c | 2015-11-02 19:03:03 +0000 | [diff] [blame] | 520 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 521 | thermal_cooling_device_unregister(dfc->cdev); |
Matthias Kaehlcke | 04fa9c8 | 2020-03-18 11:45:46 +0000 | [diff] [blame] | 522 | dev_pm_qos_remove_request(&dfc->req_max_freq); |
Lukasz Luba | 84e0d87 | 2020-12-10 14:30:12 +0000 | [diff] [blame] | 523 | |
| 524 | em_dev_unregister_perf_domain(dev); |
| 525 | |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 526 | kfree(dfc->freq_table); |
Ørjan Eide | a76caf5 | 2015-09-10 18:09:30 +0100 | [diff] [blame] | 527 | kfree(dfc); |
| 528 | } |
| 529 | EXPORT_SYMBOL_GPL(devfreq_cooling_unregister); |