1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd 4 */ 5 #include <common.h> 6 #include <dm.h> 7 #include <clk.h> 8 #include <dvfs.h> 9 #include <thermal.h> 10 #include <linux/list.h> 11 12 #include <asm/arch/clock.h> 13 #include <power/regulator.h> 14 #ifdef CONFIG_ROCKCHIP_DMC 15 #include <asm/arch/rockchip_dmc.h> 16 #endif 17 18 /* 19 * # This is a simple wide temperature(ie. wtemp) dvfs driver, the policy is: 20 * 21 * 1. U-Boot parse cpu/dmc opp table from kernel dtb, anyone of 22 * "rockchip,low-temp = <...>" and "rockchip,high-temp = <...>" present in 23 * cpu/dmc nodes means wtemp is enabled. 24 * 25 * 1.1. When temperature trigger "rockchip,low-temp", increase 50mv voltage 26 * as target voltage. If target voltage is over "rockchip,max-volt", 27 * just set "rockchip,max-volt" as target voltage and lower 2 level freq, 28 * 29 * 1.2. When temperature trigger "rockchip,high-temp", just apply opp table[0] 30 * voltage and freq. 31 * 32 * 2. U-Boot parse cpu/dmc thermal zone "trip-point-0" temperature from kernel 33 * dtb, and apply the same rules as above [1.2] policy. 34 * 35 * 36 * # The dvfs policy apply moment is: 37 * 38 * 1. Appy it after clk and regulator drivers setup; 39 * 2. Repeat apply it by CONFIG_PREBOOT command until achieve the target 40 * temperature. user should add: #define CONFIG_PREBOOT "dvfs repeat" and 41 * assign repeat property in dts: 42 * 43 * uboot-wide-temperature { 44 * status = "okay"; 45 * compatible = "rockchip,uboot-wide-temperature"; 46 * 47 * cpu,low-temp-repeat; 48 * cpu,high-temp-repeat; 49 * dmc,low-temp-repeat; 50 * dmc,high-temp-repeat; 51 * }; 52 */ 53 54 #define FDT_PATH_CPUS "/cpus" 55 #define FDT_PATH_DMC "/dmc" 56 #define FDT_PATH_THREMAL_TRIP_POINT0 \ 57 "/thermal-zones/soc-thermal/trips/trip-point-0" 58 #define FDT_PATH_THREMAL_COOLING_MAPS \ 59 "/thermal-zones/soc-thermal/cooling-maps" 60 61 #define OPP_TABLE_MAX 20 62 #define RATE_LOWER_LEVEL_N 2 63 #define DIFF_VOLTAGE_UV 50000 64 #define TEMP_STRING_LEN 12 65 #define REPEAT_PERIOD_US 1000000 66 67 static LIST_HEAD(pm_e_head); 68 69 enum pm_id { 70 PM_CPU, 71 PM_DMC, 72 }; 73 74 enum pm_event { 75 PM_EVT_NONE = 0x0, 76 PM_EVT_LOW = 0x1, 77 PM_EVT_HIGH = 0x2, 78 PM_EVT_BOTH = PM_EVT_LOW | PM_EVT_HIGH, 79 }; 80 81 struct opp_table { 82 u64 hz; 83 u32 uv; 84 }; 85 86 struct lmt_param { 87 int low_temp; /* milli degree */ 88 int high_temp; /* milli degree */ 89 int tz_temp; /* milli degree */ 90 int max_volt; /* uV */ 91 92 bool htemp_repeat; 93 bool ltemp_repeat; 94 95 bool ltemp_limit; 96 bool htemp_limit; 97 bool tztemp_limit; 98 }; 99 100 struct pm_element { 101 int id; 102 const char *name; 103 const char *supply_name; 104 int volt_diff; 105 u32 opp_nr; 106 struct opp_table opp[OPP_TABLE_MAX]; 107 struct lmt_param lmt; 108 struct udevice *supply; 109 struct clk clk; 110 struct list_head node; 111 }; 112 113 struct wtemp_dvfs_priv { 114 struct udevice *thermal; 115 struct pm_element *cpu; 116 struct pm_element *dmc; 117 }; 118 119 static struct pm_element pm_cpu = { 120 .id = PM_CPU, 121 .name = "cpu", 122 .supply_name = "cpu-supply", 123 .volt_diff = DIFF_VOLTAGE_UV, 124 }; 125 126 static struct pm_element pm_dmc = { 127 .id = PM_DMC, 128 .name = "dmc", 129 .supply_name = "center-supply", 130 .volt_diff = DIFF_VOLTAGE_UV, 131 }; 132 133 static void temp2string(int temp, char *data, int len) 134 { 135 int decimal_point; 136 int integer; 137 138 integer = abs(temp) / 1000; 139 decimal_point = abs(temp) % 1000; 140 snprintf(data, len, "%s%d.%d", 141 temp < 0 ? "-" : "", integer, decimal_point); 142 } 143 144 static ulong wtemp_get_lowlevel_rate(ulong rate, u32 level, 145 struct pm_element *e) 146 { 147 struct opp_table *opp; 148 int i, count, idx = 0; 149 150 opp = e->opp; 151 count = e->opp_nr; 152 153 for (i = 0; i < count; i++) { 154 if (opp[i].hz >= rate) { 155 idx = (i <= level) ? 0 : i - level; 156 break; 157 } 158 } 159 160 return opp[idx].hz; 161 } 162 163 static ulong __wtemp_clk_get_rate(struct pm_element *e) 164 { 165 #ifdef CONFIG_ROCKCHIP_DMC 166 if (e->id == PM_DMC) 167 return rockchip_ddrclk_sip_recalc_rate_v2(); 168 #endif 169 return clk_get_rate(&e->clk); 170 } 171 172 static ulong __wtemp_clk_set_rate(struct pm_element *e, ulong rate) 173 { 174 #ifdef CONFIG_ROCKCHIP_DMC 175 if (e->id == PM_DMC) { 176 rate = rockchip_ddrclk_sip_round_rate_v2(rate); 177 rockchip_ddrclk_sip_set_rate_v2(rate); 178 } else 179 #endif 180 rate = clk_set_rate(&e->clk, rate); 181 182 return rate; 183 } 184 185 static int __wtemp_regulator_get_value(struct pm_element *e) 186 { 187 return regulator_get_value(e->supply); 188 } 189 190 static int __wtemp_regulator_set_value(struct pm_element *e, int value) 191 { 192 return regulator_set_value(e->supply, value); 193 } 194 195 /* 196 * Policy: Increase voltage 197 * 198 * 1. target volt = original volt + diff volt; 199 * 2. If target volt is not over max_volt, just set it; 200 * 3. Otherwise set max_volt as target volt and lower the rate(front N level). 201 */ 202 static void wtemp_dvfs_low_temp_adjust(struct udevice *dev, struct pm_element *e) 203 { 204 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 205 ulong org_rate, tgt_rate, rb_rate; 206 int org_volt, tgt_volt, rb_volt; 207 208 org_rate = __wtemp_clk_get_rate(e); 209 org_volt = __wtemp_regulator_get_value(e); 210 tgt_volt = org_volt + e->volt_diff; 211 if ((e->lmt.max_volt != -ENODATA) && (tgt_volt > e->lmt.max_volt)) { 212 tgt_volt = e->lmt.max_volt; 213 __wtemp_regulator_set_value(e, tgt_volt); 214 tgt_rate = wtemp_get_lowlevel_rate(org_rate, 215 RATE_LOWER_LEVEL_N, priv->cpu); 216 __wtemp_clk_set_rate(e, tgt_rate); 217 } else { 218 __wtemp_regulator_set_value(e, tgt_volt); 219 tgt_rate = org_rate; 220 } 221 222 /* Check */ 223 rb_rate = __wtemp_clk_get_rate(e); 224 rb_volt = __wtemp_regulator_get_value(e); 225 if (tgt_rate != rb_rate) 226 printf("DVFS WARN: %s: target rate=%ld, readback rate=%ld !\n", 227 e->name, tgt_rate, rb_rate); 228 if (tgt_volt != rb_volt) 229 printf("DVFS WARN: %s: target volt=%d, readback volt=%d !\n", 230 e->name, tgt_volt, rb_volt); 231 232 printf("DVFS: %s(low): %ld->%ld Hz, %d->%d uV\n", 233 e->name, org_rate, rb_rate, org_volt, rb_volt); 234 } 235 236 /* 237 * Policy: 238 * 239 * Just set opp table[0] volt and rate, i.e. the lowest performance. 240 */ 241 static void wtemp_dvfs_high_temp_adjust(struct udevice *dev, struct pm_element *e) 242 { 243 ulong org_rate, tgt_rate, rb_rate; 244 int org_volt, tgt_volt, rb_volt; 245 246 /* Apply opp[0] */ 247 org_rate = __wtemp_clk_get_rate(e); 248 249 tgt_rate = e->opp[0].hz; 250 __wtemp_clk_set_rate(e, tgt_rate); 251 rb_rate = __wtemp_clk_get_rate(e); 252 if (tgt_rate != rb_rate) { 253 printf("DVFS WARN: %s: target rate=%ld, readback rate=%ld !\n", 254 e->name, tgt_rate, rb_rate); 255 return; 256 } 257 258 org_volt = __wtemp_regulator_get_value(e); 259 tgt_volt = e->opp[0].uv; 260 __wtemp_regulator_set_value(e, tgt_volt); 261 262 rb_volt = __wtemp_regulator_get_value(e); 263 if (tgt_volt != rb_volt) 264 printf("DVFS WARN: %s: target volt=%d, readback volt=%d !\n", 265 e->name, tgt_volt, rb_volt); 266 267 printf("DVFS: %s(high): %ld->%ld Hz, %d->%d uV\n", 268 e->name, org_rate, tgt_rate, org_volt, tgt_volt); 269 } 270 271 static bool wtemp_dvfs_is_effect(struct pm_element *e, 272 int temp, enum pm_event evt) 273 { 274 if (evt & PM_EVT_LOW) { 275 if (e->lmt.ltemp_limit && temp <= e->lmt.low_temp) 276 return false; 277 } 278 279 if (evt & PM_EVT_HIGH) { 280 if (e->lmt.tztemp_limit && temp >= e->lmt.tz_temp) 281 return false; 282 else if (e->lmt.htemp_limit && temp >= e->lmt.high_temp) 283 return false; 284 } 285 286 return true; 287 } 288 289 static int __wtemp_dvfs_apply(struct udevice *dev, struct pm_element *e, 290 int temp, enum pm_event evt) 291 { 292 enum pm_event ret = PM_EVT_NONE; 293 294 if (evt & PM_EVT_LOW) { 295 /* Over lowest temperature: increase voltage */ 296 if (e->lmt.ltemp_limit && temp <= e->lmt.low_temp) { 297 ret |= PM_EVT_LOW; 298 wtemp_dvfs_low_temp_adjust(dev, e); 299 } 300 } 301 302 if (evt & PM_EVT_HIGH) { 303 /* Over highest/thermal_zone temperature: decrease rate and voltage */ 304 if (e->lmt.tztemp_limit && temp >= e->lmt.tz_temp) { 305 ret |= PM_EVT_HIGH; 306 wtemp_dvfs_high_temp_adjust(dev, e); 307 } else if (e->lmt.htemp_limit && temp >= e->lmt.high_temp) { 308 ret |= PM_EVT_HIGH; 309 wtemp_dvfs_high_temp_adjust(dev, e); 310 } 311 } 312 313 return ret; 314 } 315 316 static int __wtemp_common_ofdata_to_platdata(ofnode node, struct pm_element *e) 317 { 318 ofnode supply, opp_node; 319 u32 phandle, uv, clock[2]; 320 uint64_t hz; 321 int ret; 322 323 /* Get regulator and clk */ 324 if (!ofnode_read_u32(node, e->supply_name, &phandle)) { 325 supply = ofnode_get_by_phandle(phandle); 326 ret = regulator_get_by_devname(supply.np->name, &e->supply); 327 if (ret) { 328 printf("DVFS: %s: Get supply(%s) failed, ret=%d", 329 e->name, supply.np->full_name, ret); 330 return ret; 331 } 332 debug("DVFS: supply: %s\n", supply.np->full_name); 333 } 334 335 if (!ofnode_read_u32_array(node, "clocks", clock, ARRAY_SIZE(clock))) { 336 e->clk.id = clock[1]; 337 ret = rockchip_get_clk(&e->clk.dev); 338 if (ret) { 339 printf("DVFS: %s: Get clk failed, ret=%d\n", e->name, ret); 340 return ret; 341 } 342 } 343 344 /* Get opp-table & limit param */ 345 if (!ofnode_read_u32(node, "operating-points-v2", &phandle)) { 346 opp_node = ofnode_get_by_phandle(phandle); 347 e->lmt.low_temp = ofnode_read_s32_default(opp_node, 348 "rockchip,low-temp", -ENODATA); 349 e->lmt.high_temp = ofnode_read_s32_default(opp_node, 350 "rockchip,high-temp", -ENODATA); 351 e->lmt.max_volt = ofnode_read_u32_default(opp_node, 352 "rockchip,max-volt", -ENODATA); 353 354 debug("DVFS: %s: low-temp=%d, high-temp=%d, max-volt=%d\n", 355 e->name, e->lmt.low_temp, e->lmt.high_temp, 356 e->lmt.max_volt); 357 358 ofnode_for_each_subnode(node, opp_node) { 359 if (e->opp_nr >= OPP_TABLE_MAX) { 360 printf("DVFS: over max(%d) opp table items\n", 361 OPP_TABLE_MAX); 362 break; 363 } 364 ofnode_read_u64(node, "opp-hz", &hz); 365 ofnode_read_u32_array(node, "opp-microvolt", &uv, 1); 366 e->opp[e->opp_nr].hz = hz; 367 e->opp[e->opp_nr].uv = uv; 368 e->opp_nr++; 369 debug("DVFS: %s: opp[%d]: hz=%lld, uv=%d, %s\n", 370 e->name, e->opp_nr - 1, 371 hz, uv, ofnode_get_name(node)); 372 } 373 } 374 if (!e->opp_nr) { 375 printf("DVFS: %s: Can't find opp table\n", e->name); 376 return -EINVAL; 377 } 378 379 if (e->lmt.max_volt == -ENODATA) 380 e->lmt.max_volt = e->opp[e->opp_nr - 1].uv; 381 if (e->lmt.low_temp != -ENODATA) 382 e->lmt.ltemp_limit = true; 383 if (e->lmt.high_temp != -ENODATA) 384 e->lmt.htemp_limit = true; 385 386 return 0; 387 } 388 389 static int wtemp_dvfs_apply(struct udevice *dev) 390 { 391 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 392 struct list_head *node; 393 struct pm_element *e; 394 char s_temp[TEMP_STRING_LEN]; 395 int temp, ret; 396 397 ret = thermal_get_temp(priv->thermal, &temp); 398 if (ret) { 399 printf("DVFS: Get temperature failed, ret=%d\n", ret); 400 return ret; 401 } 402 403 temp2string(temp, s_temp, TEMP_STRING_LEN); 404 printf("DVFS: %s'c\n", s_temp); 405 406 /* Apply dvfs policy for all pm element */ 407 list_for_each(node, &pm_e_head) { 408 e = list_entry(node, struct pm_element, node); 409 __wtemp_dvfs_apply(dev, e, temp, PM_EVT_BOTH); 410 } 411 412 return 0; 413 } 414 415 static int wtemp_dvfs_repeat_apply(struct udevice *dev) 416 { 417 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 418 struct list_head *node; 419 struct pm_element *e; 420 enum pm_event applied; 421 char s_temp[TEMP_STRING_LEN]; 422 int temp, ret; 423 424 repeat: 425 ret = thermal_get_temp(priv->thermal, &temp); 426 if (ret) { 427 printf("DVFS: Get thermal temperature failed, ret=%d\n", ret); 428 return false; 429 } 430 431 /* Apply dvfs policy for all pm element if there is repeat request */ 432 applied = PM_EVT_NONE; 433 list_for_each(node, &pm_e_head) { 434 e = list_entry(node, struct pm_element, node); 435 if (e->lmt.ltemp_repeat) 436 applied |= __wtemp_dvfs_apply(dev, e, temp, PM_EVT_LOW); 437 if (e->lmt.htemp_repeat) 438 applied |= __wtemp_dvfs_apply(dev, e, temp, PM_EVT_HIGH); 439 } 440 441 /* Everything is fine, exit */ 442 if (applied == PM_EVT_NONE) 443 goto finish; 444 445 /* Check repeat result */ 446 udelay(REPEAT_PERIOD_US); 447 list_for_each(node, &pm_e_head) { 448 e = list_entry(node, struct pm_element, node); 449 if (e->lmt.ltemp_repeat && 450 !wtemp_dvfs_is_effect(e, temp, PM_EVT_LOW)) 451 goto repeat; 452 if (e->lmt.htemp_repeat && 453 !wtemp_dvfs_is_effect(e, temp, PM_EVT_HIGH)) 454 goto repeat; 455 } 456 457 finish: 458 list_for_each(node, &pm_e_head) { 459 e = list_entry(node, struct pm_element, node); 460 temp2string(temp, s_temp, TEMP_STRING_LEN); 461 printf("DVFS: %s %s'c, %ld Hz, %d uV\n", e->name, 462 s_temp, __wtemp_clk_get_rate(e), 463 __wtemp_regulator_get_value(e)); 464 } 465 466 return 0; 467 } 468 469 static void print_e_state(void) 470 { 471 struct pm_element *e; 472 struct list_head *node; 473 char s_low[TEMP_STRING_LEN]; 474 char s_high[TEMP_STRING_LEN]; 475 char s_tz[TEMP_STRING_LEN]; 476 477 list_for_each(node, &pm_e_head) { 478 e = list_entry(node, struct pm_element, node); 479 if (!e->lmt.ltemp_limit && 480 !e->lmt.htemp_limit && !e->lmt.tztemp_limit) 481 return; 482 483 temp2string(e->lmt.tz_temp, s_tz, TEMP_STRING_LEN); 484 temp2string(e->lmt.low_temp, s_low, TEMP_STRING_LEN); 485 temp2string(e->lmt.high_temp, s_high, TEMP_STRING_LEN); 486 printf("DVFS: %s: low=%s'c, high=%s'c, Vmax=%duV, tz_temp=%s'c, " 487 "h_repeat=%d, l_repeat=%d\n", 488 e->name, e->lmt.ltemp_limit ? s_low : NULL, 489 e->lmt.htemp_limit ? s_high : NULL, 490 e->lmt.max_volt, 491 e->lmt.tztemp_limit ? s_tz : NULL, 492 e->lmt.htemp_repeat, e->lmt.ltemp_repeat); 493 } 494 } 495 496 static int wtemp_dvfs_ofdata_to_platdata(struct udevice *dev) 497 { 498 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 499 ofnode tz_trip0, cooling_maps, node; 500 ofnode cpus, cpu, dmc; 501 const char *name; 502 int ret, tz_temp; 503 u32 phandle; 504 505 INIT_LIST_HEAD(&pm_e_head); 506 507 /* 1. Parse cpu node */ 508 priv->cpu = &pm_cpu; 509 cpus = ofnode_path(FDT_PATH_CPUS); 510 if (!ofnode_valid(cpus)) { 511 debug("DVFS: Can't find %s\n", FDT_PATH_CPUS); 512 goto parse_dmc; 513 } 514 515 ofnode_for_each_subnode(cpu, cpus) { 516 name = ofnode_get_property(cpu, "device_type", NULL); 517 if (!name) 518 continue; 519 if (!strcmp(name, "cpu")) { 520 ret = __wtemp_common_ofdata_to_platdata(cpu, priv->cpu); 521 if (ret) 522 return ret; 523 break; 524 } 525 } 526 527 priv->cpu->lmt.ltemp_repeat = 528 dev_read_bool(dev, "cpu,low-temp-repeat"); 529 priv->cpu->lmt.htemp_repeat = 530 dev_read_bool(dev, "cpu,high-temp-repeat"); 531 532 list_add_tail(&priv->cpu->node, &pm_e_head); 533 534 /* 2. Parse dmc node */ 535 parse_dmc: 536 priv->dmc = &pm_dmc; 537 dmc = ofnode_path(FDT_PATH_DMC); 538 if (!ofnode_valid(dmc)) { 539 debug("DVFS: Can't find %s\n", FDT_PATH_CPUS); 540 goto parse_tz; 541 } 542 if (!IS_ENABLED(CONFIG_ROCKCHIP_DMC)) { 543 debug("DVFS: CONFIG_ROCKCHIP_DMC is disabled\n"); 544 goto parse_tz; 545 } 546 547 ret = __wtemp_common_ofdata_to_platdata(dmc, priv->dmc); 548 if (ret) 549 return ret; 550 551 priv->dmc->lmt.ltemp_repeat = 552 dev_read_bool(dev, "dmc,low-temp-repeat"); 553 priv->dmc->lmt.htemp_repeat = 554 dev_read_bool(dev, "dmc,high-temp-repeat"); 555 556 list_add_tail(&priv->dmc->node, &pm_e_head); 557 558 /* 3. Parse thermal zone node */ 559 parse_tz: 560 tz_trip0 = ofnode_path(FDT_PATH_THREMAL_TRIP_POINT0); 561 if (!ofnode_valid(tz_trip0)) { 562 debug("DVFS: Can't find %s\n", FDT_PATH_THREMAL_TRIP_POINT0); 563 goto finish; 564 } 565 566 tz_temp = ofnode_read_s32_default(tz_trip0, "temperature", -ENODATA); 567 if (tz_temp == -ENODATA) { 568 debug("DVFS: Can't get thermal zone trip0 temperature\n"); 569 goto finish; 570 } 571 572 cooling_maps = ofnode_path(FDT_PATH_THREMAL_COOLING_MAPS); 573 if (!ofnode_valid(cooling_maps)) { 574 debug("DVFS: Can't find %s\n", FDT_PATH_THREMAL_COOLING_MAPS); 575 goto finish; 576 } 577 578 ofnode_for_each_subnode(node, cooling_maps) { 579 ofnode_read_u32_array(node, "cooling-device", &phandle, 1); 580 name = ofnode_get_name(ofnode_get_by_phandle(phandle)); 581 if (!name) 582 continue; 583 if (strstr(name, "cpu")) { 584 priv->cpu->lmt.tztemp_limit = true; 585 priv->cpu->lmt.tz_temp = tz_temp; 586 } else if (strstr(name, "dmc")) { 587 priv->dmc->lmt.tztemp_limit = true; 588 priv->dmc->lmt.tz_temp = tz_temp; 589 } 590 } 591 592 finish: 593 print_e_state(); 594 595 return 0; 596 } 597 598 static const struct dm_dvfs_ops wtemp_dvfs_ops = { 599 .apply = wtemp_dvfs_apply, 600 .repeat_apply = wtemp_dvfs_repeat_apply, 601 }; 602 603 static int wtemp_dvfs_probe(struct udevice *dev) 604 { 605 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 606 int ret; 607 608 #ifdef CONFIG_ROCKCHIP_DMC 609 struct udevice *ram_dev; 610 611 /* Init dmc */ 612 ret = uclass_get_device(UCLASS_RAM, 0, &ram_dev); 613 if (ret) { 614 printf("DVFS: Get dmc device failed, ret=%d\n", ret); 615 return ret; 616 } 617 #endif 618 /* Init thermal */ 619 ret = uclass_get_device(UCLASS_THERMAL, 0, &priv->thermal); 620 if (ret) { 621 printf("DVFS: Get thermal device failed, ret=%d\n", ret); 622 return ret; 623 } 624 625 return 0; 626 } 627 628 static const struct udevice_id wtemp_dvfs_match[] = { 629 { .compatible = "rockchip,uboot-wide-temperature", }, 630 {}, 631 }; 632 633 U_BOOT_DRIVER(rockchip_wide_temp_dvfs) = { 634 .name = "rockchip_wide_temp_dvfs", 635 .id = UCLASS_DVFS, 636 .ops = &wtemp_dvfs_ops, 637 .of_match = wtemp_dvfs_match, 638 .probe = wtemp_dvfs_probe, 639 .ofdata_to_platdata = wtemp_dvfs_ofdata_to_platdata, 640 .priv_auto_alloc_size = sizeof(struct wtemp_dvfs_priv), 641 }; 642