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 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 tgt_rate = __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: %s: target rate=%ld, readback rate=%ld !\n", 227 e->name, tgt_rate, rb_rate); 228 if (tgt_volt != rb_volt) 229 printf("DVFS: %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 tgt_rate = e->opp[0].hz; 249 tgt_rate = __wtemp_clk_set_rate(e, tgt_rate); 250 251 org_volt = __wtemp_regulator_get_value(e); 252 tgt_volt = e->opp[0].uv; 253 __wtemp_regulator_set_value(e, tgt_volt); 254 255 /* Check */ 256 rb_rate = __wtemp_clk_get_rate(e); 257 rb_volt = __wtemp_regulator_get_value(e); 258 if (tgt_rate != rb_rate) 259 printf("DVFS: %s: target rate=%ld, readback rate=%ld !\n", 260 e->name, tgt_rate, rb_rate); 261 if (tgt_volt != rb_volt) 262 printf("DVFS: %s: target volt=%d, readback volt=%d !\n", 263 e->name, tgt_volt, rb_volt); 264 265 printf("DVFS: %s(high): %ld->%ld Hz, %d->%d uV\n", 266 e->name, org_rate, tgt_rate, org_volt, tgt_volt); 267 } 268 269 static bool wtemp_dvfs_is_effect(struct pm_element *e, 270 int temp, enum pm_event evt) 271 { 272 if (evt & PM_EVT_LOW) { 273 if (e->lmt.ltemp_limit && temp <= e->lmt.low_temp) 274 return false; 275 } 276 277 if (evt & PM_EVT_HIGH) { 278 if (e->lmt.tztemp_limit && temp >= e->lmt.tz_temp) 279 return false; 280 else if (e->lmt.htemp_limit && temp >= e->lmt.high_temp) 281 return false; 282 } 283 284 return true; 285 } 286 287 static int __wtemp_dvfs_apply(struct udevice *dev, struct pm_element *e, 288 int temp, enum pm_event evt) 289 { 290 enum pm_event ret = PM_EVT_NONE; 291 292 if (evt & PM_EVT_LOW) { 293 /* Over lowest temperature: increase voltage */ 294 if (e->lmt.ltemp_limit && temp <= e->lmt.low_temp) { 295 ret |= PM_EVT_LOW; 296 wtemp_dvfs_low_temp_adjust(dev, e); 297 } 298 } 299 300 if (evt & PM_EVT_HIGH) { 301 /* Over highest/thermal_zone temperature: decrease rate and voltage */ 302 if (e->lmt.tztemp_limit && temp >= e->lmt.tz_temp) { 303 ret |= PM_EVT_HIGH; 304 wtemp_dvfs_high_temp_adjust(dev, e); 305 } else if (e->lmt.htemp_limit && temp >= e->lmt.high_temp) { 306 ret |= PM_EVT_HIGH; 307 wtemp_dvfs_high_temp_adjust(dev, e); 308 } 309 } 310 311 return ret; 312 } 313 314 static int __wtemp_common_ofdata_to_platdata(ofnode node, struct pm_element *e) 315 { 316 ofnode supply, opp_node; 317 u32 phandle, uv, clock[2]; 318 uint64_t hz; 319 int ret; 320 321 /* Get regulator and clk */ 322 if (!ofnode_read_u32(node, e->supply_name, &phandle)) { 323 supply = ofnode_get_by_phandle(phandle); 324 ret = regulator_get_by_devname(supply.np->name, &e->supply); 325 if (ret) { 326 printf("DVFS: %s: Get supply(%s) failed, ret=%d", 327 e->name, supply.np->full_name, ret); 328 return ret; 329 } 330 debug("DVFS: supply: %s\n", supply.np->full_name); 331 } 332 333 if (!ofnode_read_u32_array(node, "clocks", clock, ARRAY_SIZE(clock))) { 334 e->clk.id = clock[1]; 335 ret = rockchip_get_clk(&e->clk.dev); 336 if (ret) { 337 printf("DVFS: %s: Get clk failed, ret=%d\n", e->name, ret); 338 return ret; 339 } 340 } 341 342 /* Get opp-table & limit param */ 343 if (!ofnode_read_u32(node, "operating-points-v2", &phandle)) { 344 opp_node = ofnode_get_by_phandle(phandle); 345 e->lmt.low_temp = ofnode_read_s32_default(opp_node, 346 "rockchip,low-temp", -ENODATA); 347 e->lmt.high_temp = ofnode_read_s32_default(opp_node, 348 "rockchip,high-temp", -ENODATA); 349 e->lmt.max_volt = ofnode_read_u32_default(opp_node, 350 "rockchip,max-volt", -ENODATA); 351 352 debug("DVFS: %s: low-temp=%d, high-temp=%d, max-volt=%d\n", 353 e->name, e->lmt.low_temp, e->lmt.high_temp, 354 e->lmt.max_volt); 355 356 ofnode_for_each_subnode(node, opp_node) { 357 if (e->opp_nr >= OPP_TABLE_MAX) { 358 printf("DVFS: over max(%d) opp table items\n", 359 OPP_TABLE_MAX); 360 break; 361 } 362 ofnode_read_u64(node, "opp-hz", &hz); 363 ofnode_read_u32_array(node, "opp-microvolt", &uv, 1); 364 e->opp[e->opp_nr].hz = hz; 365 e->opp[e->opp_nr].uv = uv; 366 e->opp_nr++; 367 debug("DVFS: %s: opp[%d]: hz=%lld, uv=%d, %s\n", 368 e->name, e->opp_nr - 1, 369 hz, uv, ofnode_get_name(node)); 370 } 371 } 372 if (!e->opp_nr) { 373 printf("DVFS: %s: Can't find opp table\n", e->name); 374 return -EINVAL; 375 } 376 377 if (e->lmt.max_volt == -ENODATA) 378 e->lmt.max_volt = e->opp[e->opp_nr - 1].uv; 379 if (e->lmt.low_temp != -ENODATA) 380 e->lmt.ltemp_limit = true; 381 if (e->lmt.high_temp != -ENODATA) 382 e->lmt.htemp_limit = true; 383 384 return 0; 385 } 386 387 static int wtemp_dvfs_apply(struct udevice *dev) 388 { 389 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 390 struct list_head *node; 391 struct pm_element *e; 392 char s_temp[TEMP_STRING_LEN]; 393 int temp, ret; 394 395 ret = thermal_get_temp(priv->thermal, &temp); 396 if (ret) { 397 printf("DVFS: Get temperature failed, ret=%d\n", ret); 398 return ret; 399 } 400 401 temp2string(temp, s_temp, TEMP_STRING_LEN); 402 printf("DVFS: %s'c\n", s_temp); 403 404 /* Apply dvfs policy for all pm element */ 405 list_for_each(node, &pm_e_head) { 406 e = list_entry(node, struct pm_element, node); 407 __wtemp_dvfs_apply(dev, e, temp, PM_EVT_BOTH); 408 } 409 410 return 0; 411 } 412 413 static int wtemp_dvfs_repeat_apply(struct udevice *dev) 414 { 415 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 416 struct list_head *node; 417 struct pm_element *e; 418 enum pm_event applied; 419 char s_temp[TEMP_STRING_LEN]; 420 int temp, ret; 421 422 repeat: 423 ret = thermal_get_temp(priv->thermal, &temp); 424 if (ret) { 425 printf("DVFS: Get thermal temperature failed, ret=%d\n", ret); 426 return false; 427 } 428 429 /* Apply dvfs policy for all pm element if there is repeat request */ 430 applied = PM_EVT_NONE; 431 list_for_each(node, &pm_e_head) { 432 e = list_entry(node, struct pm_element, node); 433 if (e->lmt.ltemp_repeat) 434 applied |= __wtemp_dvfs_apply(dev, e, temp, PM_EVT_LOW); 435 if (e->lmt.htemp_repeat) 436 applied |= __wtemp_dvfs_apply(dev, e, temp, PM_EVT_HIGH); 437 } 438 439 /* Everything is fine, exit */ 440 if (applied == PM_EVT_NONE) 441 goto finish; 442 443 /* Check repeat result */ 444 udelay(REPEAT_PERIOD_US); 445 list_for_each(node, &pm_e_head) { 446 e = list_entry(node, struct pm_element, node); 447 if (e->lmt.ltemp_repeat && 448 !wtemp_dvfs_is_effect(e, temp, PM_EVT_LOW)) 449 goto repeat; 450 if (e->lmt.htemp_repeat && 451 !wtemp_dvfs_is_effect(e, temp, PM_EVT_HIGH)) 452 goto repeat; 453 } 454 455 finish: 456 list_for_each(node, &pm_e_head) { 457 e = list_entry(node, struct pm_element, node); 458 temp2string(temp, s_temp, TEMP_STRING_LEN); 459 printf("DVFS: %s %s'c, %ld Hz, %d uV\n", e->name, 460 s_temp, __wtemp_clk_get_rate(e), 461 __wtemp_regulator_get_value(e)); 462 } 463 464 return 0; 465 } 466 467 static void print_e_state(void) 468 { 469 struct pm_element *e; 470 struct list_head *node; 471 char s_low[TEMP_STRING_LEN]; 472 char s_high[TEMP_STRING_LEN]; 473 char s_tz[TEMP_STRING_LEN]; 474 475 list_for_each(node, &pm_e_head) { 476 e = list_entry(node, struct pm_element, node); 477 if (!e->lmt.ltemp_limit && 478 !e->lmt.htemp_limit && !e->lmt.tztemp_limit) 479 return; 480 481 temp2string(e->lmt.tz_temp, s_tz, TEMP_STRING_LEN); 482 temp2string(e->lmt.low_temp, s_low, TEMP_STRING_LEN); 483 temp2string(e->lmt.high_temp, s_high, TEMP_STRING_LEN); 484 printf("DVFS: %s: low=%s'c, high=%s'c, Vmax=%duV, tz_temp=%s'c, " 485 "h_repeat=%d, l_repeat=%d\n", 486 e->name, e->lmt.ltemp_limit ? s_low : NULL, 487 e->lmt.htemp_limit ? s_high : NULL, 488 e->lmt.max_volt, 489 e->lmt.tztemp_limit ? s_tz : NULL, 490 e->lmt.htemp_repeat, e->lmt.ltemp_repeat); 491 } 492 } 493 494 static int wtemp_dvfs_ofdata_to_platdata(struct udevice *dev) 495 { 496 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 497 ofnode tz_trip0, cooling_maps, node; 498 ofnode cpus, cpu, dmc; 499 const char *name; 500 int ret, tz_temp; 501 u32 phandle; 502 503 INIT_LIST_HEAD(&pm_e_head); 504 505 /* 1. Parse cpu node */ 506 priv->cpu = &pm_cpu; 507 cpus = ofnode_path(FDT_PATH_CPUS); 508 if (!ofnode_valid(cpus)) { 509 debug("DVFS: Can't find %s\n", FDT_PATH_CPUS); 510 goto parse_dmc; 511 } 512 513 ofnode_for_each_subnode(cpu, cpus) { 514 name = ofnode_get_property(cpu, "device_type", NULL); 515 if (!name) 516 continue; 517 if (!strcmp(name, "cpu")) { 518 ret = __wtemp_common_ofdata_to_platdata(cpu, priv->cpu); 519 if (ret) 520 return ret; 521 break; 522 } 523 } 524 525 priv->cpu->lmt.ltemp_repeat = 526 dev_read_bool(dev, "cpu,low-temp-repeat"); 527 priv->cpu->lmt.htemp_repeat = 528 dev_read_bool(dev, "cpu,high-temp-repeat"); 529 530 list_add_tail(&priv->cpu->node, &pm_e_head); 531 532 /* 2. Parse dmc node */ 533 parse_dmc: 534 priv->dmc = &pm_dmc; 535 dmc = ofnode_path(FDT_PATH_DMC); 536 if (!ofnode_valid(dmc)) { 537 debug("DVFS: Can't find %s\n", FDT_PATH_CPUS); 538 goto parse_tz; 539 } 540 if (!IS_ENABLED(CONFIG_ROCKCHIP_DMC)) { 541 debug("DVFS: CONFIG_ROCKCHIP_DMC is disabled\n"); 542 goto parse_tz; 543 } 544 545 ret = __wtemp_common_ofdata_to_platdata(dmc, priv->dmc); 546 if (ret) 547 return ret; 548 549 priv->dmc->lmt.ltemp_repeat = 550 dev_read_bool(dev, "dmc,low-temp-repeat"); 551 priv->dmc->lmt.htemp_repeat = 552 dev_read_bool(dev, "dmc,high-temp-repeat"); 553 554 list_add_tail(&priv->dmc->node, &pm_e_head); 555 556 /* 3. Parse thermal zone node */ 557 parse_tz: 558 tz_trip0 = ofnode_path(FDT_PATH_THREMAL_TRIP_POINT0); 559 if (!ofnode_valid(tz_trip0)) { 560 debug("DVFS: Can't find %s\n", FDT_PATH_THREMAL_TRIP_POINT0); 561 goto finish; 562 } 563 564 tz_temp = ofnode_read_s32_default(tz_trip0, "temperature", -ENODATA); 565 if (tz_temp == -ENODATA) { 566 debug("DVFS: Can't get thermal zone trip0 temperature\n"); 567 goto finish; 568 } 569 570 cooling_maps = ofnode_path(FDT_PATH_THREMAL_COOLING_MAPS); 571 if (!ofnode_valid(cooling_maps)) { 572 debug("DVFS: Can't find %s\n", FDT_PATH_THREMAL_COOLING_MAPS); 573 goto finish; 574 } 575 576 ofnode_for_each_subnode(node, cooling_maps) { 577 ofnode_read_u32_array(node, "cooling-device", &phandle, 1); 578 name = ofnode_get_name(ofnode_get_by_phandle(phandle)); 579 if (!name) 580 continue; 581 if (strstr(name, "cpu")) { 582 priv->cpu->lmt.tztemp_limit = true; 583 priv->cpu->lmt.tz_temp = tz_temp; 584 } else if (strstr(name, "dmc")) { 585 priv->dmc->lmt.tztemp_limit = true; 586 priv->dmc->lmt.tz_temp = tz_temp; 587 } 588 } 589 590 finish: 591 print_e_state(); 592 593 return 0; 594 } 595 596 static const struct dm_dvfs_ops wtemp_dvfs_ops = { 597 .apply = wtemp_dvfs_apply, 598 .repeat_apply = wtemp_dvfs_repeat_apply, 599 }; 600 601 static int wtemp_dvfs_probe(struct udevice *dev) 602 { 603 struct wtemp_dvfs_priv *priv = dev_get_priv(dev); 604 int ret; 605 606 #ifdef CONFIG_ROCKCHIP_DMC 607 struct udevice *ram_dev; 608 609 /* Init dmc */ 610 ret = uclass_get_device(UCLASS_RAM, 0, &ram_dev); 611 if (ret) { 612 printf("DVFS: Get dmc device failed, ret=%d\n", ret); 613 return ret; 614 } 615 #endif 616 /* Init thermal */ 617 ret = uclass_get_device(UCLASS_THERMAL, 0, &priv->thermal); 618 if (ret) { 619 printf("DVFS: Get thermal device failed, ret=%d\n", ret); 620 return ret; 621 } 622 623 return 0; 624 } 625 626 static const struct udevice_id wtemp_dvfs_match[] = { 627 { .compatible = "rockchip,uboot-wide-temperature", }, 628 {}, 629 }; 630 631 U_BOOT_DRIVER(rockchip_wide_temp_dvfs) = { 632 .name = "rockchip_wide_temp_dvfs", 633 .id = UCLASS_DVFS, 634 .ops = &wtemp_dvfs_ops, 635 .of_match = wtemp_dvfs_match, 636 .probe = wtemp_dvfs_probe, 637 .ofdata_to_platdata = wtemp_dvfs_ofdata_to_platdata, 638 .priv_auto_alloc_size = sizeof(struct wtemp_dvfs_priv), 639 }; 640