aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/hw/slw.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/hw/slw.c')
-rw-r--r--roms/skiboot/hw/slw.c1731
1 files changed, 1731 insertions, 0 deletions
diff --git a/roms/skiboot/hw/slw.c b/roms/skiboot/hw/slw.c
new file mode 100644
index 000000000..56ba05b0a
--- /dev/null
+++ b/roms/skiboot/hw/slw.c
@@ -0,0 +1,1731 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Everything to do with deep power saving (stop) states
+ * SLeep/Winkle, Handle ChipTOD chip & configure core timebases
+ *
+ * Copyright 2013-2019 IBM Corp.
+ */
+
+#include <skiboot.h>
+#include <xscom.h>
+#include <xscom-p8-regs.h>
+#include <xscom-p9-regs.h>
+#include <xscom-p10-regs.h>
+#include <io.h>
+#include <cpu.h>
+#include <chip.h>
+#include <mem_region.h>
+#include <chiptod.h>
+#include <interrupts.h>
+#include <timebase.h>
+#include <errorlog.h>
+#include <libfdt/libfdt.h>
+#include <opal-api.h>
+#include <nvram.h>
+#include <sbe-p8.h>
+#include <xive.h>
+
+#include <p10_stop_api.H>
+#include <p8_pore_table_gen_api.H>
+#include <sbe_xip_image.h>
+
+static uint32_t slw_saved_reset[0x100];
+
+static bool slw_current_le = false;
+
+enum wakeup_engine_states wakeup_engine_state = WAKEUP_ENGINE_NOT_PRESENT;
+bool has_deep_states = false;
+
+DEFINE_LOG_ENTRY(OPAL_RC_SLW_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_SLW,
+ OPAL_PLATFORM_FIRMWARE, OPAL_PREDICTIVE_ERR_GENERAL,
+ OPAL_NA);
+
+DEFINE_LOG_ENTRY(OPAL_RC_SLW_SET, OPAL_PLATFORM_ERR_EVT, OPAL_SLW,
+ OPAL_PLATFORM_FIRMWARE, OPAL_INFO,
+ OPAL_NA);
+
+DEFINE_LOG_ENTRY(OPAL_RC_SLW_GET, OPAL_PLATFORM_ERR_EVT, OPAL_SLW,
+ OPAL_PLATFORM_FIRMWARE, OPAL_INFO,
+ OPAL_NA);
+
+DEFINE_LOG_ENTRY(OPAL_RC_SLW_REG, OPAL_PLATFORM_ERR_EVT, OPAL_SLW,
+ OPAL_PLATFORM_FIRMWARE, OPAL_INFO,
+ OPAL_NA);
+
+static void slw_do_rvwinkle(void *data)
+{
+ struct cpu_thread *cpu = this_cpu();
+ struct cpu_thread *master = data;
+ uint64_t lpcr = mfspr(SPR_LPCR);
+ struct proc_chip *chip;
+
+ /* Setup our ICP to receive IPIs */
+ icp_prep_for_pm();
+
+ /* Setup LPCR to wakeup on external interrupts only */
+ mtspr(SPR_LPCR, ((lpcr & ~SPR_LPCR_P8_PECE) | SPR_LPCR_P8_PECE2));
+ isync();
+
+ prlog(PR_DEBUG, "SLW: CPU PIR 0x%04x going to rvwinkle...\n",
+ cpu->pir);
+
+ /* Tell that we got it */
+ cpu->state = cpu_state_rvwinkle;
+
+ enter_p8_pm_state(1);
+
+ /* Restore SPRs */
+ init_shared_sprs();
+ init_replicated_sprs();
+
+ /* Ok, it's ours again */
+ cpu->state = cpu_state_active;
+
+ prlog(PR_DEBUG, "SLW: CPU PIR 0x%04x woken up !\n", cpu->pir);
+
+ /* Cleanup our ICP */
+ reset_cpu_icp();
+
+ /* Resync timebase */
+ chiptod_wakeup_resync();
+
+ /* Restore LPCR */
+ mtspr(SPR_LPCR, lpcr);
+ isync();
+
+ /* If we are passed a master pointer we are the designated
+ * waker, let's proceed. If not, return, we are finished.
+ */
+ if (!master)
+ return;
+
+ prlog(PR_DEBUG, "SLW: CPU PIR 0x%04x waiting for master...\n",
+ cpu->pir);
+
+ /* Allriiiight... now wait for master to go down */
+ while(master->state != cpu_state_rvwinkle)
+ sync();
+
+ /* XXX Wait one second ! (should check xscom state ? ) */
+ time_wait_ms(1000);
+
+ for_each_chip(chip) {
+ struct cpu_thread *c;
+ uint64_t tmp;
+ for_each_available_core_in_chip(c, chip->id) {
+ xscom_read(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(pir_to_core_id(c->pir),
+ EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &tmp);
+ prlog(PR_TRACE, "SLW: core %x:%x"
+ " history: 0x%016llx (mid2)\n",
+ chip->id, pir_to_core_id(c->pir),
+ tmp);
+ }
+ }
+
+ prlog(PR_DEBUG, "SLW: Waking master (PIR 0x%04x)...\n", master->pir);
+
+ /* Now poke all the secondary threads on the master's core */
+ for_each_cpu(cpu) {
+ if (!cpu_is_sibling(cpu, master) || (cpu == master))
+ continue;
+ icp_kick_cpu(cpu);
+
+ /* Wait for it to claim to be back (XXX ADD TIMEOUT) */
+ while(cpu->state != cpu_state_active)
+ sync();
+ }
+
+ /* Now poke the master and be gone */
+ icp_kick_cpu(master);
+}
+
+static void slw_patch_reset(void)
+{
+ uint32_t *src, *dst, *sav;
+
+ src = &reset_patch_start;
+ dst = (uint32_t *)0x100;
+ sav = slw_saved_reset;
+ while(src < &reset_patch_end) {
+ *(sav++) = *(dst);
+ *(dst++) = *(src++);
+ }
+ sync_icache();
+}
+
+static void slw_unpatch_reset(void)
+{
+ extern uint32_t reset_patch_start;
+ extern uint32_t reset_patch_end;
+ uint32_t *src, *dst, *sav;
+
+ src = &reset_patch_start;
+ dst = (uint32_t *)0x100;
+ sav = slw_saved_reset;
+ while(src < &reset_patch_end) {
+ *(dst++) = *(sav++);
+ src++;
+ }
+ sync_icache();
+}
+
+static bool slw_general_init(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint32_t core = pir_to_core_id(c->pir);
+ uint64_t tmp;
+ int rc;
+
+ /* PowerManagement GP0 clear PM_DISABLE */
+ rc = xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP0), &tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_INIT),
+ "SLW: Failed to read PM_GP0\n");
+ return false;
+ }
+ tmp = tmp & ~0x8000000000000000ULL;
+ rc = xscom_write(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP0), tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_INIT),
+ "SLW: Failed to write PM_GP0\n");
+ return false;
+ }
+ prlog(PR_TRACE, "SLW: PMGP0 set to 0x%016llx\n", tmp);
+
+ /* Read back for debug */
+ rc = xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP0), &tmp);
+ if (rc)
+ log_simple_error(&e_info(OPAL_RC_SLW_INIT),
+ "SLW: Failed to re-read PM_GP0. Continuing...\n");
+
+ prlog(PR_TRACE, "SLW: PMGP0 read 0x%016llx\n", tmp);
+
+ return true;
+}
+
+static bool slw_set_overrides(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint32_t core = pir_to_core_id(c->pir);
+ int rc;
+
+ rc = xscom_write(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_SPECIAL_WAKEUP_PHYP),
+ 0);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_SET),
+ "SLW: Failed to write PM_SPECIAL_WAKEUP_PHYP\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool slw_set_overrides_p10(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint64_t tmp;
+ int rc;
+ uint32_t core = pir_to_core_id(c->pir);
+
+ /* Special wakeup bits that could hold power mgt */
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P10_QME_CORE(core, P10_QME_SPWU_HYP),
+ &tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_SET),
+ "SLW: Failed to read P10_QME_SPWU_HYP\n");
+ return false;
+ }
+ if (tmp & P10_SPWU_REQ)
+ prlog(PR_WARNING,
+ "SLW: core %d P10_QME_SPWU_HYP requested 0x%016llx\n",
+ core, tmp);
+
+ return true;
+}
+
+
+static bool slw_set_overrides_p9(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint64_t tmp;
+ int rc;
+ uint32_t core = pir_to_core_id(c->pir);
+
+ /* Special wakeup bits that could hold power mgt */
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P9_EC_SLAVE(core, EC_PPM_SPECIAL_WKUP_HYP),
+ &tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_SET),
+ "SLW: Failed to read EC_PPM_SPECIAL_WKUP_HYP\n");
+ return false;
+ }
+ if (tmp)
+ prlog(PR_WARNING,
+ "SLW: core %d EC_PPM_SPECIAL_WKUP_HYP read 0x%016llx\n",
+ core, tmp);
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P9_EC_SLAVE(core, EC_PPM_SPECIAL_WKUP_OTR),
+ &tmp);
+ if (tmp)
+ prlog(PR_WARNING,
+ "SLW: core %d EC_PPM_SPECIAL_WKUP_OTR read 0x%016llx\n",
+ core, tmp);
+ return true;
+}
+
+static bool slw_unset_overrides(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint32_t core = pir_to_core_id(c->pir);
+
+ /* XXX FIXME: Save and restore the overrides */
+ prlog(PR_DEBUG, "SLW: slw_unset_overrides %x:%x\n", chip->id, core);
+ return true;
+}
+
+static bool slw_set_idle_mode(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint32_t core = pir_to_core_id(c->pir);
+ uint64_t tmp;
+ int rc;
+
+ /*
+ * PM GP1 allows fast/deep mode to be selected independently for sleep
+ * and winkle. Init PM GP1 so that sleep happens in fast mode and
+ * winkle happens in deep mode.
+ * Make use of the OR XSCOM for this since the OCC might be manipulating
+ * the PM_GP1 register as well. Before doing this ensure that the bits
+ * managing idle states are cleared so as to override any bits set at
+ * init time.
+ */
+
+ tmp = ~EX_PM_GP1_SLEEP_WINKLE_MASK;
+ rc = xscom_write(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_CLEAR_GP1),
+ tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_SET),
+ "SLW: Failed to write PM_GP1\n");
+ return false;
+ }
+
+ rc = xscom_write(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_SET_GP1),
+ EX_PM_SETUP_GP1_FAST_SLEEP_DEEP_WINKLE);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_SET),
+ "SLW: Failed to write PM_GP1\n");
+ return false;
+ }
+
+ /* Read back for debug */
+ xscom_read(chip->id, XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_GP1), &tmp);
+ prlog(PR_TRACE, "SLW: PMGP1 read 0x%016llx\n", tmp);
+ return true;
+}
+
+static bool slw_get_idle_state_history(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint32_t core = pir_to_core_id(c->pir);
+ uint64_t tmp;
+ int rc;
+
+ /* Cleanup history */
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_GET),
+ "SLW: Failed to read PM_IDLE_STATE_HISTORY\n");
+ return false;
+ }
+
+ prlog(PR_TRACE, "SLW: core %x:%x history: 0x%016llx (old1)\n",
+ chip->id, core, tmp);
+
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(core, EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &tmp);
+
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_GET),
+ "SLW: Failed to read PM_IDLE_STATE_HISTORY\n");
+ return false;
+ }
+
+ prlog(PR_TRACE, "SLW: core %x:%x history: 0x%016llx (old2)\n",
+ chip->id, core, tmp);
+
+ return true;
+}
+
+static bool idle_prepare_core(struct proc_chip *chip, struct cpu_thread *c)
+{
+ prlog(PR_TRACE, "FASTSLEEP: Prepare core %x:%x\n",
+ chip->id, pir_to_core_id(c->pir));
+
+ if(!slw_general_init(chip, c))
+ return false;
+ if(!slw_set_overrides(chip, c))
+ return false;
+ if(!slw_set_idle_mode(chip, c))
+ return false;
+ if(!slw_get_idle_state_history(chip, c))
+ return false;
+
+ return true;
+
+}
+
+/* Define device-tree fields */
+#define MAX_NAME_LEN 16
+struct cpu_idle_states {
+ char name[MAX_NAME_LEN];
+ u32 latency_ns;
+ u32 residency_ns;
+ /*
+ * Register value/mask used to select different idle states.
+ * PMICR in POWER8 and PSSCR in POWER9
+ */
+ u64 pm_ctrl_reg_val;
+ u64 pm_ctrl_reg_mask;
+ u32 flags;
+};
+
+static struct cpu_idle_states nap_only_cpu_idle_states[] = {
+ { /* nap */
+ .name = "nap",
+ .latency_ns = 4000,
+ .residency_ns = 100000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_NAP_ENABLED \
+ | 0*OPAL_PM_SLEEP_ENABLED \
+ | 0*OPAL_PM_WINKLE_ENABLED \
+ | 0*OPAL_USE_PMICR,
+ .pm_ctrl_reg_val = 0,
+ .pm_ctrl_reg_mask = 0 },
+};
+
+static struct cpu_idle_states power8_cpu_idle_states[] = {
+ { /* nap */
+ .name = "nap",
+ .latency_ns = 4000,
+ .residency_ns = 100000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_NAP_ENABLED \
+ | 0*OPAL_USE_PMICR,
+ .pm_ctrl_reg_val = 0,
+ .pm_ctrl_reg_mask = 0 },
+ { /* fast sleep (with workaround) */
+ .name = "fastsleep_",
+ .latency_ns = 40000,
+ .residency_ns = 300000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_SLEEP_ENABLED_ER1 \
+ | 0*OPAL_USE_PMICR, /* Not enabled until deep
+ states are available */
+ .pm_ctrl_reg_val = OPAL_PM_FASTSLEEP_PMICR,
+ .pm_ctrl_reg_mask = OPAL_PM_SLEEP_PMICR_MASK },
+ { /* Winkle */
+ .name = "winkle",
+ .latency_ns = 10000000,
+ .residency_ns = 1000000000, /* Educated guess (not measured).
+ * Winkle is not currently used by
+ * linux cpuidle subsystem so we
+ * don't have real world user.
+ * However, this should be roughly
+ * accurate for when linux does
+ * use it. */
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_WINKLE_ENABLED \
+ | 0*OPAL_USE_PMICR, /* Currently choosing deep vs
+ fast via EX_PM_GP1 reg */
+ .pm_ctrl_reg_val = 0,
+ .pm_ctrl_reg_mask = 0 },
+};
+
+/*
+ * cpu_idle_states for key idle states of POWER9 that we want to
+ * exploit.
+ * Note latency_ns and residency_ns are estimated values for now.
+ */
+static struct cpu_idle_states power9_cpu_idle_states[] = {
+ {
+ .name = "stop0_lite", /* Enter stop0 with no state loss */
+ .latency_ns = 1000,
+ .residency_ns = 10000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 0*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3),
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop0",
+ .latency_ns = 2000,
+ .residency_ns = 20000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+ /* stop1_lite has been removed since it adds no additional benefit over stop0_lite */
+
+ {
+ .name = "stop1",
+ .latency_ns = 5000,
+ .residency_ns = 50000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(1) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ /*
+ * stop2_lite has been removed since currently it adds minimal benefit over stop2.
+ * However, the benefit is eclipsed by the time required to ungate the clocks
+ */
+
+ {
+ .name = "stop2",
+ .latency_ns = 10000,
+ .residency_ns = 100000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(2) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop4",
+ .latency_ns = 100000,
+ .residency_ns = 10000000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(4) \
+ | OPAL_PM_PSSCR_MTL(7) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop5",
+ .latency_ns = 200000,
+ .residency_ns = 20000000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(5) \
+ | OPAL_PM_PSSCR_MTL(7) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+ {
+ .name = "stop8",
+ .latency_ns = 2000000,
+ .residency_ns = 20000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(8) \
+ | OPAL_PM_PSSCR_MTL(11) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+ {
+ .name = "stop11",
+ .latency_ns = 10000000,
+ .residency_ns = 100000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(11) \
+ | OPAL_PM_PSSCR_MTL(11) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+};
+
+/*
+ * Prior to Mambo.7.8.21, mambo did set the MSR correctly for lite stop
+ * states, so disable them for now.
+ */
+static struct cpu_idle_states power9_mambo_cpu_idle_states[] = {
+ {
+ .name = "stop0",
+ .latency_ns = 2000,
+ .residency_ns = 20000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop1",
+ .latency_ns = 5000,
+ .residency_ns = 50000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(1) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop2",
+ .latency_ns = 10000,
+ .residency_ns = 100000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(2) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop4",
+ .latency_ns = 100000,
+ .residency_ns = 1000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(4) \
+ | OPAL_PM_PSSCR_MTL(7) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+ {
+ .name = "stop8",
+ .latency_ns = 2000000,
+ .residency_ns = 20000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(8) \
+ | OPAL_PM_PSSCR_MTL(11) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+ {
+ .name = "stop11",
+ .latency_ns = 10000000,
+ .residency_ns = 100000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(11) \
+ | OPAL_PM_PSSCR_MTL(11) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+};
+
+/*
+ * cpu_idle_states for fused core configuration
+ * These will be a subset of power9 idle states.
+ */
+static struct cpu_idle_states power9_fusedcore_cpu_idle_states[] = {
+ {
+ .name = "stop0_lite", /* Enter stop0 with no state loss */
+ .latency_ns = 1000,
+ .residency_ns = 10000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 0*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3),
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop0",
+ .latency_ns = 2000,
+ .residency_ns = 20000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+
+ /* stop1_lite has been removed since it adds no additional benefit over stop0_lite */
+
+ {
+ .name = "stop1",
+ .latency_ns = 5000,
+ .residency_ns = 50000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(1) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ /*
+ * stop2_lite has been removed since currently it adds minimal benefit over stop2.
+ * However, the benefit is eclipsed by the time required to ungate the clocks
+ */
+
+ {
+ .name = "stop2",
+ .latency_ns = 10000,
+ .residency_ns = 100000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(2) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+};
+
+/*
+ * Note latency_ns and residency_ns are estimated values for now.
+ */
+static struct cpu_idle_states power10_cpu_idle_states[] = {
+ {
+ .name = "stop0_lite", /* Enter stop0 with no state loss */
+ .latency_ns = 1000,
+ .residency_ns = 10000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 0*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(0) \
+ | OPAL_PM_PSSCR_TR(3),
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop0",
+ .latency_ns = 10000,
+ .residency_ns = 100000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(0) \
+ | OPAL_PM_PSSCR_MTL(0) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop2",
+ .latency_ns = 20000,
+ .residency_ns = 200000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(2) \
+ | OPAL_PM_PSSCR_MTL(2) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+ {
+ .name = "stop3",
+ .latency_ns = 45000,
+ .residency_ns = 450000,
+ .flags = 0*OPAL_PM_DEC_STOP \
+ | 0*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 0*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 0*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_FAST,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(3) \
+ | OPAL_PM_PSSCR_MTL(3) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+#if 0
+ {
+ .name = "stop11",
+ .latency_ns = 10000000,
+ .residency_ns = 100000000,
+ .flags = 1*OPAL_PM_DEC_STOP \
+ | 1*OPAL_PM_TIMEBASE_STOP \
+ | 1*OPAL_PM_LOSE_USER_CONTEXT \
+ | 1*OPAL_PM_LOSE_HYP_CONTEXT \
+ | 1*OPAL_PM_LOSE_FULL_CONTEXT \
+ | 1*OPAL_PM_STOP_INST_DEEP,
+ .pm_ctrl_reg_val = OPAL_PM_PSSCR_RL(11) \
+ | OPAL_PM_PSSCR_MTL(11) \
+ | OPAL_PM_PSSCR_TR(3) \
+ | OPAL_PM_PSSCR_ESL \
+ | OPAL_PM_PSSCR_EC,
+ .pm_ctrl_reg_mask = OPAL_PM_PSSCR_MASK },
+#endif
+};
+
+static void slw_late_init_p9(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+ int rc;
+
+ prlog(PR_INFO, "SLW: Configuring self-restore for HRMOR\n");
+ for_each_available_cpu(c) {
+ if (c->chip_id != chip->id)
+ continue;
+ /*
+ * Clear HRMOR. Need to update only for thread
+ * 0 of each core. Doing it anyway for all threads
+ */
+ rc = p9_stop_save_cpureg((void *)chip->homer_base,
+ P9_STOP_SPR_HRMOR, 0,
+ c->pir);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Failed to set HRMOR for CPU %x,RC=0x%x\n",
+ c->pir, rc);
+ prlog(PR_ERR, "Disabling deep stop states\n");
+ }
+ }
+}
+
+static void slw_late_init_p10(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+ int rc;
+
+ prlog(PR_INFO, "SLW: Configuring self-restore for HRMOR\n");
+ for_each_available_cpu(c) {
+ if (c->chip_id != chip->id)
+ continue;
+ /*
+ * Clear HRMOR. Need to update only for thread
+ * 0 of each core. Doing it anyway for all threads
+ */
+ rc = proc_stop_save_cpureg((void *)chip->homer_base,
+ PROC_STOP_SPR_HRMOR, 0,
+ c->pir);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Failed to set HRMOR for CPU %x,RC=0x%x\n",
+ c->pir, rc);
+ prlog(PR_ERR, "Disabling deep stop states\n");
+ }
+ }
+}
+
+/* Add device tree properties to describe idle states */
+void add_cpu_idle_state_properties(void)
+{
+ struct dt_node *power_mgt;
+ struct cpu_idle_states *states;
+ struct proc_chip *chip;
+ int nr_states;
+
+ bool can_sleep = true;
+ bool has_stop_inst = false;
+ u8 i;
+
+ fdt64_t *pm_ctrl_reg_val_buf;
+ fdt64_t *pm_ctrl_reg_mask_buf;
+ u32 supported_states_mask;
+ u32 opal_disabled_states_mask = ~0xFC000000; /* all but stop11 */
+ const char* nvram_disable_str;
+ u32 nvram_disabled_states_mask = 0x00;
+ u32 stop_levels;
+
+ /* Variables to track buffer length */
+ u8 name_buf_len;
+ u8 num_supported_idle_states;
+
+ /* Buffers to hold idle state properties */
+ char *name_buf, *alloced_name_buf;
+ fdt32_t *latency_ns_buf;
+ fdt32_t *residency_ns_buf;
+ fdt32_t *flags_buf;
+
+ prlog(PR_DEBUG, "CPU idle state device tree init\n");
+
+ /* Create /ibm,opal/power-mgt if it doesn't exist already */
+ power_mgt = dt_new_check(opal_node, "power-mgt");
+ if (!power_mgt) {
+ /**
+ * @fwts-label CreateDTPowerMgtNodeFail
+ * @fwts-advice OPAL failed to add the power-mgt device tree
+ * node. This could mean that firmware ran out of memory,
+ * or there's a bug somewhere.
+ */
+ prlog(PR_ERR, "creating dt node /ibm,opal/power-mgt failed\n");
+ return;
+ }
+
+ /*
+ * Chose the right state table for the chip
+ *
+ * XXX We use the first chip version, we should probably look
+ * for the smaller of all chips instead..
+ */
+ chip = next_chip(NULL);
+ assert(chip);
+ if (proc_gen >= proc_gen_p9) {
+ if (chip->type == PROC_CHIP_P9_NIMBUS ||
+ chip->type == PROC_CHIP_P9_CUMULUS ||
+ chip->type == PROC_CHIP_P9P) {
+ if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS) {
+ states = power9_mambo_cpu_idle_states;
+ nr_states = ARRAY_SIZE(power9_mambo_cpu_idle_states);
+ } else if (this_cpu()->is_fused_core) {
+ states = power9_fusedcore_cpu_idle_states;
+ nr_states = ARRAY_SIZE(power9_fusedcore_cpu_idle_states);
+ } else {
+ states = power9_cpu_idle_states;
+ nr_states = ARRAY_SIZE(power9_cpu_idle_states);
+ }
+ } else if (chip->type == PROC_CHIP_P10) {
+ states = power10_cpu_idle_states;
+ nr_states = ARRAY_SIZE(power10_cpu_idle_states);
+ } else {
+ prlog(PR_ERR, "determining chip type\n");
+ return;
+ }
+
+ has_stop_inst = true;
+ stop_levels = dt_prop_get_u32_def(power_mgt,
+ "ibm,enabled-stop-levels", 0);
+ if (!stop_levels) {
+ prerror("SLW: No stop levels available. Power saving is disabled!\n");
+ has_deep_states = false;
+ } else {
+ /* Iterate to see if we have deep states enabled */
+ for (i = 0; i < nr_states; i++) {
+ u32 level = 31 - (states[i].pm_ctrl_reg_val &
+ OPAL_PM_PSSCR_RL_MASK);
+
+ if ((stop_levels & (1ul << level)) &&
+ (states[i].flags & OPAL_PM_STOP_INST_DEEP))
+ has_deep_states = true;
+ }
+ }
+ if ((wakeup_engine_state == WAKEUP_ENGINE_PRESENT) && has_deep_states) {
+ if (chip->type == PROC_CHIP_P9_NIMBUS ||
+ chip->type == PROC_CHIP_P9_CUMULUS) {
+ slw_late_init_p9(chip);
+ xive_late_init();
+ nx_p9_rng_late_init();
+ } else if (chip->type == PROC_CHIP_P10) {
+ slw_late_init_p10(chip);
+ xive2_late_init();
+ }
+ }
+ if (wakeup_engine_state != WAKEUP_ENGINE_PRESENT)
+ has_deep_states = false;
+ } else if (chip->type == PROC_CHIP_P8_MURANO ||
+ chip->type == PROC_CHIP_P8_VENICE ||
+ chip->type == PROC_CHIP_P8_NAPLES) {
+ const struct dt_property *p;
+
+ p = dt_find_property(dt_root, "ibm,enabled-idle-states");
+ if (p)
+ prlog(PR_NOTICE,
+ "SLW: HB-provided idle states property found\n");
+ states = power8_cpu_idle_states;
+ nr_states = ARRAY_SIZE(power8_cpu_idle_states);
+
+ /* Check if hostboot say we can sleep */
+ if (!p || !dt_prop_find_string(p, "fast-sleep")) {
+ prlog(PR_WARNING, "SLW: Sleep not enabled by HB"
+ " on this platform\n");
+ can_sleep = false;
+ }
+
+ /* Clip to NAP only on Murano and Venice DD1.x */
+ if ((chip->type == PROC_CHIP_P8_MURANO ||
+ chip->type == PROC_CHIP_P8_VENICE) &&
+ chip->ec_level < 0x20) {
+ prlog(PR_NOTICE, "SLW: Sleep not enabled on P8 DD1.x\n");
+ can_sleep = false;
+ }
+
+ } else {
+ states = nap_only_cpu_idle_states;
+ nr_states = ARRAY_SIZE(nap_only_cpu_idle_states);
+ }
+
+
+ /*
+ * Currently we can't append strings and cells to dt properties.
+ * So create buffers to which you can append values, then create
+ * dt properties with this buffer content.
+ */
+
+ /* Allocate memory to idle state property buffers. */
+ alloced_name_buf= malloc(nr_states * sizeof(char) * MAX_NAME_LEN);
+ name_buf = alloced_name_buf;
+ latency_ns_buf = malloc(nr_states * sizeof(u32));
+ residency_ns_buf= malloc(nr_states * sizeof(u32));
+ flags_buf = malloc(nr_states * sizeof(u32));
+ pm_ctrl_reg_val_buf = malloc(nr_states * sizeof(u64));
+ pm_ctrl_reg_mask_buf = malloc(nr_states * sizeof(u64));
+
+ name_buf_len = 0;
+ num_supported_idle_states = 0;
+
+ /*
+ * Create a mask with the flags of all supported idle states
+ * set. Use this to only add supported idle states to the
+ * device-tree
+ */
+ if (has_stop_inst) {
+ /* Power 9/10 / POWER ISA 3.0 and above */
+ supported_states_mask = OPAL_PM_STOP_INST_FAST;
+ if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT)
+ supported_states_mask |= OPAL_PM_STOP_INST_DEEP;
+ } else {
+ /* Power 7 and Power 8 */
+ supported_states_mask = OPAL_PM_NAP_ENABLED;
+ if (can_sleep)
+ supported_states_mask |= OPAL_PM_SLEEP_ENABLED |
+ OPAL_PM_SLEEP_ENABLED_ER1;
+ if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT)
+ supported_states_mask |= OPAL_PM_WINKLE_ENABLED;
+ }
+ nvram_disable_str = nvram_query_dangerous("opal-stop-state-disable-mask");
+ if (nvram_disable_str)
+ nvram_disabled_states_mask = strtol(nvram_disable_str, NULL, 0);
+ prlog(PR_DEBUG, "NVRAM stop disable mask: %x\n", nvram_disabled_states_mask);
+ for (i = 0; i < nr_states; i++) {
+ /* For each state, check if it is one of the supported states. */
+ if (!(states[i].flags & supported_states_mask))
+ continue;
+
+ /* We can only use the stop levels that HB has made available */
+ if (has_stop_inst) {
+ u32 level = 31 - (states[i].pm_ctrl_reg_val &
+ OPAL_PM_PSSCR_RL_MASK);
+
+ if (!(stop_levels & (1ul << level)))
+ continue;
+
+ if ((opal_disabled_states_mask |
+ nvram_disabled_states_mask) &
+ (1ul << level)) {
+ if (nvram_disable_str &&
+ !(nvram_disabled_states_mask & (1ul << level))) {
+ prlog(PR_NOTICE, "SLW: Enabling: %s "
+ "(disabled in OPAL, forced by "
+ "NVRAM)\n",states[i].name);
+ } else {
+ prlog(PR_NOTICE, "SLW: Disabling: %s in OPAL\n",
+ states[i].name);
+ continue;
+ }
+ }
+ }
+
+ prlog(PR_INFO, "SLW: Enabling: %s\n", states[i].name);
+
+ /*
+ * If a state is supported add each of its property
+ * to its corresponding property buffer.
+ */
+ strncpy(name_buf, states[i].name, MAX_NAME_LEN);
+ name_buf = name_buf + strlen(states[i].name) + 1;
+
+ *latency_ns_buf = cpu_to_fdt32(states[i].latency_ns);
+ latency_ns_buf++;
+
+ *residency_ns_buf = cpu_to_fdt32(states[i].residency_ns);
+ residency_ns_buf++;
+
+ *flags_buf = cpu_to_fdt32(states[i].flags);
+ flags_buf++;
+
+ *pm_ctrl_reg_val_buf = cpu_to_fdt64(states[i].pm_ctrl_reg_val);
+ pm_ctrl_reg_val_buf++;
+
+ *pm_ctrl_reg_mask_buf = cpu_to_fdt64(states[i].pm_ctrl_reg_mask);
+ pm_ctrl_reg_mask_buf++;
+
+ /* Increment buffer length trackers */
+ name_buf_len += strlen(states[i].name) + 1;
+ num_supported_idle_states++;
+
+ }
+
+ /* Point buffer pointers back to beginning of the buffer */
+ name_buf -= name_buf_len;
+ latency_ns_buf -= num_supported_idle_states;
+ residency_ns_buf -= num_supported_idle_states;
+ flags_buf -= num_supported_idle_states;
+ pm_ctrl_reg_val_buf -= num_supported_idle_states;
+ pm_ctrl_reg_mask_buf -= num_supported_idle_states;
+ /* Create dt properties with the buffer content */
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-names", name_buf,
+ name_buf_len* sizeof(char));
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-latencies-ns",
+ latency_ns_buf, num_supported_idle_states * sizeof(u32));
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-residency-ns",
+ residency_ns_buf, num_supported_idle_states * sizeof(u32));
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-flags", flags_buf,
+ num_supported_idle_states * sizeof(u32));
+
+ if (has_stop_inst) {
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-psscr",
+ pm_ctrl_reg_val_buf,
+ num_supported_idle_states * sizeof(u64));
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-psscr-mask",
+ pm_ctrl_reg_mask_buf,
+ num_supported_idle_states * sizeof(u64));
+ } else {
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-pmicr",
+ pm_ctrl_reg_val_buf,
+ num_supported_idle_states * sizeof(u64));
+ dt_add_property(power_mgt, "ibm,cpu-idle-state-pmicr-mask",
+ pm_ctrl_reg_mask_buf,
+ num_supported_idle_states * sizeof(u64));
+ }
+ assert(alloced_name_buf == name_buf);
+ free(alloced_name_buf);
+ free(latency_ns_buf);
+ free(residency_ns_buf);
+ free(flags_buf);
+ free(pm_ctrl_reg_val_buf);
+ free(pm_ctrl_reg_mask_buf);
+}
+
+static void slw_cleanup_core(struct proc_chip *chip, struct cpu_thread *c)
+{
+ uint64_t tmp;
+ int rc;
+
+ /* Display history to check transition */
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(pir_to_core_id(c->pir),
+ EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_GET),
+ "SLW: Failed to read PM_IDLE_STATE_HISTORY\n");
+ /* XXX error handling ? return false; */
+ }
+
+ prlog(PR_DEBUG, "SLW: core %x:%x history: 0x%016llx (new1)\n",
+ chip->id, pir_to_core_id(c->pir), tmp);
+
+ rc = xscom_read(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(pir_to_core_id(c->pir),
+ EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &tmp);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_GET),
+ "SLW: Failed to read PM_IDLE_STATE_HISTORY\n");
+ /* XXX error handling ? return false; */
+ }
+
+ prlog(PR_DEBUG, "SLW: core %x:%x history: 0x%016llx (new2)\n",
+ chip->id, pir_to_core_id(c->pir), tmp);
+
+ /*
+ * XXX FIXME: Error out if the transition didn't reach rvwinkle ?
+ */
+
+ /*
+ * XXX FIXME: We should restore a bunch of the EX bits we
+ * overwrite to sane values here
+ */
+ slw_unset_overrides(chip, c);
+}
+
+static void slw_cleanup_chip(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+
+ for_each_available_core_in_chip(c, chip->id)
+ slw_cleanup_core(chip, c);
+}
+
+static void slw_patch_scans(struct proc_chip *chip, bool le_mode)
+{
+ int64_t rc;
+ uint64_t old_val, new_val;
+
+ rc = sbe_xip_get_scalar((void *)chip->slw_base,
+ "skip_ex_override_ring_scans", &old_val);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Failed to read scan override on chip %d\n",
+ chip->id);
+ return;
+ }
+
+ new_val = le_mode ? 0 : 1;
+
+ prlog(PR_TRACE, "SLW: Chip %d, LE value was: %lld, setting to %lld\n",
+ chip->id, old_val, new_val);
+
+ rc = sbe_xip_set_scalar((void *)chip->slw_base,
+ "skip_ex_override_ring_scans", new_val);
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Failed to set LE mode on chip %d\n", chip->id);
+ return;
+ }
+}
+
+int64_t slw_reinit(uint64_t flags)
+{
+ struct proc_chip *chip;
+ struct cpu_thread *cpu;
+ bool has_waker = false;
+ bool target_le = slw_current_le;
+
+ if (flags & OPAL_REINIT_CPUS_HILE_BE)
+ target_le = false;
+ if (flags & OPAL_REINIT_CPUS_HILE_LE)
+ target_le = true;
+
+ prlog(PR_TRACE, "SLW Reinit from CPU PIR 0x%04x,"
+ " HILE set to %s endian...\n",
+ this_cpu()->pir,
+ target_le ? "little" : "big");
+
+ /* Prepare chips/cores for rvwinkle */
+ for_each_chip(chip) {
+ if (!chip->slw_base) {
+ log_simple_error(&e_info(OPAL_RC_SLW_INIT),
+ "SLW: Not found on chip %d\n", chip->id);
+ return OPAL_HARDWARE;
+ }
+
+ slw_patch_scans(chip, target_le);
+ }
+ slw_current_le = target_le;
+
+ /* XXX Save HIDs ? Or do that in head.S ... */
+
+ slw_patch_reset();
+
+ /* rvwinkle everybody and pick one to wake me once I rvwinkle myself */
+ for_each_available_cpu(cpu) {
+ struct cpu_thread *master = NULL;
+
+ if (cpu == this_cpu())
+ continue;
+
+ /* Pick up a waker for myself: it must not be a sibling of
+ * the current CPU and must be a thread 0 (so it gets to
+ * sync its timebase before doing time_wait_ms()
+ */
+ if (!has_waker && !cpu_is_sibling(cpu, this_cpu()) &&
+ cpu_is_thread0(cpu)) {
+ has_waker = true;
+ master = this_cpu();
+ }
+ __cpu_queue_job(cpu, "slw_do_rvwinkle",
+ slw_do_rvwinkle, master, true);
+
+ /* Wait for it to claim to be down */
+ while(cpu->state != cpu_state_rvwinkle)
+ sync();
+ }
+
+ /* XXX Wait one second ! (should check xscom state ? ) */
+ prlog(PR_TRACE, "SLW: Waiting one second...\n");
+ time_wait_ms(1000);
+ prlog(PR_TRACE, "SLW: Done.\n");
+
+ for_each_chip(chip) {
+ struct cpu_thread *c;
+ uint64_t tmp;
+ for_each_available_core_in_chip(c, chip->id) {
+ xscom_read(chip->id,
+ XSCOM_ADDR_P8_EX_SLAVE(pir_to_core_id(c->pir),
+ EX_PM_IDLE_STATE_HISTORY_PHYP),
+ &tmp);
+ prlog(PR_DEBUG, "SLW: core %x:%x"
+ " history: 0x%016llx (mid)\n",
+ chip->id, pir_to_core_id(c->pir), tmp);
+ }
+ }
+
+
+ /* Wake everybody except on my core */
+ for_each_cpu(cpu) {
+ if (cpu->state != cpu_state_rvwinkle ||
+ cpu_is_sibling(cpu, this_cpu()))
+ continue;
+ icp_kick_cpu(cpu);
+
+ /* Wait for it to claim to be back (XXX ADD TIMEOUT) */
+ while(cpu->state != cpu_state_active)
+ sync();
+ }
+
+ /* Did we find a waker ? If we didn't, that means we had no
+ * other core in the system, we can't do it
+ */
+ if (!has_waker) {
+ prlog(PR_TRACE, "SLW: No candidate waker, giving up !\n");
+ return OPAL_HARDWARE;
+ }
+
+ /* Our siblings are rvwinkling, and our waker is waiting for us
+ * so let's just go down now
+ */
+ slw_do_rvwinkle(NULL);
+
+ slw_unpatch_reset();
+
+ for_each_chip(chip)
+ slw_cleanup_chip(chip);
+
+ prlog(PR_TRACE, "SLW Reinit complete !\n");
+
+ return OPAL_SUCCESS;
+}
+
+static void slw_patch_regs(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+ void *image = (void *)chip->slw_base;
+ int rc;
+
+ for_each_available_cpu(c) {
+ if (c->chip_id != chip->id)
+ continue;
+
+ /* Clear HRMOR */
+ rc = p8_pore_gen_cpureg_fixed(image, P8_SLW_MODEBUILD_SRAM,
+ P8_SPR_HRMOR, 0,
+ cpu_get_core_index(c),
+ cpu_get_thread_index(c));
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Failed to set HRMOR for CPU %x\n",
+ c->pir);
+ }
+
+ /* XXX Add HIDs etc... */
+ }
+}
+
+static void slw_init_chip_p9(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+
+ prlog(PR_DEBUG, "SLW: Init chip 0x%x\n", chip->id);
+
+ /* At power ON setup inits for power-mgt */
+ for_each_available_core_in_chip(c, chip->id)
+ slw_set_overrides_p9(chip, c);
+
+
+}
+
+static void slw_init_chip_p10(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+
+ prlog(PR_DEBUG, "SLW: Init chip 0x%x\n", chip->id);
+
+ /* At power ON setup inits for power-mgt */
+ for_each_available_core_in_chip(c, chip->id)
+ slw_set_overrides_p10(chip, c);
+
+
+}
+
+
+static bool slw_image_check_p9(struct proc_chip *chip)
+{
+
+ if (!chip->homer_base) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: HOMER base not set %x\n",
+ chip->id);
+ return false;
+ } else
+ return true;
+
+
+}
+
+static bool slw_image_check_p8(struct proc_chip *chip)
+{
+ int64_t rc;
+
+ prlog(PR_DEBUG, "SLW: slw_check chip 0x%x\n", chip->id);
+ if (!chip->slw_base) {
+ prerror("SLW: No image found !\n");
+ return false;
+ }
+
+ /* Check actual image size */
+ rc = sbe_xip_get_scalar((void *)chip->slw_base, "image_size",
+ &chip->slw_image_size);
+ if (rc != 0) {
+ log_simple_error(&e_info(OPAL_RC_SLW_INIT),
+ "SLW: Error %lld reading SLW image size\n", rc);
+ /* XXX Panic ? */
+ chip->slw_base = 0;
+ chip->slw_bar_size = 0;
+ chip->slw_image_size = 0;
+ return false;
+ }
+ prlog(PR_DEBUG, "SLW: Image size from image: 0x%llx\n",
+ chip->slw_image_size);
+
+ if (chip->slw_image_size > chip->slw_bar_size) {
+ log_simple_error(&e_info(OPAL_RC_SLW_INIT),
+ "SLW: Built-in image size larger than BAR size !\n");
+ /* XXX Panic ? */
+ return false;
+ }
+ return true;
+
+}
+
+static void slw_late_init_p8(struct proc_chip *chip)
+{
+
+ prlog(PR_DEBUG, "SLW: late Init chip 0x%x\n", chip->id);
+
+ /* Patch SLW image */
+ slw_patch_regs(chip);
+
+}
+static void slw_init_chip_p8(struct proc_chip *chip)
+{
+ struct cpu_thread *c;
+
+ prlog(PR_DEBUG, "SLW: Init chip 0x%x\n", chip->id);
+ /* At power ON setup inits for fast-sleep */
+ for_each_available_core_in_chip(c, chip->id) {
+ idle_prepare_core(chip, c);
+ }
+}
+
+/* Workarounds while entering fast-sleep */
+
+static void fast_sleep_enter(void)
+{
+ uint32_t core = pir_to_core_id(this_cpu()->pir);
+ uint32_t chip_id = this_cpu()->chip_id;
+ struct cpu_thread *primary_thread;
+ uint64_t tmp;
+ int rc;
+
+ primary_thread = this_cpu()->primary;
+
+ rc = xscom_read(chip_id, XSCOM_ADDR_P8_EX(core, L2_FIR_ACTION1),
+ &tmp);
+ if (rc) {
+ prlog(PR_WARNING, "fast_sleep_enter XSCOM failed(1):"
+ " rc=%d chip_id=%d core=%d\n",
+ rc, chip_id, core);
+ return;
+ }
+
+ primary_thread->save_l2_fir_action1 = tmp;
+ primary_thread->in_fast_sleep = true;
+
+ tmp = tmp & ~0x0200000000000000ULL;
+ rc = xscom_write(chip_id, XSCOM_ADDR_P8_EX(core, L2_FIR_ACTION1),
+ tmp);
+ if (rc) {
+ prlog(PR_WARNING, "fast_sleep_enter XSCOM failed(2):"
+ " rc=%d chip_id=%d core=%d\n",
+ rc, chip_id, core);
+ return;
+ }
+ rc = xscom_read(chip_id, XSCOM_ADDR_P8_EX(core, L2_FIR_ACTION1),
+ &tmp);
+ if (rc) {
+ prlog(PR_WARNING, "fast_sleep_enter XSCOM failed(3):"
+ " rc=%d chip_id=%d core=%d\n",
+ rc, chip_id, core);
+ return;
+ }
+
+}
+
+/* Workarounds while exiting fast-sleep */
+
+void fast_sleep_exit(void)
+{
+ uint32_t core = pir_to_core_id(this_cpu()->pir);
+ uint32_t chip_id = this_cpu()->chip_id;
+ struct cpu_thread *primary_thread;
+ int rc;
+
+ primary_thread = this_cpu()->primary;
+ primary_thread->in_fast_sleep = false;
+
+ rc = xscom_write(chip_id, XSCOM_ADDR_P8_EX(core, L2_FIR_ACTION1),
+ primary_thread->save_l2_fir_action1);
+ if (rc) {
+ prlog(PR_WARNING, "fast_sleep_exit XSCOM failed:"
+ " rc=%d chip_id=%d core=%d\n",
+ rc, chip_id, core);
+ return;
+ }
+}
+
+/*
+ * Setup and cleanup method for fast-sleep workarounds
+ * state = 1 fast-sleep
+ * enter = 1 Enter state
+ * exit = 0 Exit state
+ */
+
+static int64_t opal_config_cpu_idle_state(uint64_t state, uint64_t enter)
+{
+ /* Only fast-sleep for now */
+ if (state != 1)
+ return OPAL_PARAMETER;
+
+ switch(enter) {
+ case 1:
+ fast_sleep_enter();
+ break;
+ case 0:
+ fast_sleep_exit();
+ break;
+ default:
+ return OPAL_PARAMETER;
+ }
+
+ return OPAL_SUCCESS;
+}
+
+opal_call(OPAL_CONFIG_CPU_IDLE_STATE, opal_config_cpu_idle_state, 2);
+
+int64_t opal_slw_set_reg(uint64_t cpu_pir, uint64_t sprn, uint64_t val)
+{
+
+ struct cpu_thread *c = find_cpu_by_pir(cpu_pir);
+ struct proc_chip *chip;
+ int rc;
+
+ if (!c) {
+ prerror("SLW: Unknown thread with pir %x\n", (u32) cpu_pir);
+ return OPAL_PARAMETER;
+ }
+
+ chip = get_chip(c->chip_id);
+ if (!chip) {
+ prerror("SLW: Unknown chip for thread with pir %x\n",
+ (u32) cpu_pir);
+ return OPAL_PARAMETER;
+ }
+
+ if (proc_gen >= proc_gen_p9) {
+ if (!has_deep_states) {
+ prlog(PR_INFO, "SLW: Deep states not enabled\n");
+ return OPAL_SUCCESS;
+ }
+
+ if (wakeup_engine_state != WAKEUP_ENGINE_PRESENT) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: wakeup_engine in bad state=%d chip=%x\n",
+ wakeup_engine_state,chip->id);
+ return OPAL_INTERNAL_ERROR;
+ }
+ if (proc_gen == proc_gen_p9) {
+ rc = p9_stop_save_cpureg((void *)chip->homer_base,
+ sprn, val, cpu_pir);
+ } else {
+ rc = proc_stop_save_cpureg((void *)chip->homer_base,
+ sprn, val, cpu_pir);
+ }
+
+ } else if (proc_gen == proc_gen_p8) {
+ int spr_is_supported = 0;
+ void *image;
+ int i;
+
+ /* Check of the SPR is supported by libpore */
+ for (i = 0; i < SLW_SPR_REGS_SIZE ; i++) {
+ if (sprn == SLW_SPR_REGS[i].value) {
+ spr_is_supported = 1;
+ break;
+ }
+ }
+ if (!spr_is_supported) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Trying to set unsupported spr for CPU %x\n",
+ c->pir);
+ return OPAL_UNSUPPORTED;
+ }
+ image = (void *)chip->slw_base;
+ rc = p8_pore_gen_cpureg_fixed(image, P8_SLW_MODEBUILD_SRAM,
+ sprn, val,
+ cpu_get_core_index(c),
+ cpu_get_thread_index(c));
+ } else {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: proc_gen not supported\n");
+ return OPAL_UNSUPPORTED;
+
+ }
+
+ if (rc) {
+ log_simple_error(&e_info(OPAL_RC_SLW_REG),
+ "SLW: Failed to set spr %llx for CPU %x, RC=0x%x\n",
+ sprn, c->pir, rc);
+ return OPAL_INTERNAL_ERROR;
+ }
+ prlog(PR_DEBUG, "SLW: restore spr:0x%llx on c:0x%x with 0x%llx\n",
+ sprn, c->pir, val);
+ return OPAL_SUCCESS;
+
+}
+
+opal_call(OPAL_SLW_SET_REG, opal_slw_set_reg, 3);
+
+void slw_init(void)
+{
+ struct proc_chip *chip;
+
+ if (proc_chip_quirks & QUIRK_MAMBO_CALLOUTS) {
+ wakeup_engine_state = WAKEUP_ENGINE_NOT_PRESENT;
+ add_cpu_idle_state_properties();
+ return;
+ }
+ if (proc_gen == proc_gen_p8) {
+ for_each_chip(chip) {
+ slw_init_chip_p8(chip);
+ if(slw_image_check_p8(chip))
+ wakeup_engine_state = WAKEUP_ENGINE_PRESENT;
+ if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT)
+ slw_late_init_p8(chip);
+ }
+ p8_sbe_init_timer();
+ } else if (proc_gen == proc_gen_p9) {
+ for_each_chip(chip) {
+ slw_init_chip_p9(chip);
+ if(slw_image_check_p9(chip))
+ wakeup_engine_state = WAKEUP_ENGINE_PRESENT;
+ if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT)
+ slw_late_init_p9(chip);
+ }
+ } else if (proc_gen == proc_gen_p10) {
+ for_each_chip(chip) {
+ slw_init_chip_p10(chip);
+ if(slw_image_check_p9(chip))
+ wakeup_engine_state = WAKEUP_ENGINE_PRESENT;
+ if (wakeup_engine_state == WAKEUP_ENGINE_PRESENT) {
+ slw_late_init_p10(chip);
+ }
+ }
+ }
+ add_cpu_idle_state_properties();
+}