From 0eea0d250e65f61dbae608a25eb07e26567ef430 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Mon, 19 Nov 2018 17:43:25 +0800 Subject: [PATCH] dm: add dvfs uclass and wide temperature dvfs support - add dvfs uclass; - add dvfs command; - add a simple wide temperature dvfs driver. About wide temperature dvfs driver policy, see description in: drivers/power/dvfs/rockchip_wtemp_dvfs.c Change-Id: I36a8de6e47f8375bf1795b794c77d96b4571a361 Signed-off-by: Joseph Chen --- Makefile | 3 +- cmd/Kconfig | 10 + cmd/Makefile | 1 + cmd/dvfs.c | 37 ++ drivers/power/Kconfig | 2 + drivers/power/dvfs/Kconfig | 11 + drivers/power/dvfs/Makefile | 9 + drivers/power/dvfs/dvfs-uclass.c | 50 ++ drivers/power/dvfs/rockchip_wtemp_dvfs.c | 639 +++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/dvfs.h | 47 ++ 11 files changed, 809 insertions(+), 1 deletion(-) create mode 100644 cmd/dvfs.c create mode 100644 drivers/power/dvfs/Kconfig create mode 100644 drivers/power/dvfs/Makefile create mode 100644 drivers/power/dvfs/dvfs-uclass.c create mode 100644 drivers/power/dvfs/rockchip_wtemp_dvfs.c create mode 100644 include/dvfs.h diff --git a/Makefile b/Makefile index b3f61a2eac..7060da1c5f 100644 --- a/Makefile +++ b/Makefile @@ -671,7 +671,8 @@ libs-y += drivers/power/ \ drivers/power/mfd/ \ drivers/power/pmic/ \ drivers/power/battery/ \ - drivers/power/regulator/ + drivers/power/regulator/ \ + drivers/power/dvfs/ libs-y += drivers/spi/ libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/ libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/ diff --git a/cmd/Kconfig b/cmd/Kconfig index 5aeedebee5..23d8425cc1 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1296,6 +1296,16 @@ config CMD_CHARGE_DISPLAY help Support U-Boot charge display. +config CMD_DVFS + bool "Enable Driver Model DVFS command" + depends on DM_DVFS + default y + help + Support dvfs policy apply API + User interface features: + - dvfs - apply dvfs policy once + - dvfs repeat - repeat apply dvfs policy until achieve the target temperature + endmenu menu "Security commands" diff --git a/cmd/Makefile b/cmd/Makefile index d4ceacf98e..e6307041cb 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -158,6 +158,7 @@ obj-$(CONFIG_CMD_ETHSW) += ethsw.o # Power obj-$(CONFIG_CMD_PMIC) += pmic.o obj-$(CONFIG_CMD_REGULATOR) += regulator.o +obj-$(CONFIG_CMD_DVFS) += dvfs.o obj-$(CONFIG_CMD_BLOB) += blob.o endif # !CONFIG_SPL_BUILD diff --git a/cmd/dvfs.c b/cmd/dvfs.c new file mode 100644 index 0000000000..73a6f06a1e --- /dev/null +++ b/cmd/dvfs.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd + */ + +#include +#include +#include + +static int do_dvfs(cmd_tbl_t *cmdtp, int flag, + int argc, char *const argv[]) +{ + struct udevice *dev; + int ret; + + ret = uclass_get_device(UCLASS_DVFS, 0, &dev); + if (ret) { + printf("DVFS: Get dvfs device failed, ret=%d\n", ret); + return ret; + } + + if (argc == 1) + return dvfs_apply(dev); + else if (!strcmp(argv[1], "repeat")) + return dvfs_repeat_apply(dev); + else + return CMD_RET_USAGE; + + return 0; +} + +U_BOOT_CMD( + dvfs, 2, 1, do_dvfs, + "Start DVFS policy", + "dvfs - apply dvfs policy once\n" + "dvfs repeat - repeat apply dvfs policy until achieve the target temperature" +); diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index d1eac1757f..adf5ff58bf 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -8,6 +8,8 @@ source "drivers/power/pmic/Kconfig" source "drivers/power/regulator/Kconfig" +source "drivers/power/dvfs/Kconfig" + config DM_CHARGE_DISPLAY bool "Enable driver model for charge display support" depends on DM diff --git a/drivers/power/dvfs/Kconfig b/drivers/power/dvfs/Kconfig new file mode 100644 index 0000000000..0a3580e0b5 --- /dev/null +++ b/drivers/power/dvfs/Kconfig @@ -0,0 +1,11 @@ +config DM_DVFS + bool "Enable Driver Model for DVFS drivers (UCLASS_DVFS)" + depends on DM + ---help--- + This config enables the driver-model DVFS support. + +config ROCKCHIP_WTEMP_DVFS + bool "Enable rockchip wide temperature dvfs policy" + depends on DM_DVFS && ROCKCHIP_THERMAL + help + This enable support wide temperature dvfs for rockchip platforms. diff --git a/drivers/power/dvfs/Makefile b/drivers/power/dvfs/Makefile new file mode 100644 index 0000000000..1734cd8007 --- /dev/null +++ b/drivers/power/dvfs/Makefile @@ -0,0 +1,9 @@ +# +# Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd +# +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_DM_DVFS) += dvfs-uclass.o +obj-$(CONFIG_ROCKCHIP_WTEMP_DVFS) += rockchip_wtemp_dvfs.o + diff --git a/drivers/power/dvfs/dvfs-uclass.c b/drivers/power/dvfs/dvfs-uclass.c new file mode 100644 index 0000000000..abd04dcb5e --- /dev/null +++ b/drivers/power/dvfs/dvfs-uclass.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd + */ + +#include +#include +#include + +int dvfs_apply(struct udevice *dev) +{ + const struct dm_dvfs_ops *ops = device_get_ops(dev); + + if (!ops->apply) + return -ENOSYS; + + return ops->apply(dev); +} + +int dvfs_repeat_apply(struct udevice *dev) +{ + const struct dm_dvfs_ops *ops = device_get_ops(dev); + + if (!ops->repeat_apply) + return -ENOSYS; + + return ops->repeat_apply(dev); +} + +int dvfs_init(bool apply) +{ + struct udevice *dev; + int ret; + + ret = uclass_get_device(UCLASS_DVFS, 0, &dev); + if (ret) { + printf("DVFS: Get dvfs device failed, ret=%d\n", ret); + return ret; + } + + if (apply) + return dvfs_apply(dev); + + return 0; +} + +UCLASS_DRIVER(dvfs) = { + .id = UCLASS_DVFS, + .name = "dvfs", +}; diff --git a/drivers/power/dvfs/rockchip_wtemp_dvfs.c b/drivers/power/dvfs/rockchip_wtemp_dvfs.c new file mode 100644 index 0000000000..d7fe67145c --- /dev/null +++ b/drivers/power/dvfs/rockchip_wtemp_dvfs.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef CONFIG_ROCKCHIP_DMC +#include +#endif + +/* + * # This is a simple wide temperature(ie. wtemp) dvfs driver, the policy is: + * + * 1. U-Boot parse cpu/dmc opp table from kernel dtb, anyone of + * "rockchip,low-temp = <...>" and "rockchip,high-temp = <...>" present in + * cpu/dmc nodes means wtemp is enabled. + * + * 1.1. When temperature trigger "rockchip,low-temp", increase 50mv voltage + * as target voltage. If target voltage is over "rockchip,max-volt", + * just set "rockchip,max-volt" as target voltage and lower 2 level freq, + * + * 1.2. When temperature trigger "rockchip,high-temp", just apply opp table[0] + * voltage and freq. + * + * 2. U-Boot parse cpu/dmc thermal zone "trip-point-0" temperature from kernel + * dtb, and apply the same rules as above [1.2] policy. + * + * + * # The dvfs policy apply moment is: + * + * 1. Appy it after clk and regulator drivers setup; + * 2. Repeat apply it by CONFIG_PREBOOT command until achieve the target + * temperature. user should add: #define CONFIG_PREBOOT "dvfs repeat" and + * assign repeat property in dts: + * + * uboot-wide-temperature { + * status = "okay"; + * compatible = "rockchip,uboot-wide-temperature"; + * + * cpu,low-temp-repeat; + * cpu,high-temp-repeat; + * dmc,low-temp-repeat; + * dmc,high-temp-repeat; + * }; + */ + +#define FDT_PATH_CPUS "/cpus" +#define FDT_PATH_DMC "/dmc" +#define FDT_PATH_THREMAL_TRIP_POINT0 \ + "/thermal-zones/soc-thermal/trips/trip-point-0" +#define FDT_PATH_THREMAL_COOLING_MAPS \ + "/thermal-zones/soc-thermal/cooling-maps" + +#define OPP_TABLE_MAX 20 +#define RATE_LOWER_LEVEL_N 2 +#define DIFF_VOLTAGE_UV 50000 +#define TEMP_STRING_LEN 12 +#define REPEAT_PERIOD_US 1000000 + +static LIST_HEAD(pm_e_head); + +enum pm_id { + PM_CPU, + PM_DMC, +}; + +enum pm_event { + PM_EVT_NONE = 0x0, + PM_EVT_LOW = 0x1, + PM_EVT_HIGH = 0x2, + PM_EVT_BOTH = PM_EVT_LOW | PM_EVT_HIGH, +}; + +struct opp_table { + u64 hz; + u32 uv; +}; + +struct lmt_param { + int low_temp; /* milli degree */ + int high_temp; /* milli degree */ + int tz_temp; /* milli degree */ + int max_volt; /* uV */ + + bool htemp_repeat; + bool ltemp_repeat; + + bool ltemp_limit; + bool htemp_limit; + bool tztemp_limit; +}; + +struct pm_element { + int id; + const char *name; + const char *supply_name; + int volt_diff; + u32 opp_nr; + struct opp_table opp[OPP_TABLE_MAX]; + struct lmt_param lmt; + struct udevice *supply; + struct clk clk; + struct list_head node; +}; + +struct wtemp_dvfs_priv { + struct udevice *thermal; + struct pm_element *cpu; + struct pm_element *dmc; +}; + +static struct pm_element pm_cpu = { + .id = PM_CPU, + .name = "cpu", + .supply_name = "cpu-supply", + .volt_diff = DIFF_VOLTAGE_UV, +}; + +static struct pm_element pm_dmc = { + .id = PM_DMC, + .name = "dmc", + .supply_name = "center-supply", + .volt_diff = DIFF_VOLTAGE_UV, +}; + +static void temp2string(int temp, char *data, int len) +{ + int decimal_point; + int integer; + + integer = abs(temp) / 1000; + decimal_point = abs(temp) % 1000; + snprintf(data, len, "%s%d.%d", + temp < 0 ? "-" : "", integer, decimal_point); +} + +static ulong wtemp_get_lowlevel_rate(ulong rate, u32 level, + struct pm_element *e) +{ + struct opp_table *opp; + int i, count, idx = 0; + + opp = e->opp; + count = e->opp_nr; + + for (i = 0; i < count; i++) { + if (opp[i].hz >= rate) { + idx = (i <= level) ? 0 : i - level; + break; + } + } + + return opp[idx].hz; +} + +static ulong __wtemp_clk_get_rate(struct pm_element *e) +{ +#ifdef CONFIG_ROCKCHIP_DMC + if (e->id == PM_DMC) + return rockchip_ddrclk_sip_recalc_rate_v2(); +#endif + return clk_get_rate(&e->clk); +} + +static ulong __wtemp_clk_set_rate(struct pm_element *e, ulong rate) +{ +#ifdef CONFIG_ROCKCHIP_DMC + if (e->id == PM_DMC) { + rate = rockchip_ddrclk_sip_round_rate_v2(rate); + rockchip_ddrclk_sip_set_rate_v2(rate); + } else +#endif + clk_set_rate(&e->clk, rate); + + return rate; +} + +static int __wtemp_regulator_get_value(struct pm_element *e) +{ + return regulator_get_value(e->supply); +} + +static int __wtemp_regulator_set_value(struct pm_element *e, int value) +{ + return regulator_set_value(e->supply, value); +} + +/* + * Policy: Increase voltage + * + * 1. target volt = original volt + diff volt; + * 2. If target volt is not over max_volt, just set it; + * 3. Otherwise set max_volt as target volt and lower the rate(front N level). + */ +static void wtemp_dvfs_low_temp_adjust(struct udevice *dev, struct pm_element *e) +{ + struct wtemp_dvfs_priv *priv = dev_get_priv(dev); + ulong org_rate, tgt_rate, rb_rate; + int org_volt, tgt_volt, rb_volt; + + org_rate = __wtemp_clk_get_rate(e); + org_volt = __wtemp_regulator_get_value(e); + tgt_volt = org_volt + e->volt_diff; + if ((e->lmt.max_volt != -ENODATA) && (tgt_volt > e->lmt.max_volt)) { + tgt_volt = e->lmt.max_volt; + __wtemp_regulator_set_value(e, tgt_volt); + tgt_rate = wtemp_get_lowlevel_rate(org_rate, + RATE_LOWER_LEVEL_N, priv->cpu); + tgt_rate = __wtemp_clk_set_rate(e, tgt_rate); + } else { + __wtemp_regulator_set_value(e, tgt_volt); + tgt_rate = org_rate; + } + + /* Check */ + rb_rate = __wtemp_clk_get_rate(e); + rb_volt = __wtemp_regulator_get_value(e); + if (tgt_rate != rb_rate) + printf("DVFS: %s: target rate=%ld, readback rate=%ld !\n", + e->name, tgt_rate, rb_rate); + if (tgt_volt != rb_volt) + printf("DVFS: %s: target volt=%d, readback volt=%d !\n", + e->name, tgt_volt, rb_volt); + + printf("DVFS: %s(low): %ld->%ld Hz, %d->%d uV\n", + e->name, org_rate, rb_rate, org_volt, rb_volt); +} + +/* + * Policy: + * + * Just set opp table[0] volt and rate, i.e. the lowest performance. + */ +static void wtemp_dvfs_high_temp_adjust(struct udevice *dev, struct pm_element *e) +{ + ulong org_rate, tgt_rate, rb_rate; + int org_volt, tgt_volt, rb_volt; + + /* Apply opp[0] */ + org_rate = __wtemp_clk_get_rate(e); + tgt_rate = e->opp[0].hz; + tgt_rate = __wtemp_clk_set_rate(e, tgt_rate); + + org_volt = __wtemp_regulator_get_value(e); + tgt_volt = e->opp[0].uv; + __wtemp_regulator_set_value(e, tgt_volt); + + /* Check */ + rb_rate = __wtemp_clk_get_rate(e); + rb_volt = __wtemp_regulator_get_value(e); + if (tgt_rate != rb_rate) + printf("DVFS: %s: target rate=%ld, readback rate=%ld !\n", + e->name, tgt_rate, rb_rate); + if (tgt_volt != rb_volt) + printf("DVFS: %s: target volt=%d, readback volt=%d !\n", + e->name, tgt_volt, rb_volt); + + printf("DVFS: %s(high): %ld->%ld Hz, %d->%d uV\n", + e->name, org_rate, tgt_rate, org_volt, tgt_volt); +} + +static bool wtemp_dvfs_is_effect(struct pm_element *e, + int temp, enum pm_event evt) +{ + if (evt & PM_EVT_LOW) { + if (e->lmt.ltemp_limit && temp <= e->lmt.low_temp) + return false; + } + + if (evt & PM_EVT_HIGH) { + if (e->lmt.tztemp_limit && temp >= e->lmt.tz_temp) + return false; + else if (e->lmt.htemp_limit && temp >= e->lmt.high_temp) + return false; + } + + return true; +} + +static int __wtemp_dvfs_apply(struct udevice *dev, struct pm_element *e, + int temp, enum pm_event evt) +{ + enum pm_event ret = PM_EVT_NONE; + + if (evt & PM_EVT_LOW) { + /* Over lowest temperature: increase voltage */ + if (e->lmt.ltemp_limit && temp <= e->lmt.low_temp) { + ret |= PM_EVT_LOW; + wtemp_dvfs_low_temp_adjust(dev, e); + } + } + + if (evt & PM_EVT_HIGH) { + /* Over highest/thermal_zone temperature: decrease rate and voltage */ + if (e->lmt.tztemp_limit && temp >= e->lmt.tz_temp) { + ret |= PM_EVT_HIGH; + wtemp_dvfs_high_temp_adjust(dev, e); + } else if (e->lmt.htemp_limit && temp >= e->lmt.high_temp) { + ret |= PM_EVT_HIGH; + wtemp_dvfs_high_temp_adjust(dev, e); + } + } + + return ret; +} + +static int __wtemp_common_ofdata_to_platdata(ofnode node, struct pm_element *e) +{ + ofnode supply, opp_node; + u32 phandle, uv, clock[2]; + uint64_t hz; + int ret; + + /* Get regulator and clk */ + if (!ofnode_read_u32(node, e->supply_name, &phandle)) { + supply = ofnode_get_by_phandle(phandle); + ret = regulator_get_by_devname(supply.np->name, &e->supply); + if (ret) { + printf("DVFS: %s: Get supply(%s) failed, ret=%d", + e->name, supply.np->full_name, ret); + return ret; + } + debug("DVFS: supply: %s\n", supply.np->full_name); + } + + if (!ofnode_read_u32_array(node, "clocks", clock, ARRAY_SIZE(clock))) { + e->clk.id = clock[1]; + ret = rockchip_get_clk(&e->clk.dev); + if (ret) { + printf("DVFS: %s: Get clk failed, ret=%d\n", e->name, ret); + return ret; + } + } + + /* Get opp-table & limit param */ + if (!ofnode_read_u32(node, "operating-points-v2", &phandle)) { + opp_node = ofnode_get_by_phandle(phandle); + e->lmt.low_temp = ofnode_read_s32_default(opp_node, + "rockchip,low-temp", -ENODATA); + e->lmt.high_temp = ofnode_read_s32_default(opp_node, + "rockchip,high-temp", -ENODATA); + e->lmt.max_volt = ofnode_read_u32_default(opp_node, + "rockchip,max-volt", -ENODATA); + + debug("DVFS: %s: low-temp=%d, high-temp=%d, max-volt=%d\n", + e->name, e->lmt.low_temp, e->lmt.high_temp, + e->lmt.max_volt); + + ofnode_for_each_subnode(node, opp_node) { + if (e->opp_nr >= OPP_TABLE_MAX) { + printf("DVFS: over max(%d) opp table items\n", + OPP_TABLE_MAX); + break; + } + ofnode_read_u64(node, "opp-hz", &hz); + ofnode_read_u32_array(node, "opp-microvolt", &uv, 1); + e->opp[e->opp_nr].hz = hz; + e->opp[e->opp_nr].uv = uv; + e->opp_nr++; + debug("DVFS: %s: opp[%d]: hz=%lld, uv=%d, %s\n", + e->name, e->opp_nr - 1, + hz, uv, ofnode_get_name(node)); + } + } + if (!e->opp_nr) { + printf("DVFS: %s: Can't find opp table\n", e->name); + return -EINVAL; + } + + if (e->lmt.max_volt == -ENODATA) + e->lmt.max_volt = e->opp[e->opp_nr - 1].uv; + if (e->lmt.low_temp != -ENODATA) + e->lmt.ltemp_limit = true; + if (e->lmt.high_temp != -ENODATA) + e->lmt.htemp_limit = true; + + return 0; +} + +static int wtemp_dvfs_apply(struct udevice *dev) +{ + struct wtemp_dvfs_priv *priv = dev_get_priv(dev); + struct list_head *node; + struct pm_element *e; + char s_temp[TEMP_STRING_LEN]; + int temp, ret; + + ret = thermal_get_temp(priv->thermal, &temp); + if (ret) { + printf("DVFS: Get temperature failed, ret=%d\n", ret); + return ret; + } + + temp2string(temp, s_temp, TEMP_STRING_LEN); + printf("DVFS: %s'c\n", s_temp); + + /* Apply dvfs policy for all pm element */ + list_for_each(node, &pm_e_head) { + e = list_entry(node, struct pm_element, node); + __wtemp_dvfs_apply(dev, e, temp, PM_EVT_BOTH); + } + + return 0; +} + +static int wtemp_dvfs_repeat_apply(struct udevice *dev) +{ + struct wtemp_dvfs_priv *priv = dev_get_priv(dev); + struct list_head *node; + struct pm_element *e; + enum pm_event applied; + char s_temp[TEMP_STRING_LEN]; + int temp, ret; + +repeat: + ret = thermal_get_temp(priv->thermal, &temp); + if (ret) { + printf("DVFS: Get thermal temperature failed, ret=%d\n", ret); + return false; + } + + /* Apply dvfs policy for all pm element if there is repeat request */ + applied = PM_EVT_NONE; + list_for_each(node, &pm_e_head) { + e = list_entry(node, struct pm_element, node); + if (e->lmt.ltemp_repeat) + applied |= __wtemp_dvfs_apply(dev, e, temp, PM_EVT_LOW); + if (e->lmt.htemp_repeat) + applied |= __wtemp_dvfs_apply(dev, e, temp, PM_EVT_HIGH); + } + + /* Everything is fine, exit */ + if (applied == PM_EVT_NONE) + goto finish; + + /* Check repeat result */ + udelay(REPEAT_PERIOD_US); + list_for_each(node, &pm_e_head) { + e = list_entry(node, struct pm_element, node); + if (e->lmt.ltemp_repeat && + !wtemp_dvfs_is_effect(e, temp, PM_EVT_LOW)) + goto repeat; + if (e->lmt.htemp_repeat && + !wtemp_dvfs_is_effect(e, temp, PM_EVT_HIGH)) + goto repeat; + } + +finish: + list_for_each(node, &pm_e_head) { + e = list_entry(node, struct pm_element, node); + temp2string(temp, s_temp, TEMP_STRING_LEN); + printf("DVFS: %s %s'c, %ld Hz, %d uV\n", e->name, + s_temp, __wtemp_clk_get_rate(e), + __wtemp_regulator_get_value(e)); + } + + return 0; +} + +static void print_e_state(void) +{ + struct pm_element *e; + struct list_head *node; + char s_low[TEMP_STRING_LEN]; + char s_high[TEMP_STRING_LEN]; + char s_tz[TEMP_STRING_LEN]; + + list_for_each(node, &pm_e_head) { + e = list_entry(node, struct pm_element, node); + if (!e->lmt.ltemp_limit && + !e->lmt.htemp_limit && !e->lmt.tztemp_limit) + return; + + temp2string(e->lmt.tz_temp, s_tz, TEMP_STRING_LEN); + temp2string(e->lmt.low_temp, s_low, TEMP_STRING_LEN); + temp2string(e->lmt.high_temp, s_high, TEMP_STRING_LEN); + printf("DVFS: %s: low=%s'c, high=%s'c, Vmax=%duV, tz_temp=%s'c, " + "h_repeat=%d, l_repeat=%d\n", + e->name, e->lmt.ltemp_limit ? s_low : NULL, + e->lmt.htemp_limit ? s_high : NULL, + e->lmt.max_volt, + e->lmt.tztemp_limit ? s_tz : NULL, + e->lmt.htemp_repeat, e->lmt.ltemp_repeat); + } +} + +static int wtemp_dvfs_ofdata_to_platdata(struct udevice *dev) +{ + struct wtemp_dvfs_priv *priv = dev_get_priv(dev); + ofnode tz_trip0, cooling_maps, node; + ofnode cpus, cpu, dmc; + const char *name; + int ret, tz_temp; + u32 phandle; + + INIT_LIST_HEAD(&pm_e_head); + + /* 1. Parse cpu node */ + priv->cpu = &pm_cpu; + cpus = ofnode_path(FDT_PATH_CPUS); + if (!ofnode_valid(cpus)) { + debug("DVFS: Can't find %s\n", FDT_PATH_CPUS); + goto parse_dmc; + } + + ofnode_for_each_subnode(cpu, cpus) { + name = ofnode_get_property(cpu, "device_type", NULL); + if (!name) + continue; + if (!strcmp(name, "cpu")) { + ret = __wtemp_common_ofdata_to_platdata(cpu, priv->cpu); + if (ret) + return ret; + break; + } + } + + priv->cpu->lmt.ltemp_repeat = + dev_read_bool(dev, "cpu,low-temp-repeat"); + priv->cpu->lmt.htemp_repeat = + dev_read_bool(dev, "cpu,high-temp-repeat"); + + list_add_tail(&priv->cpu->node, &pm_e_head); + + /* 2. Parse dmc node */ +parse_dmc: + priv->dmc = &pm_dmc; + dmc = ofnode_path(FDT_PATH_DMC); + if (!ofnode_valid(dmc)) { + debug("DVFS: Can't find %s\n", FDT_PATH_CPUS); + goto parse_tz; + } + if (!IS_ENABLED(CONFIG_ROCKCHIP_DMC)) { + debug("DVFS: CONFIG_ROCKCHIP_DMC is disabled\n"); + goto parse_tz; + } + + ret = __wtemp_common_ofdata_to_platdata(dmc, priv->dmc); + if (ret) + return ret; + + priv->dmc->lmt.ltemp_repeat = + dev_read_bool(dev, "dmc,low-temp-repeat"); + priv->dmc->lmt.htemp_repeat = + dev_read_bool(dev, "dmc,high-temp-repeat"); + + list_add_tail(&priv->dmc->node, &pm_e_head); + + /* 3. Parse thermal zone node */ +parse_tz: + tz_trip0 = ofnode_path(FDT_PATH_THREMAL_TRIP_POINT0); + if (!ofnode_valid(tz_trip0)) { + debug("DVFS: Can't find %s\n", FDT_PATH_THREMAL_TRIP_POINT0); + goto finish; + } + + tz_temp = ofnode_read_s32_default(tz_trip0, "temperature", -ENODATA); + if (tz_temp == -ENODATA) { + debug("DVFS: Can't get thermal zone trip0 temperature\n"); + goto finish; + } + + cooling_maps = ofnode_path(FDT_PATH_THREMAL_COOLING_MAPS); + if (!ofnode_valid(cooling_maps)) { + debug("DVFS: Can't find %s\n", FDT_PATH_THREMAL_COOLING_MAPS); + goto finish; + } + + ofnode_for_each_subnode(node, cooling_maps) { + ofnode_read_u32_array(node, "cooling-device", &phandle, 1); + name = ofnode_get_name(ofnode_get_by_phandle(phandle)); + if (!name) + continue; + if (strstr(name, "cpu")) { + priv->cpu->lmt.tztemp_limit = true; + priv->cpu->lmt.tz_temp = tz_temp; + } else if (strstr(name, "dmc")) { + priv->dmc->lmt.tztemp_limit = true; + priv->dmc->lmt.tz_temp = tz_temp; + } + } + +finish: + print_e_state(); + + return 0; +} + +static const struct dm_dvfs_ops wtemp_dvfs_ops = { + .apply = wtemp_dvfs_apply, + .repeat_apply = wtemp_dvfs_repeat_apply, +}; + +static int wtemp_dvfs_probe(struct udevice *dev) +{ + struct wtemp_dvfs_priv *priv = dev_get_priv(dev); + int ret; + +#ifdef CONFIG_ROCKCHIP_DMC + struct udevice *ram_dev; + + /* Init dmc */ + ret = uclass_get_device(UCLASS_RAM, 0, &ram_dev); + if (ret) { + printf("DVFS: Get dmc device failed, ret=%d\n", ret); + return ret; + } +#endif + /* Init thermal */ + ret = uclass_get_device(UCLASS_THERMAL, 0, &priv->thermal); + if (ret) { + printf("DVFS: Get thermal device failed, ret=%d\n", ret); + return ret; + } + + return 0; +} + +static const struct udevice_id wtemp_dvfs_match[] = { + { .compatible = "rockchip,uboot-wide-temperature", }, + {}, +}; + +U_BOOT_DRIVER(rockchip_wide_temp_dvfs) = { + .name = "rockchip_wide_temp_dvfs", + .id = UCLASS_DVFS, + .ops = &wtemp_dvfs_ops, + .of_match = wtemp_dvfs_match, + .probe = wtemp_dvfs_probe, + .ofdata_to_platdata = wtemp_dvfs_ofdata_to_platdata, + .priv_auto_alloc_size = sizeof(struct wtemp_dvfs_priv), +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index fdfa9b5f58..addf27639b 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -98,6 +98,7 @@ enum uclass_id { UCLASS_KEY, /* Key */ UCLASS_RC, /* Remote Controller */ UCLASS_CHARGE_DISPLAY, /* Charge display */ + UCLASS_DVFS, /* DVFS policy */ UCLASS_COUNT, UCLASS_INVALID = -1, diff --git a/include/dvfs.h b/include/dvfs.h new file mode 100644 index 0000000000..6b93239bf4 --- /dev/null +++ b/include/dvfs.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2018 Rockchip Electronics Co., Ltd + */ + +#ifndef _DVFS_H_ +#define _DVFS_H_ + +#include + +/** + * dvfs_init() - init first dvfs driver + * + * @apply: do dvfs policy apply if true, otherwise just init. + * @return 0 if OK, 1 on error + */ +int dvfs_init(bool apply); + +/** + * dvfs_apply() - do dvfs policy apply + * + * @dev: dvfs device + * @return 0 if OK, otherwise on error + */ +int dvfs_apply(struct udevice *dev); + +/** + * dvfs_repeat_apply() - do dvfs policy repeat apply + * + * @dev: dvfs device + * @return 0 if OK, otherwise on error + */ +int dvfs_repeat_apply(struct udevice *dev); + +/** + * struct dm_dvfs_ops - Driver model Thermal operations + * + * The uclass interface is implemented by all Thermal devices which use + * driver model. + */ + +struct dm_dvfs_ops { + int (*apply)(struct udevice *dev); + int (*repeat_apply)(struct udevice *dev); +}; + +#endif /* _DVFS_H_ */