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 <chenjh@rock-chips.com>
This commit is contained in:
parent
3e6a97f92c
commit
0eea0d250e
3
Makefile
3
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/
|
||||
|
|
|
|||
10
cmd/Kconfig
10
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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <console.h>
|
||||
#include <dvfs.h>
|
||||
|
||||
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"
|
||||
);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <console.h>
|
||||
#include <dvfs.h>
|
||||
|
||||
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",
|
||||
};
|
||||
|
|
@ -0,0 +1,639 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <clk.h>
|
||||
#include <dvfs.h>
|
||||
#include <thermal.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <asm/arch/clock.h>
|
||||
#include <power/regulator.h>
|
||||
#ifdef CONFIG_ROCKCHIP_DMC
|
||||
#include <asm/arch/rockchip_dmc.h>
|
||||
#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),
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* (C) Copyright 2018 Rockchip Electronics Co., Ltd
|
||||
*/
|
||||
|
||||
#ifndef _DVFS_H_
|
||||
#define _DVFS_H_
|
||||
|
||||
#include <dm.h>
|
||||
|
||||
/**
|
||||
* 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_ */
|
||||
Loading…
Reference in New Issue