/* * Copyright 2021-2024 NXP * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include extern void cgc1_save(void); extern void cgc1_restore(void); extern void imx_apd_ctx_save(unsigned int cpu); extern void imx_apd_ctx_restore(unsigned int cpu); extern void usb_wakeup_enable(bool enable); extern void upower_wait_resp(void); extern bool is_lpav_owned_by_apd(void); extern void apd_io_pad_off(void); extern int upower_pmic_i2c_read(uint32_t reg_addr, uint32_t *reg_val); extern void imx8ulp_init_scmi_server(void); static uintptr_t secure_entrypoint; #define CORE_PWR_STATE(state) ((state)->pwr_domain_state[MPIDR_AFFLVL0]) #define CLUSTER_PWR_STATE(state) ((state)->pwr_domain_state[MPIDR_AFFLVL1]) #define SYSTEM_PWR_STATE(state) ((state)->pwr_domain_state[PLAT_MAX_PWR_LVL]) #define RVBARADDRx(c) (IMX_SIM1_BASE + 0x5c + 0x4 * (c)) #define WKPUx(c) (IMX_SIM1_BASE + 0x3c + 0x4 * (c)) #define AD_COREx_LPMODE(c) (IMX_CMC1_BASE + 0x50 + 0x4 * (c)) #define PMIC_CFG(v, m, msk) \ { \ .volt = (v), \ .mode = (m), \ .mode_msk = (msk), \ } #define PAD_CFG(c, r, t) \ { \ .pad_close = (c), \ .pad_reset = (r), \ .pad_tqsleep = (t) \ } #define BIAS_CFG(m, n, p, mbias) \ { \ .dombias_cfg = { \ .mode = (m), \ .rbbn = (n), \ .rbbp = (p), \ }, \ .membias_cfg = {mbias}, \ } #define SWT_BOARD(swt_on, msk) \ { \ .on = (swt_on), \ .mask = (msk), \ } #define SWT_MEM(a, p, m) \ { \ .array = (a), \ .perif = (p), \ .mask = (m), \ } static int imx_pwr_set_cpu_entry(unsigned int cpu, unsigned int entry) { mmio_write_32(RVBARADDRx(cpu), entry); /* set update bit */ mmio_write_32(IMX_SIM1_BASE + 0x8, mmio_read_32(IMX_SIM1_BASE + 0x8) | BIT_32(24 + cpu)); /* wait for ack */ while (!(mmio_read_32(IMX_SIM1_BASE + 0x8) & BIT_32(26 + cpu))) { } /* clear update bit */ mmio_write_32(IMX_SIM1_BASE + 0x8, mmio_read_32(IMX_SIM1_BASE + 0x8) & ~BIT_32(24 + cpu)); /* clear ack bit */ mmio_write_32(IMX_SIM1_BASE + 0x8, mmio_read_32(IMX_SIM1_BASE + 0x8) | BIT_32(26 + cpu)); return 0; } static volatile uint32_t cgc1_nicclk; int imx_pwr_domain_on(u_register_t mpidr) { unsigned int cpu = MPIDR_AFFLVL0_VAL(mpidr); imx_pwr_set_cpu_entry(cpu, secure_entrypoint); /* slow down the APD NIC bus clock */ cgc1_nicclk = mmio_read_32(IMX_CGC1_BASE + 0x34); mmio_clrbits_32(IMX_CGC1_BASE + 0x34, GENMASK_32(29, 28)); mmio_write_32(IMX_CMC1_BASE + 0x18, 0x3f); mmio_write_32(IMX_CMC1_BASE + 0x50 + 0x4 * cpu, 0); /* enable wku wakeup for idle */ mmio_write_32(IMX_SIM1_BASE + 0x3c + 0x4 * cpu, 0xffffffff); return PSCI_E_SUCCESS; } void imx_pwr_domain_on_finish(const psci_power_state_t *target_state) { imx_pwr_set_cpu_entry(0, IMX_ROM_ENTRY); plat_gic_pcpu_init(); plat_gic_cpuif_enable(); /* set APD NIC back to orignally setting */ mmio_write_32(IMX_CGC1_BASE + 0x34, cgc1_nicclk); } int imx_validate_ns_entrypoint(uintptr_t ns_entrypoint) { return PSCI_E_SUCCESS; } void imx_pwr_domain_off(const psci_power_state_t *target_state) { unsigned int cpu = MPIDR_AFFLVL0_VAL(read_mpidr_el1()); plat_gic_cpuif_disable(); /* disable wakeup */ mmio_write_32(WKPUx(cpu), 0); /* set core power mode to PD */ mmio_write_32(AD_COREx_LPMODE(cpu), 0x3); } /* APD power mode config */ ps_apd_pwr_mode_cfgs_t apd_pwr_mode_cfgs = { [DPD_PWR_MODE] = { .swt_board_offs = 0x180, .swt_mem_offs = 0x188, .pmic_cfg = PMIC_CFG(0x23, 0x0, 0x2), .pad_cfg = PAD_CFG(0x0, 0xc, 0x01e80a02), .bias_cfg = BIAS_CFG(0x0, 0x2, 0x2, 0x0), }, /* PD */ [PD_PWR_MODE] = { .swt_board_offs = 0x170, .swt_mem_offs = 0x178, .pmic_cfg = PMIC_CFG(0x23, 0x0, 0x2), .pad_cfg = PAD_CFG(0x0, 0xc, 0x01e80a00), .bias_cfg = BIAS_CFG(0x0, 0x2, 0x2, 0x0), }, [ADMA_PWR_MODE] = { .swt_board_offs = 0x120, .swt_mem_offs = 0x128, .pmic_cfg = PMIC_CFG(0x23, 0x0, 0x2), .pad_cfg = PAD_CFG(0x0, 0x0, 0x0deb7a00), .bias_cfg = BIAS_CFG(0x2, 0x2, 0x2, 0x0), }, [ACT_PWR_MODE] = { .swt_board_offs = 0x110, .swt_mem_offs = 0x118, .pmic_cfg = PMIC_CFG(0x23, 0x0, 0x2), .pad_cfg = PAD_CFG(0x0, 0x0, 0x0deb7a00), .bias_cfg = BIAS_CFG(0x2, 0x2, 0x2, 0x0), }, }; /* APD power switch config */ ps_apd_swt_cfgs_t apd_swt_cfgs = { [DPD_PWR_MODE] = { .swt_board[0] = SWT_BOARD(0x0, 0x1fffc), .swt_mem[0] = SWT_MEM(0x0, 0x0, 0x1ffff), .swt_mem[1] = SWT_MEM(0x003fffff, 0x003fffff, 0x0), }, [PD_PWR_MODE] = { .swt_board[0] = SWT_BOARD(0x0, 0x00001fffc), .swt_mem[0] = SWT_MEM(0x00010c00, 0x0, 0x1ffff), .swt_mem[1] = SWT_MEM(0x003fffff, 0x003f0000, 0x0), }, [ADMA_PWR_MODE] = { .swt_board[0] = SWT_BOARD(0x15f74, 0x15f74), .swt_mem[0] = SWT_MEM(0x0001fffd, 0x0001fffd, 0x1ffff), .swt_mem[1] = SWT_MEM(0x003fffff, 0x003fffff, 0x0), }, [ACT_PWR_MODE] = { .swt_board[0] = SWT_BOARD(0x15f74, 0x15f74), .swt_mem[0] = SWT_MEM(0x0001fffd, 0x0001fffd, 0x1ffff), .swt_mem[1] = SWT_MEM(0x003fffff, 0x003fffff, 0x0), }, }; /* PMIC config for power down, LDO1 should be OFF */ ps_apd_pmic_reg_data_cfgs_t pd_pmic_reg_cfgs = { [0] = { .tag = PMIC_REG_VALID_TAG, .power_mode = PD_PWR_MODE, .i2c_addr = 0x30, .i2c_data = 0x9c, }, [1] = { .tag = PMIC_REG_VALID_TAG, .power_mode = PD_PWR_MODE, .i2c_addr = 0x22, .i2c_data = 0xb, }, [2] = { .tag = PMIC_REG_VALID_TAG, .power_mode = ACT_PWR_MODE, .i2c_addr = 0x30, .i2c_data = 0x9d, }, [3] = { .tag = PMIC_REG_VALID_TAG, .power_mode = ACT_PWR_MODE, .i2c_addr = 0x22, .i2c_data = 0x28, }, }; /* PMIC config for deep power down, BUCK3 should be OFF */ ps_apd_pmic_reg_data_cfgs_t dpd_pmic_reg_cfgs = { [0] = { .tag = PMIC_REG_VALID_TAG, .power_mode = DPD_PWR_MODE, .i2c_addr = 0x21, .i2c_data = 0x78, }, [1] = { .tag = PMIC_REG_VALID_TAG, .power_mode = DPD_PWR_MODE, .i2c_addr = 0x30, .i2c_data = 0x9c, }, [2] = { .tag = PMIC_REG_VALID_TAG, .power_mode = ACT_PWR_MODE, .i2c_addr = 0x21, .i2c_data = 0x79, }, [3] = { .tag = PMIC_REG_VALID_TAG, .power_mode = ACT_PWR_MODE, .i2c_addr = 0x30, .i2c_data = 0x9d, }, }; struct ps_pwr_mode_cfg_t *pwr_sys_cfg = (struct ps_pwr_mode_cfg_t *)UPWR_DRAM_SHARED_BASE_ADDR; void imx_set_pwr_mode_cfg(abs_pwr_mode_t mode) { uint32_t volt; if (mode >= NUM_PWR_MODES) { return; } /* apd power mode config */ memcpy(&pwr_sys_cfg->ps_apd_pwr_mode_cfg[mode], &apd_pwr_mode_cfgs[mode], sizeof(struct ps_apd_pwr_mode_cfg_t)); /* apd power switch config */ memcpy(&pwr_sys_cfg->ps_apd_swt_cfg[mode], &apd_swt_cfgs[mode], sizeof(swt_config_t)); /* * BUCK3 & LDO1 can only be shutdown when LPAV is owned by APD side * otherwise RTD side is responsible to control them in low power mode. */ if (is_lpav_owned_by_apd()) { /* power off the BUCK3 in DPD mode */ if (mode == DPD_PWR_MODE) { memcpy(&pwr_sys_cfg->ps_apd_pmic_reg_data_cfg, &dpd_pmic_reg_cfgs, sizeof(ps_apd_pmic_reg_data_cfgs_t)); /* LDO1 should be power off in PD mode */ } else if (mode == PD_PWR_MODE) { /* overwrite the buck3 voltage setting in active mode */ upower_pmic_i2c_read(0x22, &volt); pd_pmic_reg_cfgs[3].i2c_data = volt; memcpy(&pwr_sys_cfg->ps_apd_pmic_reg_data_cfg, &pd_pmic_reg_cfgs, sizeof(ps_apd_pmic_reg_data_cfgs_t)); } } } void imx_domain_suspend(const psci_power_state_t *target_state) { unsigned int cpu = MPIDR_AFFLVL0_VAL(read_mpidr_el1()); if (is_local_state_off(CORE_PWR_STATE(target_state))) { plat_gic_cpuif_disable(); imx_pwr_set_cpu_entry(cpu, secure_entrypoint); /* core put into power down */ mmio_write_32(IMX_CMC1_BASE + 0x50 + 0x4 * cpu, 0x3); /* FIXME config wakeup interrupt in WKPU */ mmio_write_32(IMX_SIM1_BASE + 0x3c + 0x4 * cpu, 0x7fffffe3); } else { /* for core standby/retention mode */ mmio_write_32(IMX_CMC1_BASE + 0x50 + 0x4 * cpu, 0x1); mmio_write_32(IMX_SIM1_BASE + 0x3c + 0x4 * cpu, 0x7fffffe3); dsb(); write_scr_el3(read_scr_el3() | SCR_FIQ_BIT); isb(); } if (is_local_state_retn(CLUSTER_PWR_STATE(target_state))) { /* * just for sleep mode for now, need to update to * support more modes, same for suspend finish call back. */ mmio_write_32(IMX_CMC1_BASE + 0x10, 0x1); mmio_write_32(IMX_CMC1_BASE + 0x20, 0x1); } else if (is_local_state_off(CLUSTER_PWR_STATE(target_state))) { /* * for cluster off state, put cluster into power down mode, * config the cluster clock to be off. */ mmio_write_32(IMX_CMC1_BASE + 0x10, 0x7); mmio_write_32(IMX_CMC1_BASE + 0x20, 0xf); } if (is_local_state_off(SYSTEM_PWR_STATE(target_state))) { /* * low power mode config info used by upower * to do low power mode transition. */ imx_set_pwr_mode_cfg(ADMA_PWR_MODE); imx_set_pwr_mode_cfg(ACT_PWR_MODE); imx_set_pwr_mode_cfg(PD_PWR_MODE); /* clear the upower wakeup */ upwr_xcp_set_rtd_apd_llwu(APD_DOMAIN, 0, NULL); upower_wait_resp(); /* enable the USB wakeup */ usb_wakeup_enable(true); /* config the WUU to enabled the wakeup source */ mmio_write_32(IMX_PCC3_BASE + 0x98, 0xc0800000); /* !!! clear all the pad wakeup pending event */ mmio_write_32(IMX_WUU1_BASE + 0x20, 0xffffffff); /* enable upower usb phy wakeup by default */ mmio_setbits_32(IMX_WUU1_BASE + 0x18, BIT(4) | BIT(1) | BIT(0)); /* enabled all pad wakeup by default */ mmio_write_32(IMX_WUU1_BASE + 0x8, 0xffffffff); /* save the AD domain context before entering PD mode */ imx_apd_ctx_save(cpu); } } #define DRAM_LPM_STATUS U(0x2802b004) void imx_domain_suspend_finish(const psci_power_state_t *target_state) { unsigned int cpu = MPIDR_AFFLVL0_VAL(read_mpidr_el1()); if (is_local_state_off(SYSTEM_PWR_STATE(target_state))) { /* restore the ap domain context */ imx_apd_ctx_restore(cpu); /* clear the upower wakeup */ upwr_xcp_set_rtd_apd_llwu(APD_DOMAIN, 0, NULL); upower_wait_resp(); /* disable all pad wakeup */ mmio_write_32(IMX_WUU1_BASE + 0x8, 0x0); /* clear all the pad wakeup pending event */ mmio_write_32(IMX_WUU1_BASE + 0x20, 0xffffffff); /* * disable the usb wakeup after resume to make sure the pending * usb wakeup in WUU can be cleared successfully, otherwise, * APD will resume failed in next PD mode. */ usb_wakeup_enable(false); /* re-init the SCMI channel */ imx8ulp_init_scmi_server(); } /* * wait for DDR is ready when DDR is under the RTD * side control for power saving */ while (mmio_read_32(DRAM_LPM_STATUS) != 0) { ; } /* * when resume from low power mode, need to delay for a while * before access the CMC register. */ udelay(5); /* clear cluster's LPM setting. */ mmio_write_32(IMX_CMC1_BASE + 0x20, 0x0); mmio_write_32(IMX_CMC1_BASE + 0x10, 0x0); /* clear core's LPM setting */ mmio_write_32(IMX_CMC1_BASE + 0x50 + 0x4 * cpu, 0x0); mmio_write_32(IMX_SIM1_BASE + 0x3c + 0x4 * cpu, 0x0); if (is_local_state_off(CORE_PWR_STATE(target_state))) { imx_pwr_set_cpu_entry(0, IMX_ROM_ENTRY); plat_gic_cpuif_enable(); } else { dsb(); write_scr_el3(read_scr_el3() & (~SCR_FIQ_BIT)); isb(); } } void __dead2 imx8ulp_pwr_domain_pwr_down_wfi(const psci_power_state_t *target_state) { while (1) { wfi(); } } void __dead2 imx8ulp_system_reset(void) { imx_pwr_set_cpu_entry(0, IMX_ROM_ENTRY); /* Write invalid command to WDOG CNT to trigger reset */ mmio_write_32(IMX_WDOG3_BASE + 0x4, 0x12345678); while (true) { wfi(); } } int imx_validate_power_state(unsigned int power_state, psci_power_state_t *req_state) { int pwr_lvl = psci_get_pstate_pwrlvl(power_state); int pwr_type = psci_get_pstate_type(power_state); if (pwr_lvl > PLAT_MAX_PWR_LVL) { return PSCI_E_INVALID_PARAMS; } if (pwr_type == PSTATE_TYPE_STANDBY) { CORE_PWR_STATE(req_state) = PLAT_MAX_RET_STATE; CLUSTER_PWR_STATE(req_state) = PLAT_MAX_RET_STATE; } /* No power down state support */ if (pwr_type == PSTATE_TYPE_POWERDOWN) { return PSCI_E_INVALID_PARAMS; } return PSCI_E_SUCCESS; } void imx_get_sys_suspend_power_state(psci_power_state_t *req_state) { unsigned int i; for (i = IMX_PWR_LVL0; i <= PLAT_MAX_PWR_LVL; i++) { req_state->pwr_domain_state[i] = PLAT_POWER_DOWN_OFF_STATE; } } void __dead2 imx_system_off(void) { unsigned int i; /* config the all the core into OFF mode and IRQ masked. */ for (i = 0U; i < PLATFORM_CORE_COUNT; i++) { /* disable wakeup from wkpu */ mmio_write_32(WKPUx(i), 0x0); /* reset the core reset entry to 0x1000 */ imx_pwr_set_cpu_entry(i, 0x1000); /* config the core power mode to off */ mmio_write_32(AD_COREx_LPMODE(i), 0x3); } plat_gic_cpuif_disable(); /* power off all the pad */ apd_io_pad_off(); /* Config the power mode info for entering DPD mode and ACT mode */ imx_set_pwr_mode_cfg(ADMA_PWR_MODE); imx_set_pwr_mode_cfg(ACT_PWR_MODE); imx_set_pwr_mode_cfg(DPD_PWR_MODE); /* Set the APD domain into DPD mode */ mmio_write_32(IMX_CMC1_BASE + 0x10, 0x7); mmio_write_32(IMX_CMC1_BASE + 0x20, 0x1f); /* make sure no pending upower wakeup */ upwr_xcp_set_rtd_apd_llwu(APD_DOMAIN, 0, NULL); upower_wait_resp(); /* enable the upower wakeup from wuu, act as APD boot up method */ mmio_write_32(IMX_PCC3_BASE + 0x98, 0xc0800000); mmio_setbits_32(IMX_WUU1_BASE + 0x18, BIT(4)); /* make sure no pad wakeup event is pending */ mmio_write_32(IMX_WUU1_BASE + 0x20, 0xffffffff); wfi(); ERROR("power off failed.\n"); panic(); } static const plat_psci_ops_t imx_plat_psci_ops = { .pwr_domain_on = imx_pwr_domain_on, .pwr_domain_on_finish = imx_pwr_domain_on_finish, .validate_ns_entrypoint = imx_validate_ns_entrypoint, .system_off = imx_system_off, .system_reset = imx8ulp_system_reset, .pwr_domain_off = imx_pwr_domain_off, .pwr_domain_suspend = imx_domain_suspend, .pwr_domain_suspend_finish = imx_domain_suspend_finish, .get_sys_suspend_power_state = imx_get_sys_suspend_power_state, .validate_power_state = imx_validate_power_state, .pwr_domain_pwr_down_wfi = imx8ulp_pwr_domain_pwr_down_wfi, }; int plat_setup_psci_ops(uintptr_t sec_entrypoint, const plat_psci_ops_t **psci_ops) { secure_entrypoint = sec_entrypoint; imx_pwr_set_cpu_entry(0, sec_entrypoint); *psci_ops = &imx_plat_psci_ops; mmio_write_32(IMX_CMC1_BASE + 0x18, 0x3f); mmio_write_32(IMX_SIM1_BASE + 0x3c, 0xffffffff); return 0; }