/* * Copyright (c) 2017-2019, STMicroelectronics - All Rights Reserved * * SPDX-License-Identifier: BSD-3-Clause */ #include <assert.h> #include <libfdt.h> #include <arch_helpers.h> #include <common/debug.h> #include <drivers/arm/gic_common.h> #include <drivers/arm/gicv2.h> #include <drivers/delay_timer.h> #include <drivers/st/stm32_iwdg.h> #include <drivers/st/stm32_rtc.h> #include <drivers/st/stm32mp_pmic.h> #include <drivers/st/stm32mp1_clk.h> #include <drivers/st/stm32mp1_ddr_helpers.h> #include <drivers/st/stm32mp1_pwr.h> #include <drivers/st/stm32mp1_rcc.h> #include <drivers/st/stpmic1.h> #include <dt-bindings/clock/stm32mp1-clks.h> #include <dt-bindings/power/stm32mp1-power.h> #include <lib/mmio.h> #include <plat/common/platform.h> #include <boot_api.h> #include <stm32mp_common.h> #include <stm32mp_dt.h> #include <stm32mp1_context.h> #include <stm32mp1_low_power.h> #include <stm32mp1_private.h> static unsigned int gicc_pmr; static struct stm32_rtc_calendar sleep_time, current_calendar; static unsigned long long stdby_time_in_ms; static bool enter_cstop_done; struct pwr_lp_config { uint32_t pwr_cr1; uint32_t pwr_mpucr; const char *regul_suspend_node_name; }; #define PWR_CR1_MASK (PWR_CR1_LPDS | PWR_CR1_LPCFG | PWR_CR1_LVDS) #define PWR_MPUCR_MASK (PWR_MPUCR_CSTDBYDIS | PWR_MPUCR_CSSF | PWR_MPUCR_PDDS) static const struct pwr_lp_config config_pwr[STM32_PM_MAX_SOC_MODE] = { [STM32_PM_CSLEEP_RUN] = { .pwr_cr1 = 0U, .pwr_mpucr = PWR_MPUCR_CSSF, .regul_suspend_node_name = NULL, }, [STM32_PM_CSTOP_ALLOW_STOP] = { .pwr_cr1 = 0U, .pwr_mpucr = PWR_MPUCR_CSTDBYDIS | PWR_MPUCR_CSSF, .regul_suspend_node_name = NULL, }, [STM32_PM_CSTOP_ALLOW_LP_STOP] = { .pwr_cr1 = PWR_CR1_LPDS, .pwr_mpucr = PWR_MPUCR_CSTDBYDIS | PWR_MPUCR_CSSF, .regul_suspend_node_name = "lp-stop", }, [STM32_PM_CSTOP_ALLOW_LPLV_STOP] = { .pwr_cr1 = PWR_CR1_LVDS | PWR_CR1_LPDS | PWR_CR1_LPCFG, .pwr_mpucr = PWR_MPUCR_CSTDBYDIS | PWR_MPUCR_CSSF, .regul_suspend_node_name = "lplv-stop", }, [STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR] = { .pwr_cr1 = 0U, .pwr_mpucr = PWR_MPUCR_CSTDBYDIS | PWR_MPUCR_CSSF | PWR_MPUCR_PDDS, .regul_suspend_node_name = "standby-ddr-sr", }, [STM32_PM_CSTOP_ALLOW_STANDBY_DDR_OFF] = { .pwr_cr1 = 0U, .pwr_mpucr = PWR_MPUCR_CSTDBYDIS | PWR_MPUCR_CSSF | PWR_MPUCR_PDDS, .regul_suspend_node_name = "standby-ddr-off", }, [STM32_PM_SHUTDOWN] = { .pwr_cr1 = 0U, .pwr_mpucr = 0U, .regul_suspend_node_name = "standby-ddr-off", }, }; #define GICC_PMR_PRIORITY_8 U(0x8) void stm32_apply_pmic_suspend_config(uint32_t mode) { const char *node_name = config_pwr[mode].regul_suspend_node_name; assert(mode < ARRAY_SIZE(config_pwr)); if (node_name != NULL) { if (!initialize_pmic_i2c()) { panic(); } if (dt_pmic_set_lp_config(node_name) != 0) { panic(); } if (dt_pmic_configure_boot_on_regulators() != 0) { panic(); } } } /* * stm32_enter_cstop - Prepare CSTOP mode * * @mode - Target low power mode * @nsec_addr - Non secure resume entry point * Return 0 if succeed to suspend, non 0 else. */ static void enter_cstop(uint32_t mode, uint32_t nsec_addr) { uint32_t zq0cr0_zdata; uint32_t bkpr_core1_addr = tamp_bkpr(BOOT_API_CORE1_BRANCH_ADDRESS_TAMP_BCK_REG_IDX); uint32_t bkpr_core1_magic = tamp_bkpr(BOOT_API_CORE1_MAGIC_NUMBER_TAMP_BCK_REG_IDX); uint32_t pwr_cr1 = config_pwr[mode].pwr_cr1; uintptr_t pwr_base = stm32mp_pwr_base(); uintptr_t rcc_base = stm32mp_rcc_base(); stm32mp1_syscfg_disable_io_compensation(); dcsw_op_all(DC_OP_CISW); stm32_clean_context(); if (mode == STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR) { /* * The first 64 bytes of DDR need to be saved for DDR DQS * training */ stm32_save_ddr_training_area(); } if (dt_pmic_status() > 0) { stm32_apply_pmic_suspend_config(mode); if (mode == STM32_PM_CSTOP_ALLOW_LP_STOP) { pwr_cr1 |= PWR_CR1_LPCFG; } } /* Clear RCC interrupt before enabling it */ mmio_setbits_32(rcc_base + RCC_MP_CIFR, RCC_MP_CIFR_WKUPF); /* Enable RCC Wake-up */ mmio_setbits_32(rcc_base + RCC_MP_CIER, RCC_MP_CIFR_WKUPF); /* Configure low power mode */ mmio_clrsetbits_32(pwr_base + PWR_MPUCR, PWR_MPUCR_MASK, config_pwr[mode].pwr_mpucr); mmio_clrsetbits_32(pwr_base + PWR_CR1, PWR_CR1_MASK, pwr_cr1); /* Clear RCC pending interrupt flags */ mmio_write_32(rcc_base + RCC_MP_CIFR, RCC_MP_CIFR_MASK); /* Request CSTOP mode to RCC */ mmio_setbits_32(rcc_base + RCC_MP_SREQSETR, RCC_MP_SREQSETR_STPREQ_P0 | RCC_MP_SREQSETR_STPREQ_P1); stm32_iwdg_refresh(); gicc_pmr = plat_ic_set_priority_mask(GICC_PMR_PRIORITY_8); /* * Set DDR in Self-refresh, even if no return address is given. * This is also the procedure awaited when switching off power supply. */ if (ddr_standby_sr_entry(&zq0cr0_zdata) != 0) { return; } stm32mp_clk_enable(RTCAPB); mmio_write_32(bkpr_core1_addr, 0); mmio_write_32(bkpr_core1_magic, 0); if (mode == STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR) { /* * Save non-secure world entrypoint after standby in Backup * register */ mmio_write_32(bkpr_core1_addr, nsec_addr); mmio_write_32(bkpr_core1_magic, BOOT_API_A7_CORE0_MAGIC_NUMBER); if (stm32_save_context(zq0cr0_zdata) != 0) { panic(); } /* Keep retention and backup RAM content in standby */ mmio_setbits_32(pwr_base + PWR_CR2, PWR_CR2_BREN | PWR_CR2_RREN); while ((mmio_read_32(pwr_base + PWR_CR2) & (PWR_CR2_BRRDY | PWR_CR2_RRRDY)) == 0U) { ; } } stm32mp_clk_disable(RTCAPB); stm32_rtc_get_calendar(&sleep_time); enter_cstop_done = true; } /* * stm32_exit_cstop - Exit from CSTOP mode */ void stm32_exit_cstop(void) { uintptr_t pwr_base = stm32mp_pwr_base(); uintptr_t rcc_base = stm32mp_rcc_base(); if (!enter_cstop_done) { return; } enter_cstop_done = false; if (ddr_sw_self_refresh_exit() != 0) { panic(); } plat_ic_set_priority_mask(gicc_pmr); /* Disable RCC Wake-up */ mmio_clrbits_32(rcc_base + RCC_MP_CIER, RCC_MP_CIFR_WKUPF); /* Disable STOP request */ mmio_setbits_32(rcc_base + RCC_MP_SREQCLRR, RCC_MP_SREQSETR_STPREQ_P0 | RCC_MP_SREQSETR_STPREQ_P1); dsb(); isb(); /* Disable retention and backup RAM content after stop */ mmio_clrbits_32(pwr_base + PWR_CR2, PWR_CR2_BREN | PWR_CR2_RREN); /* Update STGEN counter with low power mode duration */ stm32_rtc_get_calendar(¤t_calendar); stdby_time_in_ms = stm32_rtc_diff_calendar(¤t_calendar, &sleep_time); stm32mp1_stgen_increment(stdby_time_in_ms); stm32mp1_syscfg_enable_io_compensation(); } static void enter_shutdown(void) { /* Set DDR in Self-refresh before shutting down the platform */ if (ddr_standby_sr_entry(NULL) != 0) { WARN("DDR can't be set in Self-refresh mode\n"); } if (dt_pmic_status() > 0) { if (!initialize_pmic_i2c()) { panic(); } stpmic1_switch_off(); udelay(100); /* Shouldn't be reached */ panic(); } } static void enter_csleep(void) { uintptr_t pwr_base = stm32mp_pwr_base(); mmio_clrsetbits_32(pwr_base + PWR_MPUCR, PWR_MPUCR_MASK, config_pwr[STM32_PM_CSLEEP_RUN].pwr_mpucr); mmio_clrsetbits_32(pwr_base + PWR_CR1, PWR_CR1_MASK, config_pwr[STM32_PM_CSLEEP_RUN].pwr_cr1); stm32_pwr_down_wfi(); } void stm32_enter_low_power(uint32_t mode, uint32_t nsec_addr) { switch (mode) { case STM32_PM_SHUTDOWN: enter_shutdown(); break; case STM32_PM_CSLEEP_RUN: enter_csleep(); break; default: enter_cstop(mode, nsec_addr); break; } } void stm32_pwr_down_wfi(void) { uint32_t interrupt = GIC_SPURIOUS_INTERRUPT; while (interrupt == GIC_SPURIOUS_INTERRUPT) { wfi(); interrupt = gicv2_acknowledge_interrupt(); if (interrupt != GIC_SPURIOUS_INTERRUPT) { gicv2_end_of_interrupt(interrupt); } stm32_iwdg_refresh(); } }