1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * db8500_thermal.c - DB8500 Thermal Management Implementation 4 * 5 * Copyright (C) 2012 ST-Ericsson 6 * Copyright (C) 2012 Linaro Ltd. 7 * 8 * Author: Hongbo Zhang <hongbo.zhang@linaro.com> 9 */ 10 11 #include <linux/cpu_cooling.h> 12 #include <linux/interrupt.h> 13 #include <linux/mfd/dbx500-prcmu.h> 14 #include <linux/module.h> 15 #include <linux/of.h> 16 #include <linux/platform_data/db8500_thermal.h> 17 #include <linux/platform_device.h> 18 #include <linux/slab.h> 19 #include <linux/thermal.h> 20 21 #define PRCMU_DEFAULT_MEASURE_TIME 0xFFF 22 #define PRCMU_DEFAULT_LOW_TEMP 0 23 24 struct db8500_thermal_zone { 25 struct thermal_zone_device *therm_dev; 26 struct mutex th_lock; 27 struct work_struct therm_work; 28 struct db8500_thsens_platform_data *trip_tab; 29 enum thermal_device_mode mode; 30 enum thermal_trend trend; 31 unsigned long cur_temp_pseudo; 32 unsigned int cur_index; 33 }; 34 35 /* Local function to check if thermal zone matches cooling devices */ 36 static int db8500_thermal_match_cdev(struct thermal_cooling_device *cdev, 37 struct db8500_trip_point *trip_point) 38 { 39 int i; 40 41 if (!strlen(cdev->type)) 42 return -EINVAL; 43 44 for (i = 0; i < COOLING_DEV_MAX; i++) { 45 if (!strcmp(trip_point->cdev_name[i], cdev->type)) 46 return 0; 47 } 48 49 return -ENODEV; 50 } 51 52 /* Callback to bind cooling device to thermal zone */ 53 static int db8500_cdev_bind(struct thermal_zone_device *thermal, 54 struct thermal_cooling_device *cdev) 55 { 56 struct db8500_thermal_zone *pzone = thermal->devdata; 57 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 58 unsigned long max_state, upper, lower; 59 int i, ret = -EINVAL; 60 61 cdev->ops->get_max_state(cdev, &max_state); 62 63 for (i = 0; i < ptrips->num_trips; i++) { 64 if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) 65 continue; 66 67 upper = lower = i > max_state ? max_state : i; 68 69 ret = thermal_zone_bind_cooling_device(thermal, i, cdev, 70 upper, lower, THERMAL_WEIGHT_DEFAULT); 71 72 dev_info(&cdev->device, "%s bind to %d: %d-%s\n", cdev->type, 73 i, ret, ret ? "fail" : "succeed"); 74 } 75 76 return ret; 77 } 78 79 /* Callback to unbind cooling device from thermal zone */ 80 static int db8500_cdev_unbind(struct thermal_zone_device *thermal, 81 struct thermal_cooling_device *cdev) 82 { 83 struct db8500_thermal_zone *pzone = thermal->devdata; 84 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 85 int i, ret = -EINVAL; 86 87 for (i = 0; i < ptrips->num_trips; i++) { 88 if (db8500_thermal_match_cdev(cdev, &ptrips->trip_points[i])) 89 continue; 90 91 ret = thermal_zone_unbind_cooling_device(thermal, i, cdev); 92 93 dev_info(&cdev->device, "%s unbind from %d: %s\n", cdev->type, 94 i, ret ? "fail" : "succeed"); 95 } 96 97 return ret; 98 } 99 100 /* Callback to get current temperature */ 101 static int db8500_sys_get_temp(struct thermal_zone_device *thermal, int *temp) 102 { 103 struct db8500_thermal_zone *pzone = thermal->devdata; 104 105 /* 106 * TODO: There is no PRCMU interface to get temperature data currently, 107 * so a pseudo temperature is returned , it works for thermal framework 108 * and this will be fixed when the PRCMU interface is available. 109 */ 110 *temp = pzone->cur_temp_pseudo; 111 112 return 0; 113 } 114 115 /* Callback to get temperature changing trend */ 116 static int db8500_sys_get_trend(struct thermal_zone_device *thermal, 117 int trip, enum thermal_trend *trend) 118 { 119 struct db8500_thermal_zone *pzone = thermal->devdata; 120 121 *trend = pzone->trend; 122 123 return 0; 124 } 125 126 /* Callback to get thermal zone mode */ 127 static int db8500_sys_get_mode(struct thermal_zone_device *thermal, 128 enum thermal_device_mode *mode) 129 { 130 struct db8500_thermal_zone *pzone = thermal->devdata; 131 132 mutex_lock(&pzone->th_lock); 133 *mode = pzone->mode; 134 mutex_unlock(&pzone->th_lock); 135 136 return 0; 137 } 138 139 /* Callback to set thermal zone mode */ 140 static int db8500_sys_set_mode(struct thermal_zone_device *thermal, 141 enum thermal_device_mode mode) 142 { 143 struct db8500_thermal_zone *pzone = thermal->devdata; 144 145 mutex_lock(&pzone->th_lock); 146 147 pzone->mode = mode; 148 if (mode == THERMAL_DEVICE_ENABLED) 149 schedule_work(&pzone->therm_work); 150 151 mutex_unlock(&pzone->th_lock); 152 153 return 0; 154 } 155 156 /* Callback to get trip point type */ 157 static int db8500_sys_get_trip_type(struct thermal_zone_device *thermal, 158 int trip, enum thermal_trip_type *type) 159 { 160 struct db8500_thermal_zone *pzone = thermal->devdata; 161 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 162 163 if (trip >= ptrips->num_trips) 164 return -EINVAL; 165 166 *type = ptrips->trip_points[trip].type; 167 168 return 0; 169 } 170 171 /* Callback to get trip point temperature */ 172 static int db8500_sys_get_trip_temp(struct thermal_zone_device *thermal, 173 int trip, int *temp) 174 { 175 struct db8500_thermal_zone *pzone = thermal->devdata; 176 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 177 178 if (trip >= ptrips->num_trips) 179 return -EINVAL; 180 181 *temp = ptrips->trip_points[trip].temp; 182 183 return 0; 184 } 185 186 /* Callback to get critical trip point temperature */ 187 static int db8500_sys_get_crit_temp(struct thermal_zone_device *thermal, 188 int *temp) 189 { 190 struct db8500_thermal_zone *pzone = thermal->devdata; 191 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 192 int i; 193 194 for (i = ptrips->num_trips - 1; i > 0; i--) { 195 if (ptrips->trip_points[i].type == THERMAL_TRIP_CRITICAL) { 196 *temp = ptrips->trip_points[i].temp; 197 return 0; 198 } 199 } 200 201 return -EINVAL; 202 } 203 204 static struct thermal_zone_device_ops thdev_ops = { 205 .bind = db8500_cdev_bind, 206 .unbind = db8500_cdev_unbind, 207 .get_temp = db8500_sys_get_temp, 208 .get_trend = db8500_sys_get_trend, 209 .get_mode = db8500_sys_get_mode, 210 .set_mode = db8500_sys_set_mode, 211 .get_trip_type = db8500_sys_get_trip_type, 212 .get_trip_temp = db8500_sys_get_trip_temp, 213 .get_crit_temp = db8500_sys_get_crit_temp, 214 }; 215 216 static void db8500_thermal_update_config(struct db8500_thermal_zone *pzone, 217 unsigned int idx, enum thermal_trend trend, 218 unsigned long next_low, unsigned long next_high) 219 { 220 prcmu_stop_temp_sense(); 221 222 pzone->cur_index = idx; 223 pzone->cur_temp_pseudo = (next_low + next_high)/2; 224 pzone->trend = trend; 225 226 prcmu_config_hotmon((u8)(next_low/1000), (u8)(next_high/1000)); 227 prcmu_start_temp_sense(PRCMU_DEFAULT_MEASURE_TIME); 228 } 229 230 static irqreturn_t prcmu_low_irq_handler(int irq, void *irq_data) 231 { 232 struct db8500_thermal_zone *pzone = irq_data; 233 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 234 unsigned int idx = pzone->cur_index; 235 unsigned long next_low, next_high; 236 237 if (unlikely(idx == 0)) 238 /* Meaningless for thermal management, ignoring it */ 239 return IRQ_HANDLED; 240 241 if (idx == 1) { 242 next_high = ptrips->trip_points[0].temp; 243 next_low = PRCMU_DEFAULT_LOW_TEMP; 244 } else { 245 next_high = ptrips->trip_points[idx-1].temp; 246 next_low = ptrips->trip_points[idx-2].temp; 247 } 248 idx -= 1; 249 250 db8500_thermal_update_config(pzone, idx, THERMAL_TREND_DROPPING, 251 next_low, next_high); 252 253 dev_dbg(&pzone->therm_dev->device, 254 "PRCMU set max %ld, min %ld\n", next_high, next_low); 255 256 schedule_work(&pzone->therm_work); 257 258 return IRQ_HANDLED; 259 } 260 261 static irqreturn_t prcmu_high_irq_handler(int irq, void *irq_data) 262 { 263 struct db8500_thermal_zone *pzone = irq_data; 264 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 265 unsigned int idx = pzone->cur_index; 266 unsigned long next_low, next_high; 267 268 if (idx < ptrips->num_trips - 1) { 269 next_high = ptrips->trip_points[idx+1].temp; 270 next_low = ptrips->trip_points[idx].temp; 271 idx += 1; 272 273 db8500_thermal_update_config(pzone, idx, THERMAL_TREND_RAISING, 274 next_low, next_high); 275 276 dev_dbg(&pzone->therm_dev->device, 277 "PRCMU set max %ld, min %ld\n", next_high, next_low); 278 } else if (idx == ptrips->num_trips - 1) 279 pzone->cur_temp_pseudo = ptrips->trip_points[idx].temp + 1; 280 281 schedule_work(&pzone->therm_work); 282 283 return IRQ_HANDLED; 284 } 285 286 static void db8500_thermal_work(struct work_struct *work) 287 { 288 enum thermal_device_mode cur_mode; 289 struct db8500_thermal_zone *pzone; 290 291 pzone = container_of(work, struct db8500_thermal_zone, therm_work); 292 293 mutex_lock(&pzone->th_lock); 294 cur_mode = pzone->mode; 295 mutex_unlock(&pzone->th_lock); 296 297 if (cur_mode == THERMAL_DEVICE_DISABLED) 298 return; 299 300 thermal_zone_device_update(pzone->therm_dev, THERMAL_EVENT_UNSPECIFIED); 301 dev_dbg(&pzone->therm_dev->device, "thermal work finished.\n"); 302 } 303 304 #ifdef CONFIG_OF 305 static struct db8500_thsens_platform_data* 306 db8500_thermal_parse_dt(struct platform_device *pdev) 307 { 308 struct db8500_thsens_platform_data *ptrips; 309 struct device_node *np = pdev->dev.of_node; 310 char prop_name[32]; 311 const char *tmp_str; 312 u32 tmp_data; 313 int i, j; 314 315 ptrips = devm_kzalloc(&pdev->dev, sizeof(*ptrips), GFP_KERNEL); 316 if (!ptrips) 317 return NULL; 318 319 if (of_property_read_u32(np, "num-trips", &tmp_data)) 320 goto err_parse_dt; 321 322 if (tmp_data > THERMAL_MAX_TRIPS) 323 goto err_parse_dt; 324 325 ptrips->num_trips = tmp_data; 326 327 for (i = 0; i < ptrips->num_trips; i++) { 328 sprintf(prop_name, "trip%d-temp", i); 329 if (of_property_read_u32(np, prop_name, &tmp_data)) 330 goto err_parse_dt; 331 332 ptrips->trip_points[i].temp = tmp_data; 333 334 sprintf(prop_name, "trip%d-type", i); 335 if (of_property_read_string(np, prop_name, &tmp_str)) 336 goto err_parse_dt; 337 338 if (!strcmp(tmp_str, "active")) 339 ptrips->trip_points[i].type = THERMAL_TRIP_ACTIVE; 340 else if (!strcmp(tmp_str, "passive")) 341 ptrips->trip_points[i].type = THERMAL_TRIP_PASSIVE; 342 else if (!strcmp(tmp_str, "hot")) 343 ptrips->trip_points[i].type = THERMAL_TRIP_HOT; 344 else if (!strcmp(tmp_str, "critical")) 345 ptrips->trip_points[i].type = THERMAL_TRIP_CRITICAL; 346 else 347 goto err_parse_dt; 348 349 sprintf(prop_name, "trip%d-cdev-num", i); 350 if (of_property_read_u32(np, prop_name, &tmp_data)) 351 goto err_parse_dt; 352 353 if (tmp_data > COOLING_DEV_MAX) 354 goto err_parse_dt; 355 356 for (j = 0; j < tmp_data; j++) { 357 sprintf(prop_name, "trip%d-cdev-name%d", i, j); 358 if (of_property_read_string(np, prop_name, &tmp_str)) 359 goto err_parse_dt; 360 361 if (strlen(tmp_str) >= THERMAL_NAME_LENGTH) 362 goto err_parse_dt; 363 364 strcpy(ptrips->trip_points[i].cdev_name[j], tmp_str); 365 } 366 } 367 return ptrips; 368 369 err_parse_dt: 370 dev_err(&pdev->dev, "Parsing device tree data error.\n"); 371 return NULL; 372 } 373 #else 374 static inline struct db8500_thsens_platform_data* 375 db8500_thermal_parse_dt(struct platform_device *pdev) 376 { 377 return NULL; 378 } 379 #endif 380 381 static int db8500_thermal_probe(struct platform_device *pdev) 382 { 383 struct db8500_thermal_zone *pzone = NULL; 384 struct db8500_thsens_platform_data *ptrips = NULL; 385 struct device_node *np = pdev->dev.of_node; 386 int low_irq, high_irq, ret = 0; 387 unsigned long dft_low, dft_high; 388 389 if (np) 390 ptrips = db8500_thermal_parse_dt(pdev); 391 else 392 ptrips = dev_get_platdata(&pdev->dev); 393 394 if (!ptrips) 395 return -EINVAL; 396 397 pzone = devm_kzalloc(&pdev->dev, sizeof(*pzone), GFP_KERNEL); 398 if (!pzone) 399 return -ENOMEM; 400 401 mutex_init(&pzone->th_lock); 402 mutex_lock(&pzone->th_lock); 403 404 pzone->mode = THERMAL_DEVICE_DISABLED; 405 pzone->trip_tab = ptrips; 406 407 INIT_WORK(&pzone->therm_work, db8500_thermal_work); 408 409 low_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); 410 if (low_irq < 0) { 411 dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed.\n"); 412 ret = low_irq; 413 goto out_unlock; 414 } 415 416 ret = devm_request_threaded_irq(&pdev->dev, low_irq, NULL, 417 prcmu_low_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, 418 "dbx500_temp_low", pzone); 419 if (ret < 0) { 420 dev_err(&pdev->dev, "Failed to allocate temp low irq.\n"); 421 goto out_unlock; 422 } 423 424 high_irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); 425 if (high_irq < 0) { 426 dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed.\n"); 427 ret = high_irq; 428 goto out_unlock; 429 } 430 431 ret = devm_request_threaded_irq(&pdev->dev, high_irq, NULL, 432 prcmu_high_irq_handler, IRQF_NO_SUSPEND | IRQF_ONESHOT, 433 "dbx500_temp_high", pzone); 434 if (ret < 0) { 435 dev_err(&pdev->dev, "Failed to allocate temp high irq.\n"); 436 goto out_unlock; 437 } 438 439 pzone->therm_dev = thermal_zone_device_register("db8500_thermal_zone", 440 ptrips->num_trips, 0, pzone, &thdev_ops, NULL, 0, 0); 441 442 if (IS_ERR(pzone->therm_dev)) { 443 dev_err(&pdev->dev, "Register thermal zone device failed.\n"); 444 ret = PTR_ERR(pzone->therm_dev); 445 goto out_unlock; 446 } 447 dev_info(&pdev->dev, "Thermal zone device registered.\n"); 448 449 dft_low = PRCMU_DEFAULT_LOW_TEMP; 450 dft_high = ptrips->trip_points[0].temp; 451 452 db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, 453 dft_low, dft_high); 454 455 platform_set_drvdata(pdev, pzone); 456 pzone->mode = THERMAL_DEVICE_ENABLED; 457 458 out_unlock: 459 mutex_unlock(&pzone->th_lock); 460 461 return ret; 462 } 463 464 static int db8500_thermal_remove(struct platform_device *pdev) 465 { 466 struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); 467 468 thermal_zone_device_unregister(pzone->therm_dev); 469 cancel_work_sync(&pzone->therm_work); 470 mutex_destroy(&pzone->th_lock); 471 472 return 0; 473 } 474 475 static int db8500_thermal_suspend(struct platform_device *pdev, 476 pm_message_t state) 477 { 478 struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); 479 480 flush_work(&pzone->therm_work); 481 prcmu_stop_temp_sense(); 482 483 return 0; 484 } 485 486 static int db8500_thermal_resume(struct platform_device *pdev) 487 { 488 struct db8500_thermal_zone *pzone = platform_get_drvdata(pdev); 489 struct db8500_thsens_platform_data *ptrips = pzone->trip_tab; 490 unsigned long dft_low, dft_high; 491 492 dft_low = PRCMU_DEFAULT_LOW_TEMP; 493 dft_high = ptrips->trip_points[0].temp; 494 495 db8500_thermal_update_config(pzone, 0, THERMAL_TREND_STABLE, 496 dft_low, dft_high); 497 498 return 0; 499 } 500 501 #ifdef CONFIG_OF 502 static const struct of_device_id db8500_thermal_match[] = { 503 { .compatible = "stericsson,db8500-thermal" }, 504 {}, 505 }; 506 MODULE_DEVICE_TABLE(of, db8500_thermal_match); 507 #endif 508 509 static struct platform_driver db8500_thermal_driver = { 510 .driver = { 511 .name = "db8500-thermal", 512 .of_match_table = of_match_ptr(db8500_thermal_match), 513 }, 514 .probe = db8500_thermal_probe, 515 .suspend = db8500_thermal_suspend, 516 .resume = db8500_thermal_resume, 517 .remove = db8500_thermal_remove, 518 }; 519 520 module_platform_driver(db8500_thermal_driver); 521 522 MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@stericsson.com>"); 523 MODULE_DESCRIPTION("DB8500 thermal driver"); 524 MODULE_LICENSE("GPL"); 525