Import the DVFS code from arm64.
authorkettenis <kettenis@openbsd.org>
Tue, 19 Sep 2023 19:20:33 +0000 (19:20 +0000)
committerkettenis <kettenis@openbsd.org>
Tue, 19 Sep 2023 19:20:33 +0000 (19:20 +0000)
ok jca@, jmatthew@, jsing@

sys/arch/riscv64/include/cpu.h
sys/arch/riscv64/riscv64/cpu.c

index aa1ae17..7ebbeeb 100644 (file)
@@ -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 <mlarkin@openbsd.org>
@@ -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;
index 6825a6b..8ab705d 100644 (file)
@@ -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 <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>
 
@@ -31,6 +32,8 @@
 
 #include <dev/ofw/openfirm.h>
 #include <dev/ofw/ofw_clock.h>
+#include <dev/ofw/ofw_regulator.h>
+#include <dev/ofw/ofw_thermal.h>
 #include <dev/ofw/fdt.h>
 
 /* 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);
+       }
+}