summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2018-08-03 16:45:18 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2018-08-03 16:45:18 +0000
commitd613fba149a41438d558f92ccd4bd71951d001bf (patch)
tree8219af4ea5a7203a865df8fc081122c65ca32a48
parent2c4b8de745a0ff2a6a994d7889f98dd2a952d231 (diff)
Implement DVFS support.
ok patrick@
-rw-r--r--sys/arch/arm64/arm64/cpu.c275
-rw-r--r--sys/arch/arm64/include/cpu.h6
-rw-r--r--sys/dev/fdt/rkclock.c28
3 files changed, 280 insertions, 29 deletions
diff --git a/sys/arch/arm64/arm64/cpu.c b/sys/arch/arm64/arm64/cpu.c
index 3db2dd4e4e7..2daa3714c4c 100644
--- a/sys/arch/arm64/arm64/cpu.c
+++ b/sys/arch/arm64/arm64/cpu.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: cpu.c,v 1.19 2018/06/05 09:45:08 jsg Exp $ */
+/* $OpenBSD: cpu.c,v 1.20 2018/08/03 16:45:17 kettenis Exp $ */
/*
* Copyright (c) 2016 Dale Rahn <drahn@dalerahn.com>
@@ -23,6 +23,7 @@
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/sysctl.h>
+#include <sys/task.h>
#include <uvm/uvm.h>
@@ -30,6 +31,7 @@
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/ofw_regulator.h>
#include <dev/ofw/fdt.h>
#include <machine/cpufunc.h>
@@ -119,6 +121,8 @@ struct cfdriver cpu_cd = {
NULL, "cpu", DV_DULL
};
+void cpu_opp_init(struct cpu_info *, uint32_t);
+
void cpu_flush_bp_noop(void);
void cpu_flush_bp_psci(void);
@@ -222,6 +226,7 @@ cpu_attach(struct device *parent, struct device *dev, void *aux)
struct fdt_attach_args *faa = aux;
struct cpu_info *ci;
uint64_t mpidr = READ_SPECIALREG(mpidr_el1);
+ uint32_t opp;
KASSERT(faa->fa_nreg > 0);
@@ -295,6 +300,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);
+
printf("\n");
}
@@ -472,3 +481,267 @@ 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);
+
+void
+cpu_opp_init(struct cpu_info *ci, uint32_t phandle)
+{
+ struct opp_table *ot;
+ int count, node, child;
+ int i;
+
+ 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;
+ ot->ot_opp[count].opp_hz =
+ OF_getpropint64(child, "opp-hz", 0);
+ ot->ot_opp[count].opp_microvolt =
+ OF_getpropint(child, "opp-microvolt", 0);
+ count++;
+ }
+
+ ot->ot_opp_hz_min = ot->ot_opp[0].opp_hz;
+ ot->ot_opp_hz_max = ot->ot_opp[0].opp_hz;
+ for (i = 1; i < ot->ot_nopp; i++) {
+ if (ot->ot_opp[i].opp_hz < ot->ot_opp_hz_min)
+ ot->ot_opp_hz_min = ot->ot_opp[i].opp_hz;
+ if (ot->ot_opp[i].opp_hz > ot->ot_opp_hz_max)
+ ot->ot_opp_hz_max = ot->ot_opp[i].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_cpu_supply = OF_getpropint(ci->ci_node, "cpu-supply", 0);
+
+ /*
+ * Do addional 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;
+
+ 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 = ENODEV;
+ if (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(level);
+ }
+}
+
+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 = ci->ci_opp_idx;
+ 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 && 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;
+
+ CPU_INFO_FOREACH(cii, ci) {
+ struct opp_table *ot = ci->ci_opp_table;
+ uint64_t min, max;
+ uint64_t level_hz, opp_hz;
+ uint32_t opp_microvolt;
+ int opp_idx;
+ 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;
+ opp_microvolt = ot->ot_opp[i].opp_microvolt;
+ opp_idx = i;
+ }
+ }
+ KASSERT(opp_idx >= 0);
+
+ ci->ci_opp_idx = opp_idx;
+ }
+
+ /*
+ * Update the hardware from a task since setting the
+ * regulators might need process context.
+ */
+ task_add(systq, &cpu_opp_task);
+}
diff --git a/sys/arch/arm64/include/cpu.h b/sys/arch/arm64/include/cpu.h
index 9c7f1549836..1731846a109 100644
--- a/sys/arch/arm64/include/cpu.h
+++ b/sys/arch/arm64/include/cpu.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: cpu.h,v 1.9 2018/06/30 10:20:21 kettenis Exp $ */
+/* $OpenBSD: cpu.h,v 1.10 2018/08/03 16:45:17 kettenis Exp $ */
/*
* Copyright (c) 2016 Dale Rahn <drahn@dalerahn.com>
*
@@ -107,6 +107,10 @@ struct cpu_info {
void (*ci_flush_bp)(void);
+ struct opp_table *ci_opp_table;
+ volatile int ci_opp_idx;
+ uint32_t ci_cpu_supply;
+
#ifdef MULTIPROCESSOR
struct srp_hazard ci_srp_hazards[SRP_HAZARD_NUM];
volatile int ci_flags;
diff --git a/sys/dev/fdt/rkclock.c b/sys/dev/fdt/rkclock.c
index 28bf5091e30..6161ea5ffa8 100644
--- a/sys/dev/fdt/rkclock.c
+++ b/sys/dev/fdt/rkclock.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: rkclock.c,v 1.27 2018/08/01 15:55:50 kettenis Exp $ */
+/* $OpenBSD: rkclock.c,v 1.28 2018/08/03 16:45:17 kettenis Exp $ */
/*
* Copyright (c) 2017, 2018 Mark Kettenis <kettenis@openbsd.org>
*
@@ -762,38 +762,12 @@ struct rkclock_softc *rk3399_pmucru_sc;
void
rk3399_init(struct rkclock_softc *sc)
{
- int node;
int i;
/* PMUCRU instance should attach before us. */
KASSERT(rk3399_pmucru_sc != NULL);
/*
- * Since the hardware comes up with a really conservative CPU
- * clock frequency, and U-Boot doesn't set it to a more
- * reasonable default, try to do so here. These defaults were
- * chosen assuming that the voltage for both clusters is at
- * least 1.0 V. Only do this on the Firefly-RK3399 for now
- * where this is likely to be true given the default voltages
- * for the regulators on that board.
- */
- node = OF_finddevice("/");
- if (OF_is_compatible(node, "firefly,firefly-rk3399")) {
- uint32_t idx;
-
- /* Run the "LITTLE" cluster at 1.2 GHz. */
- idx = RK3399_ARMCLKL;
- rk3399_set_frequency(sc, &idx, 1200000000);
-
-#ifdef MULTIPROCESSOR
- /* Switch PLL of the "big" cluster into normal mode. */
- HWRITE4(sc, RK3399_CRU_BPLL_CON(3),
- RK3399_CRU_PLL_PLL_WORK_MODE_MASK << 16 |
- RK3399_CRU_PLL_PLL_WORK_MODE_NORMAL);
-#endif
- }
-
- /*
* The U-Boot shipped on the Theobroma Systems RK3399-Q7
* module is buggy and sets the parent of the clock for the
* "big" cluster to LPLL. Undo that mistake here such that