Add hw.setperf support for UltraSPARC-IIe support.
authorkettenis <kettenis@openbsd.org>
Mon, 11 Aug 2008 18:20:37 +0000 (18:20 +0000)
committerkettenis <kettenis@openbsd.org>
Mon, 11 Aug 2008 18:20:37 +0000 (18:20 +0000)
tested by miod@, matthieu@, naddy@, jsg@, djm@

sys/arch/sparc64/sparc64/cpu.c

index 2d0dd14..57140c3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpu.c,v 1.46 2008/08/07 21:25:47 kettenis Exp $       */
+/*     $OpenBSD: cpu.c,v 1.47 2008/08/11 18:20:37 kettenis Exp $       */
 /*     $NetBSD: cpu.c,v 1.13 2001/05/26 21:27:15 chs Exp $ */
 
 /*
@@ -53,8 +53,9 @@
  */
 
 #include <sys/param.h>
-#include <sys/systm.h>
 #include <sys/device.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
 
 #include <uvm/uvm_extern.h>
 
@@ -98,6 +99,15 @@ struct cfattach cpu_ca = {
 void cpu_init(struct cpu_info *ci);
 void cpu_hatch(void);
 
+int hummingbird_div(uint64_t);
+uint64_t hummingbird_estar_mode(int);
+void hummingbird_enable_self_refresh(void);
+void hummingbird_disable_self_refresh(void);
+void hummingbird_set_refresh_count(int, int);
+void hummingbird_setperf(int);
+int hummingbird_cpuspeed(int *);
+void hummingbird_init(struct cpu_info *ci);
+
 #define        IU_IMPL(v)      ((((u_int64_t)(v))&VER_IMPL) >> VER_IMPL_SHIFT)
 #define        IU_VERS(v)      ((((u_int64_t)(v))&VER_MASK) >> VER_MASK_SHIFT)
 
@@ -258,6 +268,7 @@ cpu_attach(parent, dev, aux)
         * Allocate cpu_info structure if needed.
         */
        ci = alloc_cpuinfo(ma);
+       ci->ci_node = ma->ma_node;
 
        clk = getpropint(node, "clock-frequency", 0);
        if (clk == 0) {
@@ -366,6 +377,12 @@ cpu_attach(parent, dev, aux)
                       (long)cacheinfo.ec_totalsize/1024,
                       (long)cacheinfo.ec_linesize);
        }
+
+#ifndef SMALL_KERNEL
+       if (impl == IMPL_HUMMINGBIRD)
+               hummingbird_init(ci);
+#endif
+
        printf("\n");
        cache_enable();
 }
@@ -444,6 +461,224 @@ struct cfdriver cpu_cd = {
        NULL, "cpu", DV_DULL
 };
 
