From 753d231a70bf1ed183aaf2c98f9456d303b002b9 Mon Sep 17 00:00:00 2001 From: kettenis Date: Tue, 19 Sep 2023 19:20:33 +0000 Subject: [PATCH] Import the DVFS code from arm64. ok jca@, jmatthew@, jsing@ --- sys/arch/riscv64/include/cpu.h | 7 +- sys/arch/riscv64/riscv64/cpu.c | 335 ++++++++++++++++++++++++++++++++- 2 files changed, 340 insertions(+), 2 deletions(-) diff --git a/sys/arch/riscv64/include/cpu.h b/sys/arch/riscv64/include/cpu.h index aa1ae178f7d..7ebbeeb1900 100644 --- a/sys/arch/riscv64/include/cpu.h +++ b/sys/arch/riscv64/include/cpu.h @@ -1,4 +1,4 @@ -/* $OpenBSD: cpu.h,v 1.18 2023/08/23 01:55:47 cheloha Exp $ */ +/* $OpenBSD: cpu.h,v 1.19 2023/09/19 19:20:33 kettenis Exp $ */ /* * Copyright (c) 2019 Mike Larkin @@ -101,6 +101,11 @@ struct cpu_info { #endif int ci_want_resched; + struct opp_table *ci_opp_table; + volatile int ci_opp_idx; + volatile int ci_opp_max; + uint32_t ci_cpu_supply; + #ifdef MULTIPROCESSOR struct srp_hazard ci_srp_hazards[SRP_HAZARD_NUM]; volatile int ci_flags; diff --git a/sys/arch/riscv64/riscv64/cpu.c b/sys/arch/riscv64/riscv64/cpu.c index 6825a6b3424..8ab705db302 100644 --- a/sys/arch/riscv64/riscv64/cpu.c +++ b/sys/arch/riscv64/riscv64/cpu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cpu.c,v 1.14 2023/06/15 22:18:08 cheloha Exp $ */ +/* $OpenBSD: cpu.c,v 1.15 2023/09/19 19:20:33 kettenis Exp $ */ /* * Copyright (c) 2016 Dale Rahn @@ -23,6 +23,7 @@ #include #include #include +#include #include @@ -31,6 +32,8 @@ #include #include +#include +#include #include /* CPU Identification */ @@ -84,6 +87,8 @@ struct cfdriver cpu_cd = { int cpu_errata_sifive_cip_1200; +void cpu_opp_init(struct cpu_info *, uint32_t); + void cpu_identify(struct cpu_info *ci) { @@ -163,6 +168,7 @@ cpu_attach(struct device *parent, struct device *dev, void *aux) struct fdt_attach_args *faa = aux; struct cpu_info *ci; int node, level; + uint32_t opp; KASSERT(faa->fa_nreg > 0); @@ -234,6 +240,10 @@ cpu_attach(struct device *parent, struct device *dev, void *aux) } #endif + opp = OF_getpropint(ci->ci_node, "operating-points-v2", 0); + if (opp) + cpu_opp_init(ci, opp); + node = faa->fa_node; level = 1; @@ -407,3 +417,326 @@ cpu_unidle(struct cpu_info *ci) } #endif + +/* + * Dynamic voltage and frequency scaling implementation. + */ + +extern int perflevel; + +struct opp { + uint64_t opp_hz; + uint32_t opp_microvolt; +}; + +struct opp_table { + LIST_ENTRY(opp_table) ot_list; + uint32_t ot_phandle; + + struct opp *ot_opp; + u_int ot_nopp; + uint64_t ot_opp_hz_min; + uint64_t ot_opp_hz_max; + + struct cpu_info *ot_master; +}; + +LIST_HEAD(, opp_table) opp_tables = LIST_HEAD_INITIALIZER(opp_tables); +struct task cpu_opp_task; + +void cpu_opp_mountroot(struct device *); +void cpu_opp_dotask(void *); +void cpu_opp_setperf(int); + +uint32_t cpu_opp_get_cooling_level(void *, uint32_t *); +void cpu_opp_set_cooling_level(void *, uint32_t *, uint32_t); + +void +cpu_opp_init(struct cpu_info *ci, uint32_t phandle) +{ + struct opp_table *ot; + struct cooling_device *cd; + int count, node, child; + uint32_t opp_hz, opp_microvolt; + uint32_t values[3]; + int i, j, len; + + LIST_FOREACH(ot, &opp_tables, ot_list) { + if (ot->ot_phandle == phandle) { + ci->ci_opp_table = ot; + return; + } + } + + node = OF_getnodebyphandle(phandle); + if (node == 0) + return; + + if (!OF_is_compatible(node, "operating-points-v2")) + return; + + count = 0; + for (child = OF_child(node); child != 0; child = OF_peer(child)) { + if (OF_getproplen(child, "turbo-mode") == 0) + continue; + count++; + } + if (count == 0) + return; + + ot = malloc(sizeof(struct opp_table), M_DEVBUF, M_ZERO | M_WAITOK); + ot->ot_phandle = phandle; + ot->ot_opp = mallocarray(count, sizeof(struct opp), + M_DEVBUF, M_ZERO | M_WAITOK); + ot->ot_nopp = count; + + count = 0; + for (child = OF_child(node); child != 0; child = OF_peer(child)) { + if (OF_getproplen(child, "turbo-mode") == 0) + continue; + opp_hz = OF_getpropint64(child, "opp-hz", 0); + len = OF_getpropintarray(child, "opp-microvolt", + values, sizeof(values)); + opp_microvolt = 0; + if (len == sizeof(uint32_t) || len == 3 * sizeof(uint32_t)) + opp_microvolt = values[0]; + + /* Insert into the array, keeping things sorted. */ + for (i = 0; i < count; i++) { + if (opp_hz < ot->ot_opp[i].opp_hz) + break; + } + for (j = count; j > i; j--) + ot->ot_opp[j] = ot->ot_opp[j - 1]; + ot->ot_opp[i].opp_hz = opp_hz; + ot->ot_opp[i].opp_microvolt = opp_microvolt; + count++; + } + + ot->ot_opp_hz_min = ot->ot_opp[0].opp_hz; + ot->ot_opp_hz_max = ot->ot_opp[count - 1].opp_hz; + + if (OF_getproplen(node, "opp-shared") == 0) + ot->ot_master = ci; + + LIST_INSERT_HEAD(&opp_tables, ot, ot_list); + + ci->ci_opp_table = ot; + ci->ci_opp_idx = -1; + ci->ci_opp_max = ot->ot_nopp - 1; + ci->ci_cpu_supply = OF_getpropint(ci->ci_node, "cpu-supply", 0); + + cd = malloc(sizeof(struct cooling_device), M_DEVBUF, M_ZERO | M_WAITOK); + cd->cd_node = ci->ci_node; + cd->cd_cookie = ci; + cd->cd_get_level = cpu_opp_get_cooling_level; + cd->cd_set_level = cpu_opp_set_cooling_level; + cooling_device_register(cd); + + /* + * Do additional checks at mountroot when all the clocks and + * regulators are available. + */ + config_mountroot(ci->ci_dev, cpu_opp_mountroot); +} + +void +cpu_opp_mountroot(struct device *self) +{ + struct cpu_info *ci; + CPU_INFO_ITERATOR cii; + int count = 0; + int level = 0; + + if (cpu_setperf) + return; + + CPU_INFO_FOREACH(cii, ci) { + struct opp_table *ot = ci->ci_opp_table; + uint64_t curr_hz; + uint32_t curr_microvolt; + int error; + + if (ot == NULL) + continue; + + /* Skip if this table is shared and we're not the master. */ + if (ot->ot_master && ot->ot_master != ci) + continue; + + /* PWM regulators may need to be explicitly enabled. */ + regulator_enable(ci->ci_cpu_supply); + + curr_hz = clock_get_frequency(ci->ci_node, NULL); + curr_microvolt = regulator_get_voltage(ci->ci_cpu_supply); + + /* Disable if clock isn't implemented. */ + error = ENODEV; + if (curr_hz != 0) + error = clock_set_frequency(ci->ci_node, NULL, curr_hz); + if (error) { + ci->ci_opp_table = NULL; + printf("%s: clock not implemented\n", + ci->ci_dev->dv_xname); + continue; + } + + /* Disable if regulator isn't implemented. */ + error = ci->ci_cpu_supply ? ENODEV : 0; + if (ci->ci_cpu_supply && curr_microvolt != 0) + error = regulator_set_voltage(ci->ci_cpu_supply, + curr_microvolt); + if (error) { + ci->ci_opp_table = NULL; + printf("%s: regulator not implemented\n", + ci->ci_dev->dv_xname); + continue; + } + + /* + * Initialize performance level based on the current + * speed of the first CPU that supports DVFS. + */ + if (level == 0) { + uint64_t min, max; + uint64_t level_hz; + + min = ot->ot_opp_hz_min; + max = ot->ot_opp_hz_max; + level_hz = clock_get_frequency(ci->ci_node, NULL); + level = howmany(100 * (level_hz - min), (max - min)); + } + + count++; + } + + if (count > 0) { + task_set(&cpu_opp_task, cpu_opp_dotask, NULL); + cpu_setperf = cpu_opp_setperf; + + perflevel = (level > 0) ? level : 0; + cpu_setperf(perflevel); + } +} + +void +cpu_opp_dotask(void *arg) +{ + struct cpu_info *ci; + CPU_INFO_ITERATOR cii; + + CPU_INFO_FOREACH(cii, ci) { + struct opp_table *ot = ci->ci_opp_table; + uint64_t curr_hz, opp_hz; + uint32_t curr_microvolt, opp_microvolt; + int opp_idx; + int error = 0; + + if (ot == NULL) + continue; + + /* Skip if this table is shared and we're not the master. */ + if (ot->ot_master && ot->ot_master != ci) + continue; + + opp_idx = MIN(ci->ci_opp_idx, ci->ci_opp_max); + opp_hz = ot->ot_opp[opp_idx].opp_hz; + opp_microvolt = ot->ot_opp[opp_idx].opp_microvolt; + + curr_hz = clock_get_frequency(ci->ci_node, NULL); + curr_microvolt = regulator_get_voltage(ci->ci_cpu_supply); + + if (error == 0 && opp_hz < curr_hz) + error = clock_set_frequency(ci->ci_node, NULL, opp_hz); + if (error == 0 && ci->ci_cpu_supply && + opp_microvolt != 0 && opp_microvolt != curr_microvolt) { + error = regulator_set_voltage(ci->ci_cpu_supply, + opp_microvolt); + } + if (error == 0 && opp_hz > curr_hz) + error = clock_set_frequency(ci->ci_node, NULL, opp_hz); + + if (error) + printf("%s: DVFS failed\n", ci->ci_dev->dv_xname); + } +} + +void +cpu_opp_setperf(int level) +{ + struct cpu_info *ci; + CPU_INFO_ITERATOR cii; + int update = 0; + + CPU_INFO_FOREACH(cii, ci) { + struct opp_table *ot = ci->ci_opp_table; + uint64_t min, max; + uint64_t level_hz, opp_hz; + int opp_idx = -1; + int i; + + if (ot == NULL) + continue; + + /* Skip if this table is shared and we're not the master. */ + if (ot->ot_master && ot->ot_master != ci) + continue; + + min = ot->ot_opp_hz_min; + max = ot->ot_opp_hz_max; + level_hz = min + (level * (max - min)) / 100; + opp_hz = min; + for (i = 0; i < ot->ot_nopp; i++) { + if (ot->ot_opp[i].opp_hz <= level_hz && + ot->ot_opp[i].opp_hz >= opp_hz) + opp_hz = ot->ot_opp[i].opp_hz; + } + + /* Find index of selected operating point. */ + for (i = 0; i < ot->ot_nopp; i++) { + if (ot->ot_opp[i].opp_hz == opp_hz) { + opp_idx = i; + break; + } + } + KASSERT(opp_idx >= 0); + + if (ci->ci_opp_idx != opp_idx) { + ci->ci_opp_idx = opp_idx; + update = 1; + } + } + + /* + * Update the hardware from a task since setting the + * regulators might need process context. + */ + if (update) + task_add(systq, &cpu_opp_task); +} + +uint32_t +cpu_opp_get_cooling_level(void *cookie, uint32_t *cells) +{ + struct cpu_info *ci = cookie; + struct opp_table *ot = ci->ci_opp_table; + + return ot->ot_nopp - ci->ci_opp_max - 1; +} + +void +cpu_opp_set_cooling_level(void *cookie, uint32_t *cells, uint32_t level) +{ + struct cpu_info *ci = cookie; + struct opp_table *ot = ci->ci_opp_table; + int opp_max; + + if (level > (ot->ot_nopp - 1)) + level = ot->ot_nopp - 1; + + opp_max = (ot->ot_nopp - level - 1); + if (ci->ci_opp_max != opp_max) { + ci->ci_opp_max = opp_max; + task_add(systq, &cpu_opp_task); + } +} -- 2.20.1