Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v5.4 444 lines 13 kB view raw
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * drivers/thermal/clock_cooling.c 4 * 5 * Copyright (C) 2014 Eduardo Valentin <edubezval@gmail.com> 6 * 7 * Copyright (C) 2013 Texas Instruments Inc. 8 * Contact: Eduardo Valentin <eduardo.valentin@ti.com> 9 * 10 * Highly based on cpu_cooling.c. 11 * Copyright (C) 2012 Samsung Electronics Co., Ltd(http://www.samsung.com) 12 * Copyright (C) 2012 Amit Daniel <amit.kachhap@linaro.org> 13 */ 14#include <linux/clk.h> 15#include <linux/cpufreq.h> 16#include <linux/device.h> 17#include <linux/err.h> 18#include <linux/idr.h> 19#include <linux/mutex.h> 20#include <linux/pm_opp.h> 21#include <linux/slab.h> 22#include <linux/thermal.h> 23#include <linux/clock_cooling.h> 24 25/** 26 * struct clock_cooling_device - data for cooling device with clock 27 * @id: unique integer value corresponding to each clock_cooling_device 28 * registered. 29 * @dev: struct device pointer to the device being used to cool off using 30 * clock frequencies. 31 * @cdev: thermal_cooling_device pointer to keep track of the 32 * registered cooling device. 33 * @clk_rate_change_nb: reference to notifier block used to receive clock 34 * rate changes. 35 * @freq_table: frequency table used to keep track of available frequencies. 36 * @clock_state: integer value representing the current state of clock 37 * cooling devices. 38 * @clock_val: integer value representing the absolute value of the clipped 39 * frequency. 40 * @clk: struct clk reference used to enforce clock limits. 41 * @lock: mutex lock to protect this struct. 42 * 43 * This structure is required for keeping information of each 44 * clock_cooling_device registered. In order to prevent corruption of this a 45 * mutex @lock is used. 46 */ 47struct clock_cooling_device { 48 int id; 49 struct device *dev; 50 struct thermal_cooling_device *cdev; 51 struct notifier_block clk_rate_change_nb; 52 struct cpufreq_frequency_table *freq_table; 53 unsigned long clock_state; 54 unsigned long clock_val; 55 struct clk *clk; 56 struct mutex lock; /* lock to protect the content of this struct */ 57}; 58#define to_clock_cooling_device(x) \ 59 container_of(x, struct clock_cooling_device, clk_rate_change_nb) 60static DEFINE_IDA(clock_ida); 61 62/* Below code defines functions to be used for clock as cooling device */ 63 64enum clock_cooling_property { 65 GET_LEVEL, 66 GET_FREQ, 67 GET_MAXL, 68}; 69 70/** 71 * clock_cooling_get_property - fetch a property of interest for a give cpu. 72 * @ccdev: clock cooling device reference 73 * @input: query parameter 74 * @output: query return 75 * @property: type of query (frequency, level, max level) 76 * 77 * This is the common function to 78 * 1. get maximum clock cooling states 79 * 2. translate frequency to cooling state 80 * 3. translate cooling state to frequency 81 * Note that the code may be not in good shape 82 * but it is written in this way in order to: 83 * a) reduce duplicate code as most of the code can be shared. 84 * b) make sure the logic is consistent when translating between 85 * cooling states and frequencies. 86 * 87 * Return: 0 on success, -EINVAL when invalid parameters are passed. 88 */ 89static int clock_cooling_get_property(struct clock_cooling_device *ccdev, 90 unsigned long input, 91 unsigned long *output, 92 enum clock_cooling_property property) 93{ 94 int i; 95 unsigned long max_level = 0, level = 0; 96 unsigned int freq = CPUFREQ_ENTRY_INVALID; 97 int descend = -1; 98 struct cpufreq_frequency_table *pos, *table = ccdev->freq_table; 99 100 if (!output) 101 return -EINVAL; 102 103 if (!table) 104 return -EINVAL; 105 106 cpufreq_for_each_valid_entry(pos, table) { 107 /* ignore duplicate entry */ 108 if (freq == pos->frequency) 109 continue; 110 111 /* get the frequency order */ 112 if (freq != CPUFREQ_ENTRY_INVALID && descend == -1) 113 descend = freq > pos->frequency; 114 115 freq = pos->frequency; 116 max_level++; 117 } 118 119 /* No valid cpu frequency entry */ 120 if (max_level == 0) 121 return -EINVAL; 122 123 /* max_level is an index, not a counter */ 124 max_level--; 125 126 /* get max level */ 127 if (property == GET_MAXL) { 128 *output = max_level; 129 return 0; 130 } 131 132 if (property == GET_FREQ) 133 level = descend ? input : (max_level - input); 134 135 i = 0; 136 cpufreq_for_each_valid_entry(pos, table) { 137 /* ignore duplicate entry */ 138 if (freq == pos->frequency) 139 continue; 140 141 /* now we have a valid frequency entry */ 142 freq = pos->frequency; 143 144 if (property == GET_LEVEL && (unsigned int)input == freq) { 145 /* get level by frequency */ 146 *output = descend ? i : (max_level - i); 147 return 0; 148 } 149 if (property == GET_FREQ && level == i) { 150 /* get frequency by level */ 151 *output = freq; 152 return 0; 153 } 154 i++; 155 } 156 157 return -EINVAL; 158} 159 160/** 161 * clock_cooling_get_level - return the cooling level of given clock cooling. 162 * @cdev: reference of a thermal cooling device of used as clock cooling device 163 * @freq: the frequency of interest 164 * 165 * This function will match the cooling level corresponding to the 166 * requested @freq and return it. 167 * 168 * Return: The matched cooling level on success or THERMAL_CSTATE_INVALID 169 * otherwise. 170 */ 171unsigned long clock_cooling_get_level(struct thermal_cooling_device *cdev, 172 unsigned long freq) 173{ 174 struct clock_cooling_device *ccdev = cdev->devdata; 175 unsigned long val; 176 177 if (clock_cooling_get_property(ccdev, (unsigned long)freq, &val, 178 GET_LEVEL)) 179 return THERMAL_CSTATE_INVALID; 180 181 return val; 182} 183EXPORT_SYMBOL_GPL(clock_cooling_get_level); 184 185/** 186 * clock_cooling_get_frequency - get the absolute value of frequency from level. 187 * @ccdev: clock cooling device reference 188 * @level: cooling level 189 * 190 * This function matches cooling level with frequency. Based on a cooling level 191 * of frequency, equals cooling state of cpu cooling device, it will return 192 * the corresponding frequency. 193 * e.g level=0 --> 1st MAX FREQ, level=1 ---> 2nd MAX FREQ, .... etc 194 * 195 * Return: 0 on error, the corresponding frequency otherwise. 196 */ 197static unsigned long 198clock_cooling_get_frequency(struct clock_cooling_device *ccdev, 199 unsigned long level) 200{ 201 int ret = 0; 202 unsigned long freq; 203 204 ret = clock_cooling_get_property(ccdev, level, &freq, GET_FREQ); 205 if (ret) 206 return 0; 207 208 return freq; 209} 210 211/** 212 * clock_cooling_apply - function to apply frequency clipping. 213 * @ccdev: clock_cooling_device pointer containing frequency clipping data. 214 * @cooling_state: value of the cooling state. 215 * 216 * Function used to make sure the clock layer is aware of current thermal 217 * limits. The limits are applied by updating the clock rate in case it is 218 * higher than the corresponding frequency based on the requested cooling_state. 219 * 220 * Return: 0 on success, an error code otherwise (-EINVAL in case wrong 221 * cooling state). 222 */ 223static int clock_cooling_apply(struct clock_cooling_device *ccdev, 224 unsigned long cooling_state) 225{ 226 unsigned long clip_freq, cur_freq; 227 int ret = 0; 228 229 /* Here we write the clipping */ 230 /* Check if the old cooling action is same as new cooling action */ 231 if (ccdev->clock_state == cooling_state) 232 return 0; 233 234 clip_freq = clock_cooling_get_frequency(ccdev, cooling_state); 235 if (!clip_freq) 236 return -EINVAL; 237 238 cur_freq = clk_get_rate(ccdev->clk); 239 240 mutex_lock(&ccdev->lock); 241 ccdev->clock_state = cooling_state; 242 ccdev->clock_val = clip_freq; 243 /* enforce clock level */ 244 if (cur_freq > clip_freq) 245 ret = clk_set_rate(ccdev->clk, clip_freq); 246 mutex_unlock(&ccdev->lock); 247 248 return ret; 249} 250 251/** 252 * clock_cooling_clock_notifier - notifier callback on clock rate changes. 253 * @nb: struct notifier_block * with callback info. 254 * @event: value showing clock event for which this function invoked. 255 * @data: callback-specific data 256 * 257 * Callback to hijack the notification on clock transition. 258 * Every time there is a clock change, we intercept all pre change events 259 * and block the transition in case the new rate infringes thermal limits. 260 * 261 * Return: NOTIFY_DONE (success) or NOTIFY_BAD (new_rate > thermal limit). 262 */ 263static int clock_cooling_clock_notifier(struct notifier_block *nb, 264 unsigned long event, void *data) 265{ 266 struct clk_notifier_data *ndata = data; 267 struct clock_cooling_device *ccdev = to_clock_cooling_device(nb); 268 269 switch (event) { 270 case PRE_RATE_CHANGE: 271 /* 272 * checks on current state 273 * TODO: current method is not best we can find as it 274 * allows possibly voltage transitions, in case DVFS 275 * layer is also hijacking clock pre notifications. 276 */ 277 if (ndata->new_rate > ccdev->clock_val) 278 return NOTIFY_BAD; 279 /* fall through */ 280 case POST_RATE_CHANGE: 281 case ABORT_RATE_CHANGE: 282 default: 283 return NOTIFY_DONE; 284 } 285} 286 287/* clock cooling device thermal callback functions are defined below */ 288 289/** 290 * clock_cooling_get_max_state - callback function to get the max cooling state. 291 * @cdev: thermal cooling device pointer. 292 * @state: fill this variable with the max cooling state. 293 * 294 * Callback for the thermal cooling device to return the clock 295 * max cooling state. 296 * 297 * Return: 0 on success, an error code otherwise. 298 */ 299static int clock_cooling_get_max_state(struct thermal_cooling_device *cdev, 300 unsigned long *state) 301{ 302 struct clock_cooling_device *ccdev = cdev->devdata; 303 unsigned long count = 0; 304 int ret; 305 306 ret = clock_cooling_get_property(ccdev, 0, &count, GET_MAXL); 307 if (!ret) 308 *state = count; 309 310 return ret; 311} 312 313/** 314 * clock_cooling_get_cur_state - function to get the current cooling state. 315 * @cdev: thermal cooling device pointer. 316 * @state: fill this variable with the current cooling state. 317 * 318 * Callback for the thermal cooling device to return the clock 319 * current cooling state. 320 * 321 * Return: 0 (success) 322 */ 323static int clock_cooling_get_cur_state(struct thermal_cooling_device *cdev, 324 unsigned long *state) 325{ 326 struct clock_cooling_device *ccdev = cdev->devdata; 327 328 *state = ccdev->clock_state; 329 330 return 0; 331} 332 333/** 334 * clock_cooling_set_cur_state - function to set the current cooling state. 335 * @cdev: thermal cooling device pointer. 336 * @state: set this variable to the current cooling state. 337 * 338 * Callback for the thermal cooling device to change the clock cooling 339 * current cooling state. 340 * 341 * Return: 0 on success, an error code otherwise. 342 */ 343static int clock_cooling_set_cur_state(struct thermal_cooling_device *cdev, 344 unsigned long state) 345{ 346 struct clock_cooling_device *clock_device = cdev->devdata; 347 348 return clock_cooling_apply(clock_device, state); 349} 350 351/* Bind clock callbacks to thermal cooling device ops */ 352static struct thermal_cooling_device_ops const clock_cooling_ops = { 353 .get_max_state = clock_cooling_get_max_state, 354 .get_cur_state = clock_cooling_get_cur_state, 355 .set_cur_state = clock_cooling_set_cur_state, 356}; 357 358/** 359 * clock_cooling_register - function to create clock cooling device. 360 * @dev: struct device pointer to the device used as clock cooling device. 361 * @clock_name: string containing the clock used as cooling mechanism. 362 * 363 * This interface function registers the clock cooling device with the name 364 * "thermal-clock-%x". The cooling device is based on clock frequencies. 365 * The struct device is assumed to be capable of DVFS transitions. 366 * The OPP layer is used to fetch and fill the available frequencies for 367 * the referred device. The ordered frequency table is used to control 368 * the clock cooling device cooling states and to limit clock transitions 369 * based on the cooling state requested by the thermal framework. 370 * 371 * Return: a valid struct thermal_cooling_device pointer on success, 372 * on failure, it returns a corresponding ERR_PTR(). 373 */ 374struct thermal_cooling_device * 375clock_cooling_register(struct device *dev, const char *clock_name) 376{ 377 struct thermal_cooling_device *cdev; 378 struct clock_cooling_device *ccdev = NULL; 379 char dev_name[THERMAL_NAME_LENGTH]; 380 int ret = 0; 381 382 ccdev = devm_kzalloc(dev, sizeof(*ccdev), GFP_KERNEL); 383 if (!ccdev) 384 return ERR_PTR(-ENOMEM); 385 386 mutex_init(&ccdev->lock); 387 ccdev->dev = dev; 388 ccdev->clk = devm_clk_get(dev, clock_name); 389 if (IS_ERR(ccdev->clk)) 390 return ERR_CAST(ccdev->clk); 391 392 ret = ida_simple_get(&clock_ida, 0, 0, GFP_KERNEL); 393 if (ret < 0) 394 return ERR_PTR(ret); 395 ccdev->id = ret; 396 397 snprintf(dev_name, sizeof(dev_name), "thermal-clock-%d", ccdev->id); 398 399 cdev = thermal_cooling_device_register(dev_name, ccdev, 400 &clock_cooling_ops); 401 if (IS_ERR(cdev)) { 402 ida_simple_remove(&clock_ida, ccdev->id); 403 return ERR_PTR(-EINVAL); 404 } 405 ccdev->cdev = cdev; 406 ccdev->clk_rate_change_nb.notifier_call = clock_cooling_clock_notifier; 407 408 /* Assuming someone has already filled the opp table for this device */ 409 ret = dev_pm_opp_init_cpufreq_table(dev, &ccdev->freq_table); 410 if (ret) { 411 ida_simple_remove(&clock_ida, ccdev->id); 412 return ERR_PTR(ret); 413 } 414 ccdev->clock_state = 0; 415 ccdev->clock_val = clock_cooling_get_frequency(ccdev, 0); 416 417 clk_notifier_register(ccdev->clk, &ccdev->clk_rate_change_nb); 418 419 return cdev; 420} 421EXPORT_SYMBOL_GPL(clock_cooling_register); 422 423/** 424 * clock_cooling_unregister - function to remove clock cooling device. 425 * @cdev: thermal cooling device pointer. 426 * 427 * This interface function unregisters the "thermal-clock-%x" cooling device. 428 */ 429void clock_cooling_unregister(struct thermal_cooling_device *cdev) 430{ 431 struct clock_cooling_device *ccdev; 432 433 if (!cdev) 434 return; 435 436 ccdev = cdev->devdata; 437 438 clk_notifier_unregister(ccdev->clk, &ccdev->clk_rate_change_nb); 439 dev_pm_opp_free_cpufreq_table(ccdev->dev, &ccdev->freq_table); 440 441 thermal_cooling_device_unregister(ccdev->cdev); 442 ida_simple_remove(&clock_ida, ccdev->id); 443} 444EXPORT_SYMBOL_GPL(clock_cooling_unregister);