+#ifndef SMALL_KERNEL
+
+/*
+ * Hummingbird (UltraSPARC-IIe) has a clock control unit that enables
+ * Energy Star mode.  This only works in combination with unbuffered
+ * DIMMs so it is not supported on all machines with UltraSPARC-IIe
+ * CPUs.
+ */
+
+/* Memory_Control_0 (MC0) register. */
+#define HB_MC0                 0x1fe0000f010ULL
+#define  HB_MC0_SELF_REFRESH           0x00010000
+#define  HB_MC0_REFRESH_COUNT_MASK     0x00007f00
+#define  HB_MC0_REFRESH_COUNT_SHIFT    8
+#define  HB_MC0_REFRESH_COUNT(reg) \
+  (((reg) & HB_MC0_REFRESH_COUNT_MASK) >> HB_MC0_REFRESH_COUNT_SHIFT)
+#define  HB_MC0_REFRESH_CLOCKS_PER_COUNT       64ULL
+#define  HB_MC0_REFRESH_INTERVAL       7800ULL
+
+/* Energy Star register. */
+#define HB_ESTAR               0x1fe0000f080ULL
+#define  HB_ESTAR_MODE_MASK            0x00000007
+#define  HB_ESTAR_MODE_DIV_1           0x00000000
+#define  HB_ESTAR_MODE_DIV_2           0x00000001
+#define  HB_ESTAR_MODE_DIV_4           0x00000003
+#define  HB_ESTAR_MODE_DIV_6           0x00000002
+#define  HB_ESTAR_MODE_DIV_8           0x00000004
+#define  HB_ESTAR_NUM_MODES            5
+
+int hummingbird_divisors[HB_ESTAR_NUM_MODES];
+
+int
+hummingbird_div(uint64_t estar_mode)
+{
+       switch(estar_mode) {
+       case HB_ESTAR_MODE_DIV_1:
+               return 1;
+       case HB_ESTAR_MODE_DIV_2:
+               return 2;
+       case HB_ESTAR_MODE_DIV_4:
+               return 4;
+       case HB_ESTAR_MODE_DIV_6:
+               return 6;
+       case HB_ESTAR_MODE_DIV_8:
+               return 8;
+       default:
+               panic("bad E-Star mode");
+       }
+}
+
+uint64_t
+hummingbird_estar_mode(int div)
+{
+       switch(div) {
+       case 1:
+               return HB_ESTAR_MODE_DIV_1;
+       case 2:
+               return HB_ESTAR_MODE_DIV_2;
+       case 4:
+               return HB_ESTAR_MODE_DIV_4;
+       case 6:
+               return HB_ESTAR_MODE_DIV_6;
+       case 8:
+               return HB_ESTAR_MODE_DIV_8;
+       default:
+               panic("bad clock divisor");
+       }
+}
+
+void
+hummingbird_enable_self_refresh(void)
+{
+       uint64_t reg;
+
+       reg = ldxa(HB_MC0, ASI_PHYS_NON_CACHED);
+       reg |= HB_MC0_SELF_REFRESH;
+       stxa(HB_MC0, ASI_PHYS_NON_CACHED, reg);
+       reg = ldxa(HB_MC0, ASI_PHYS_NON_CACHED);
+}
+
+void
+hummingbird_disable_self_refresh(void)
+{
+       uint64_t reg;
+
+       reg = ldxa(HB_MC0, ASI_PHYS_NON_CACHED);
+       reg &= ~HB_MC0_SELF_REFRESH;
+       stxa(HB_MC0, ASI_PHYS_NON_CACHED, reg);
+       reg = ldxa(HB_MC0, ASI_PHYS_NON_CACHED);
+}
+
+void
+hummingbird_set_refresh_count(int div, int new_div)
+{
+       extern u_int64_t cpu_clockrate[];
+       uint64_t count, new_count;
+       uint64_t delta;
+       uint64_t reg;
+
+       reg = ldxa(HB_MC0, ASI_PHYS_NON_CACHED);
+       count = HB_MC0_REFRESH_COUNT(reg);
+       new_count = (HB_MC0_REFRESH_INTERVAL * cpu_clockrate[0]) /
+               (HB_MC0_REFRESH_CLOCKS_PER_COUNT * new_div * 1000000000);
+       reg &= ~HB_MC0_REFRESH_COUNT_MASK;
+       reg |= (new_count << HB_MC0_REFRESH_COUNT_SHIFT);
+       stxa(HB_MC0, ASI_PHYS_NON_CACHED, reg);
+       reg = ldxa(HB_MC0, ASI_PHYS_NON_CACHED);
+
+       if (new_div > div && (reg & HB_MC0_SELF_REFRESH) == 0) {
+               delta = HB_MC0_REFRESH_CLOCKS_PER_COUNT * 
+                   ((count + new_count) * 1000000UL * div) / cpu_clockrate[0];
+               delay(delta + 1);
+       }
+}
+
+void
+hummingbird_setperf(int level)
+{
+       uint64_t estar_mode, new_estar_mode;
+       uint64_t reg, s;
+       int div, new_div, i;
+
+       new_estar_mode = HB_ESTAR_MODE_DIV_1;
+       for (i = 0; i < HB_ESTAR_NUM_MODES && hummingbird_divisors[i]; i++) {
+               if (level <= 100 / hummingbird_divisors[i])
+                       new_estar_mode =
+                           hummingbird_estar_mode(hummingbird_divisors[i]);
+       }
+
+       reg = ldxa(HB_ESTAR, ASI_PHYS_NON_CACHED);
+       estar_mode = reg & HB_ESTAR_MODE_MASK;
+       if (estar_mode == new_estar_mode)
+               return;
+
+       reg &= ~HB_ESTAR_MODE_MASK;
+       div = hummingbird_div(estar_mode);
+       new_div = hummingbird_div(new_estar_mode);
+
+       s = intr_disable();
+       if (estar_mode == HB_ESTAR_MODE_DIV_1 &&
+           new_estar_mode == HB_ESTAR_MODE_DIV_2) {
+               hummingbird_set_refresh_count(1, 2);
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | HB_ESTAR_MODE_DIV_2);
+               delay(1);
+               hummingbird_enable_self_refresh();
+       } else if (estar_mode == HB_ESTAR_MODE_DIV_2 &&
+           new_estar_mode == HB_ESTAR_MODE_DIV_1) {
+               hummingbird_disable_self_refresh();
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | HB_ESTAR_MODE_DIV_1);
+               delay(1);
+               hummingbird_set_refresh_count(2, 1);
+       } else if (estar_mode == HB_ESTAR_MODE_DIV_1) {
+               /* 
+                * Transition to 1/2 speed first, then to
+                * lower speed.
+                */
+               hummingbird_set_refresh_count(1, 2);
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | HB_ESTAR_MODE_DIV_2);
+               delay(1);
+               hummingbird_enable_self_refresh();
+
+               hummingbird_set_refresh_count(2, new_div);
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | new_estar_mode);
+               delay(1);
+       } else if (new_estar_mode == HB_ESTAR_MODE_DIV_1) {
+               /* 
+                * Transition to 1/2 speed first, then to
+                * full speed.
+                */
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | HB_ESTAR_MODE_DIV_2);
+               delay(1);
+               hummingbird_set_refresh_count(div, 2);
+
+               hummingbird_disable_self_refresh();
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | HB_ESTAR_MODE_DIV_1);
+               delay(1);
+               hummingbird_set_refresh_count(2, 1);
+       } else if (div < new_div) {
+               hummingbird_set_refresh_count(div, new_div);
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | new_estar_mode);
+               delay(1);
+       } else if (div > new_div) {
+               stxa(HB_ESTAR, ASI_PHYS_NON_CACHED, reg | new_estar_mode);
+               delay(1);
+               hummingbird_set_refresh_count(div, new_div);
+       }
+       intr_restore(s);
+}
+
+int
+hummingbird_cpuspeed(int *freq)
+{
+       extern u_int64_t cpu_clockrate[];
+       uint64_t reg;
+       int div;
+
+       reg = ldxa(HB_ESTAR, ASI_PHYS_NON_CACHED);
+       div = hummingbird_div(reg & HB_ESTAR_MODE_MASK);
+       *freq = cpu_clockrate[1] / div;
+       return (0);
+}
+
+void
+hummingbird_init(struct cpu_info *ci)
+{
+       /*
+        * The "clock-divisors" property seems to indicate which
+        * frequency scalings are supported on a particular model.
+        */
+       if (OF_getprop(ci->ci_node, "clock-divisors",
+           &hummingbird_divisors, sizeof(hummingbird_divisors)) <= 0)
+               return;
+
+       cpu_setperf = hummingbird_setperf;
+       cpu_cpuspeed = hummingbird_cpuspeed;    
+}
+#endif
+
 #ifdef MULTIPROCESSOR
 void cpu_mp_startup(void);