diff --git a/drivers/st/ddr/stm32mp1_ram.c b/drivers/st/ddr/stm32mp1_ram.c index 40cd455..0f7c7d7 100644 --- a/drivers/st/ddr/stm32mp1_ram.c +++ b/drivers/st/ddr/stm32mp1_ram.c @@ -50,6 +50,26 @@ } /******************************************************************************* + * This function tests a simple read/write access to the DDR. + * Note that the previous content is restored after test. + * Returns 0 if success, and address value else. + ******************************************************************************/ +static uint32_t ddr_test_rw_access(void) +{ + uint32_t saved_value = mmio_read_32(STM32MP_DDR_BASE); + + mmio_write_32(STM32MP_DDR_BASE, DDR_PATTERN); + + if (mmio_read_32(STM32MP_DDR_BASE) != DDR_PATTERN) { + return (uint32_t)STM32MP_DDR_BASE; + } + + mmio_write_32(STM32MP_DDR_BASE, saved_value); + + return 0; +} + +/******************************************************************************* * This function tests the DDR data bus wiring. * This is inspired from the Data Bus Test algorithm written by Michael Barr * in "Programming Embedded Systems in C and C++" book. @@ -168,8 +188,12 @@ int ret; struct stm32mp1_ddr_config config; int node, len; - uint32_t uret, idx; + uint32_t magic, uret, idx; void *fdt; + 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); #define PARAM(x, y) \ { \ @@ -237,6 +261,18 @@ } } + config.self_refresh = false; + + stm32mp_clk_enable(RTCAPB); + + magic = mmio_read_32(bkpr_core1_magic); + if (magic == BOOT_API_A7_CORE0_MAGIC_NUMBER) { + config.self_refresh = true; + config.zdata = stm32_get_zdata_from_context(); + } + + stm32mp_clk_disable(RTCAPB); + /* Disable axidcg clock gating during init */ mmio_clrbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_AXIDCGEN); @@ -245,6 +281,14 @@ /* Enable axidcg clock gating */ mmio_setbits_32(priv->rcc + RCC_DDRITFCR, RCC_DDRITFCR_AXIDCGEN); + /* check if DDR content is lost (self-refresh aborted) */ + if ((magic == BOOT_API_A7_CORE0_MAGIC_NUMBER) && !config.self_refresh) { + /* clear Backup register */ + mmio_write_32(bkpr_core1_addr, 0); + /* clear magic number */ + mmio_write_32(bkpr_core1_magic, 0); + } + priv->info.size = config.info.size; VERBOSE("%s : ram size(%x, %x)\n", __func__, @@ -254,25 +298,37 @@ panic(); } - uret = ddr_test_data_bus(); - if (uret != 0U) { - ERROR("DDR data bus test: can't access memory @ 0x%x\n", - uret); - panic(); - } + if (config.self_refresh) { + uret = ddr_test_rw_access(); + if (uret != 0U) { + ERROR("DDR rw test: Can't access memory @ 0x%x\n", + uret); + panic(); + } - uret = ddr_test_addr_bus(); - if (uret != 0U) { - ERROR("DDR addr bus test: can't access memory @ 0x%x\n", - uret); - panic(); - } + /* Restore area overwritten by training */ + stm32_restore_ddr_training_area(); + } else { + uret = ddr_test_data_bus(); + if (uret != 0U) { + ERROR("DDR data bus test: can't access memory @ 0x%x\n", + uret); + panic(); + } - uret = ddr_check_size(); - if (uret < config.info.size) { - ERROR("DDR size: 0x%x does not match DT config: 0x%x\n", - uret, config.info.size); - panic(); + uret = ddr_test_addr_bus(); + if (uret != 0U) { + ERROR("DDR addr bus test: can't access memory @ 0x%x\n", + uret); + panic(); + } + + uret = ddr_check_size(); + if (uret < config.info.size) { + ERROR("DDR size: 0x%x does not match DT config: 0x%x\n", + uret, config.info.size); + panic(); + } } if (stm32mp_unmap_ddr() != 0) { diff --git a/drivers/st/pmic/stm32mp_pmic.c b/drivers/st/pmic/stm32mp_pmic.c index 9e9dddc..dc44b73 100644 --- a/drivers/st/pmic/stm32mp_pmic.c +++ b/drivers/st/pmic/stm32mp_pmic.c @@ -27,6 +27,8 @@ #define STPMIC1_BUCK_OUTPUT_SHIFT 2 #define STPMIC1_BUCK3_1V8 (39U << STPMIC1_BUCK_OUTPUT_SHIFT) +#define REGULATOR_MODE_STANDBY 8U + #define STPMIC1_DEFAULT_START_UP_DELAY_MS 1 static struct i2c_handle_s i2c_handle; @@ -174,6 +176,99 @@ return 0; } +int dt_pmic_set_lp_config(const char *node_name) +{ + int pmic_node, regulators_node, regulator_node; + int status; + void *fdt; + + if (node_name == NULL) { + return 0; + } + + if (fdt_get_address(&fdt) == 0) { + return -ENOENT; + } + + pmic_node = dt_get_pmic_node(fdt); + if (pmic_node < 0) { + return -FDT_ERR_NOTFOUND; + } + + status = stpmic1_powerctrl_on(); + if (status != 0) { + return status; + }; + + regulators_node = fdt_subnode_offset(fdt, pmic_node, "regulators"); + + fdt_for_each_subnode(regulator_node, fdt, regulators_node) { + const fdt32_t *cuint; + const char *reg_name; + int regulator_state_node; + + /* + * First, copy active configuration (Control register) to + * PWRCTRL Control register, even if regulator_state_node + * does not exist. + */ + reg_name = fdt_get_name(fdt, regulator_node, NULL); + status = stpmic1_lp_copy_reg(reg_name); + if (status != 0) { + return status; + } + + /* Then apply configs from regulator_state_node */ + regulator_state_node = fdt_subnode_offset(fdt, + regulator_node, + node_name); + if (regulator_state_node <= 0) { + continue; + } + + if (fdt_getprop(fdt, regulator_state_node, + "regulator-on-in-suspend", NULL) != NULL) { + status = stpmic1_lp_reg_on_off(reg_name, 1); + if (status != 0) { + return status; + } + } + + if (fdt_getprop(fdt, regulator_state_node, + "regulator-off-in-suspend", NULL) != NULL) { + status = stpmic1_lp_reg_on_off(reg_name, 0); + if (status != 0) { + return status; + } + } + + cuint = fdt_getprop(fdt, regulator_state_node, + "regulator-suspend-microvolt", NULL); + if (cuint != NULL) { + uint16_t voltage = (uint16_t)(fdt32_to_cpu(*cuint) / + 1000U); + + status = stpmic1_lp_set_voltage(reg_name, voltage); + if (status != 0) { + return status; + } + } + + cuint = fdt_getprop(fdt, regulator_state_node, + "regulator-mode", NULL); + if (cuint != NULL) { + if (fdt32_to_cpu(*cuint) == REGULATOR_MODE_STANDBY) { + status = stpmic1_lp_set_mode(reg_name, 1); + if (status != 0) { + return status; + } + } + } + } + + return 0; +} + bool initialize_pmic_i2c(void) { int ret; diff --git a/drivers/st/pmic/stpmic1.c b/drivers/st/pmic/stpmic1.c index 9999630..51754fc 100644 --- a/drivers/st/pmic/stpmic1.c +++ b/drivers/st/pmic/stpmic1.c @@ -648,6 +648,58 @@ regul->mask_reset); } +/* Low-power functions */ +int stpmic1_lp_copy_reg(const char *name) +{ + uint8_t val; + int status; + const struct regul_struct *regul = get_regulator_data(name); + + status = stpmic1_register_read(regul->control_reg, &val); + if (status != 0) { + return status; + } + + return stpmic1_register_write(regul->low_power_reg, val); +} + +int stpmic1_lp_reg_on_off(const char *name, uint8_t enable) +{ + const struct regul_struct *regul = get_regulator_data(name); + + return stpmic1_register_update(regul->low_power_reg, enable, + LDO_BUCK_ENABLE_MASK); +} + +int stpmic1_lp_set_mode(const char *name, uint8_t hplp) +{ + const struct regul_struct *regul = get_regulator_data(name); + + return stpmic1_register_update(regul->low_power_reg, + hplp << LDO_BUCK_HPLP_SHIFT, + LDO_BUCK_HPLP_ENABLE_MASK); +} + +int stpmic1_lp_set_voltage(const char *name, uint16_t millivolts) +{ + uint8_t voltage_index = voltage_to_index(name, millivolts); + const struct regul_struct *regul = get_regulator_data(name); + uint8_t mask; + + /* Voltage can be set for buck or ldo (except ldo4) regulators */ + if (strncmp(name, "buck", 4) == 0) { + mask = BUCK_VOLTAGE_MASK; + } else if ((strncmp(name, "ldo", 3) == 0) && + (strncmp(name, "ldo4", 4) != 0)) { + mask = LDO_VOLTAGE_MASK; + } else { + return 0; + } + + return stpmic1_register_update(regul->low_power_reg, voltage_index << 2, + mask); +} + int stpmic1_regulator_voltage_get(const char *name) { const struct regul_struct *regul = get_regulator_data(name); diff --git a/fdts/stm32mp157a-dk1.dts b/fdts/stm32mp157a-dk1.dts index 4ea83f7..23e1886 100644 --- a/fdts/stm32mp157a-dk1.dts +++ b/fdts/stm32mp157a-dk1.dts @@ -192,6 +192,7 @@ /* ATF Specific */ #include +#include #include "stm32mp15-ddr3-1x4Gb-1066-binG.dtsi" #include "stm32mp157c-security.dtsi" @@ -309,3 +310,138 @@ secure-status = "okay"; }; }; + +&pwr { + system_suspend_supported_soc_modes = < + STM32_PM_CSLEEP_RUN + STM32_PM_CSTOP_ALLOW_LP_STOP + STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR + >; + + system_off_soc_mode = ; +}; + +/* Low-power states of regulators */ +&vddcore { + lp-stop { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1200000>; + }; + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd_ddr { + lp-stop { + regulator-suspend-microvolt = <1350000>; + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-suspend-microvolt = <1350000>; + regulator-on-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd { + lp-stop { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; + standby-ddr-off { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; +}; + +&v3v3 { + lp-stop { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&v1v8_audio { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&v3v3_hdmi { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vtt_ddr { + lp-stop { + regulator-off-in-suspend; + }; + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd_usb { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdda { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&v1v2_hdmi { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vref_ddr { + lp-stop { + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-on-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; diff --git a/fdts/stm32mp157c-ed1.dts b/fdts/stm32mp157c-ed1.dts index 7794925..d766cc6 100644 --- a/fdts/stm32mp157c-ed1.dts +++ b/fdts/stm32mp157c-ed1.dts @@ -196,6 +196,7 @@ /* ATF Specific */ #include +#include #include "stm32mp15-ddr3-2x4Gb-1066-binG.dtsi" #include "stm32mp157c-security.dtsi" @@ -315,3 +316,152 @@ secure-status = "okay"; }; }; + +&pwr { + system_suspend_supported_soc_modes = < + STM32_PM_CSLEEP_RUN + STM32_PM_CSTOP_ALLOW_LP_STOP + STM32_PM_CSTOP_ALLOW_LPLV_STOP + STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR + >; + system_off_soc_mode = ; +}; + +/* Low-power states of regulators */ +&vddcore { + lp-stop { + regulator-on-in-suspend; + regulator-suspend-microvolt = <1200000>; + }; + lplv-stop { + regulator-on-in-suspend; + regulator-suspend-microvolt = <900000>; + }; + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd_ddr { + lp-stop { + regulator-suspend-microvolt = <1350000>; + regulator-on-in-suspend; + }; + lplv-stop { + regulator-suspend-microvolt = <1350000>; + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-suspend-microvolt = <1350000>; + regulator-on-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd { + lp-stop { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; + lplv-stop { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; + standby-ddr-off { + regulator-suspend-microvolt = <3300000>; + regulator-on-in-suspend; + }; +}; + +&v3v3 { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdda { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&v2v8 { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vtt_ddr { + lp-stop { + regulator-off-in-suspend; + }; + lplv-stop { + regulator-off-in-suspend; + }; + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd_usb { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vdd_sd { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&v1v8 { + standby-ddr-sr { + regulator-off-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; + +&vref_ddr { + lp-stop { + regulator-on-in-suspend; + }; + lplv-stop { + regulator-on-in-suspend; + }; + standby-ddr-sr { + regulator-on-in-suspend; + }; + standby-ddr-off { + regulator-off-in-suspend; + }; +}; diff --git a/include/drivers/st/stm32mp1_pwr.h b/include/drivers/st/stm32mp1_pwr.h index e17df44..9b662f2 100644 --- a/include/drivers/st/stm32mp1_pwr.h +++ b/include/drivers/st/stm32mp1_pwr.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018, STMicroelectronics - All Rights Reserved + * Copyright (c) 2017-2019, STMicroelectronics - All Rights Reserved * * SPDX-License-Identifier: BSD-3-Clause */ @@ -13,20 +13,39 @@ #define PWR_CR2 U(0x08) #define PWR_CR3 U(0x0C) #define PWR_MPUCR U(0x10) +#define PWR_MCUCR U(0x14) #define PWR_WKUPCR U(0x20) #define PWR_MPUWKUPENR U(0x28) +#define PWR_OFFSET_MASK GENMASK(9, 0) + #define PWR_CR1_LPDS BIT(0) #define PWR_CR1_LPCFG BIT(1) #define PWR_CR1_LVDS BIT(2) #define PWR_CR1_DBP BIT(8) +#define PWR_CR2_BREN BIT(0) +#define PWR_CR2_RREN BIT(1) +#define PWR_CR2_BRRDY BIT(16) +#define PWR_CR2_RRRDY BIT(17) + +#define PWR_CR3_VBE BIT(8) +#define PWR_CR3_VBRS BIT(9) #define PWR_CR3_DDRSREN BIT(10) #define PWR_CR3_DDRSRDIS BIT(11) #define PWR_CR3_DDRRETEN BIT(12) +#define PWR_CR3_USB33DEN BIT(24) +#define PWR_CR3_REG18EN BIT(28) +#define PWR_CR3_REG11EN BIT(30) #define PWR_MPUCR_PDDS BIT(0) #define PWR_MPUCR_CSTDBYDIS BIT(3) #define PWR_MPUCR_CSSF BIT(9) +#define PWR_MCUCR_PDDS BIT(0) + +#define PWR_WKUPCR_MASK GENMASK(27, 16) | GENMASK(13, 8) | GENMASK(5, 0) + +#define PWR_MPUWKUPENR_MASK GENMASK(5, 0) + #endif /* STM32MP1_PWR_H */ diff --git a/include/drivers/st/stm32mp_pmic.h b/include/drivers/st/stm32mp_pmic.h index 984cd60..f5810f3 100644 --- a/include/drivers/st/stm32mp_pmic.h +++ b/include/drivers/st/stm32mp_pmic.h @@ -27,6 +27,8 @@ */ int dt_pmic_configure_boot_on_regulators(void); +int dt_pmic_set_lp_config(const char *node_name); + /* * initialize_pmic_i2c - Initialize I2C for the PMIC control * diff --git a/include/drivers/st/stpmic1.h b/include/drivers/st/stpmic1.h index f7e293b..e4ceea5 100644 --- a/include/drivers/st/stpmic1.h +++ b/include/drivers/st/stpmic1.h @@ -161,6 +161,10 @@ int stpmic1_regulator_voltage_get(const char *name); int stpmic1_regulator_pull_down_set(const char *name); int stpmic1_regulator_mask_reset_set(const char *name); +int stpmic1_lp_copy_reg(const char *name); +int stpmic1_lp_reg_on_off(const char *name, uint8_t enable); +int stpmic1_lp_set_mode(const char *name, uint8_t hplp); +int stpmic1_lp_set_voltage(const char *name, uint16_t millivolts); void stpmic1_bind_i2c(struct i2c_handle_s *i2c_handle, uint16_t i2c_addr); int stpmic1_get_version(unsigned long *version); diff --git a/include/dt-bindings/power/stm32mp1-power.h b/include/dt-bindings/power/stm32mp1-power.h new file mode 100644 index 0000000..d588dd7 --- /dev/null +++ b/include/dt-bindings/power/stm32mp1-power.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 or BSD-3-Clause */ +/* + * Copyright (C) 2018-2019, STMicroelectronics - All Rights Reserved + * Author: Yann Gautier for STMicroelectronics. + */ + +#ifndef DT_BINDINGS_STM32MP1_POWER_H +#define DT_BINDINGS_STM32MP1_POWER_H + +#define STM32_PM_CSLEEP_RUN 0 +#define STM32_PM_CSTOP_ALLOW_STOP 1 +#define STM32_PM_CSTOP_ALLOW_LP_STOP 2 +#define STM32_PM_CSTOP_ALLOW_LPLV_STOP 3 +#define STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR 4 +#define STM32_PM_CSTOP_ALLOW_STANDBY_DDR_OFF 5 +#define STM32_PM_SHUTDOWN 6 +#define STM32_PM_MAX_SOC_MODE 7 + +#endif /* DT_BINDINGS_STM32MP1_POWER_H */ diff --git a/plat/st/stm32mp1/bl2_plat_setup.c b/plat/st/stm32mp1/bl2_plat_setup.c index 652765c..a7966bc 100644 --- a/plat/st/stm32mp1/bl2_plat_setup.c +++ b/plat/st/stm32mp1/bl2_plat_setup.c @@ -31,6 +31,8 @@ #include #include +#define PWRLP_TEMPO_5_HSI 5 + static console_t console; static struct stm32mp_auth_ops stm32mp1_auth_ops; @@ -177,6 +179,10 @@ uint32_t clk_rate; uintptr_t pwr_base; uintptr_t rcc_base; + uint32_t bkpr_core1_magic = + tamp_bkpr(BOOT_API_CORE1_MAGIC_NUMBER_TAMP_BCK_REG_IDX); + uint32_t bkpr_core1_addr = + tamp_bkpr(BOOT_API_CORE1_BRANCH_ADDRESS_TAMP_BCK_REG_IDX); mmap_add_region(BL_CODE_BASE, BL_CODE_BASE, BL_CODE_END - BL_CODE_BASE, @@ -206,6 +212,11 @@ pwr_base = stm32mp_pwr_base(); rcc_base = stm32mp_rcc_base(); + /* Clear Stop Request bits to correctly manage low-power exit */ + mmio_write_32(rcc_base + RCC_MP_SREQCLRR, + (uint32_t)(RCC_MP_SREQCLRR_STPREQ_P0 | + RCC_MP_SREQCLRR_STPREQ_P1)); + /* * Disable the backup domain write protection. * The protection is enable at each reset by hardware @@ -217,6 +228,12 @@ ; } + /* + * Configure Standby mode available for MCU by default + * and allow to switch in standby SoC in all case + */ + mmio_setbits_32(pwr_base + PWR_MCUCR, PWR_MCUCR_PDDS); + if (bsec_probe() != 0) { panic(); } @@ -233,9 +250,25 @@ mmio_clrbits_32(rcc_base + RCC_BDCR, RCC_BDCR_VSWRST); } + /* Wait 5 HSI periods before re-enabling PLLs after STOP modes */ + mmio_clrsetbits_32(rcc_base + RCC_PWRLPDLYCR, + RCC_PWRLPDLYCR_PWRLP_DLY_MASK, + PWRLP_TEMPO_5_HSI); + + /* Disable retention and backup RAM content after standby */ + mmio_clrbits_32(pwr_base + PWR_CR2, PWR_CR2_BREN | PWR_CR2_RREN); + /* Disable MCKPROT */ mmio_clrbits_32(rcc_base + RCC_TZCR, RCC_TZCR_MCKPROT); + if ((boot_context->boot_action != + BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY) && + (boot_context->boot_action != + BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY)) { + mmio_write_32(bkpr_core1_addr, 0); + mmio_write_32(bkpr_core1_magic, 0); + } + generic_delay_timer_init(); if (stm32mp1_clk_probe() < 0) { diff --git a/plat/st/stm32mp1/include/boot_api.h b/plat/st/stm32mp1/include/boot_api.h index c16639a..8fc1f6e 100644 --- a/plat/st/stm32mp1/include/boot_api.h +++ b/plat/st/stm32mp1/include/boot_api.h @@ -11,6 +11,90 @@ #include /* + * Exported constants + */ + +/* + * Boot Context related definitions + */ + +/* + * Possible value of boot context field 'boot_action' + */ +/* Boot action is Process Cold Boot */ +#define BOOT_API_CTX_BOOT_ACTION_COLD_BOOT_PROCESS 0x09U +/* Boot action is Process Wakeup from CSTANDBY */ +#define BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY 0x0AU +/* Boot action is Process Wakeup from STANDBY */ +#define BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY 0x0BU +/* Boot action is Process Engineering Boot */ +#define BOOT_API_CTX_BOOT_ACTION_ENGI_BOOT 0x0CU + +#define BOOT_API_CTX_BOOT_ACTION_MPU_CORE0_RESET_PROCESS 0x0F + +/* + * Possible value of boot context field 'stby_exit_status' + */ + +/* The boot reason is not a STANDBY Exit reason */ +#define BOOT_API_CTX_STBY_EXIT_STATUS_NO_STANDBY 0x00 + +/* STANDBY Exit with MPU_BEN=1, MCU_BEN=0 */ +#define BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_MPU_ONLY 0x01 + +/* + * STANDBY Exit with MPU_BEN=1, MCU_BEN=1, MPU will go for cold boot + * MCU restarted by bootROM + */ +#define BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_ALL_CORES 0x02 + +/* + * STANDBY Exit with MPU_BEN=1, MCU_BEN=1, MPU will go for cold boot + * but MCU restart aborted (code integrity check) : have not been restarted + * by bootROM + */ +#define BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_ALL_CORES_MCU_ABT 0x03 + +/* + * STANDBY Exit with MPU_BEN=0, MCU_BEN=1, MPU gone to CSTANDBY, + * MCU restarted correctly by bootROM + * This value should never be read by FSBL, because not executed in that case + */ +#define BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_MCU_ONLY 0x04 + +/* + * STANDBY Exit with MPU_BEN=0, MCU_BEN=1, MCU restart aborted + * due code integrity check, then MPU will go for cold boot despite + * was not planned initially + */ +#define BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_MCU_ONLY_MCU_ABT 0x05 + +/* + * STANDBY Exit with MPU_BEN=1, MCU_BEN=1, MCU restart aborted + * due to MCU security perimeter issue + */ +#define \ +BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_ALL_CORES_MCU_ABT_SEC_PERIMETER_ISSUE 0x06 + +/* + * STANDBY Exit with MPU_BEN=0, MCU_BEN=1, MCU restart aborted + * due to MCU security perimeter issue, then MPU will go for cold boot + * despite was not planned initially + */ +#define \ +BOOT_API_CTX_STBY_EXIT_STATUS_WKUP_MCU_ONLY_MCU_ABT_SEC_PERIMETER_ISSUE 0x07 + +/* + * Possible value of boot context field 'cstby_exit_status' + */ +/* The boot reason is not a CSTANDBY Exit reason */ +#define BOOT_API_CTX_CSTBY_EXIT_STATUS_NO_CSTBY 0x00 +/* CSTANDBY Exit with MCU detected as Not running */ +#define BOOT_API_CTX_CSTBY_EXIT_STATUS_MCU_NOT_RUNNING 0x01 +/* CSTANDBY Exit with MCU detected as Running */ +#define BOOT_API_CTX_CSTBY_EXIT_STATUS_MCU_RUNNING 0x02 + +/* * Possible value of boot context field 'auth_status' */ /* No authentication done */ @@ -153,7 +237,26 @@ uint16_t boot_interface_instance; uint32_t reserved1[13]; uint32_t otp_afmux_values[3]; - uint32_t reserved[5]; + uint32_t reserved[2]; + /* + * Log to boot context, what was the kind of boot action + * takes values from defines BOOT_API_BOOT_ACTION_XXX above + */ + uint32_t boot_action; + /* + * STANDBY Exit status to be checked by FSBL in case + * field 'boot_action' == BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY + * take values from defines above 'BOOT_API_CTX_STBY_EXIT_STATUS_XXX' + * depending on encountered situation + */ + uint32_t stby_exit_status; + /* + * CSTANDBY Exit status to be checked by FSBL in case + * boot_action == BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY + * take values from defines above 'BOOT_API_CTX_CSTBY_EXIT_STATUS_XXX' + * depending on encountered situation + */ + uint32_t cstby_exit_status; uint32_t auth_status; /* diff --git a/plat/st/stm32mp1/include/platform_def.h b/plat/st/stm32mp1/include/platform_def.h index 27ba6f7..a836125 100644 --- a/plat/st/stm32mp1/include/platform_def.h +++ b/plat/st/stm32mp1/include/platform_def.h @@ -113,6 +113,8 @@ */ #define ARM_IRQ_SEC_PHY_TIMER U(29) +#define ARM_IRQ_NON_SEC_SGI_0 U(0) + #define ARM_IRQ_SEC_SGI_0 U(8) #define ARM_IRQ_SEC_SGI_1 U(9) #define ARM_IRQ_SEC_SGI_2 U(10) diff --git a/plat/st/stm32mp1/include/stm32mp1_context.h b/plat/st/stm32mp1/include/stm32mp1_context.h index 698415a..0816917 100644 --- a/plat/st/stm32mp1/include/stm32mp1_context.h +++ b/plat/st/stm32mp1/include/stm32mp1_context.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2018, ARM Limited and Contributors. All rights reserved. + * Copyright (c) 2017-2019, ARM Limited and Contributors. All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause */ @@ -7,8 +7,19 @@ #ifndef STM32MP1_CONTEXT_H #define STM32MP1_CONTEXT_H +#include #include +#define DDR_CRC_GRANULE 32 + +void stm32_clean_context(void); +int stm32_save_context(uint32_t zq0cr0_zdata); +int stm32_restore_context(void); +int stm32_restore_backup_reg(void); +uint32_t stm32_get_zdata_from_context(void); int stm32_save_boot_interface(uint32_t interface, uint32_t instance); +void stm32_save_ddr_training_area(void); +void stm32_restore_ddr_training_area(void); +uint32_t stm32_pm_get_optee_ep(void); #endif /* STM32MP1_CONTEXT_H */ diff --git a/plat/st/stm32mp1/include/stm32mp1_low_power.h b/plat/st/stm32mp1/include/stm32mp1_low_power.h new file mode 100644 index 0000000..82b3d36 --- /dev/null +++ b/plat/st/stm32mp1/include/stm32mp1_low_power.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2017-2019, STMicroelectronics - All Rights Reserved + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef STM32MP1_LOW_POWER_H +#define STM32MP1_LOW_POWER_H + +#include +#include + +void stm32_rcc_wakeup_update(bool state); +void stm32_apply_pmic_suspend_config(uint32_t mode); +void stm32_exit_cstop(void); +void stm32_pwr_down_wfi(void); +void stm32_enter_low_power(uint32_t mode, uint32_t nsec_addr); + +#endif /* STM32MP1_LOW_POWER_H */ diff --git a/plat/st/stm32mp1/include/stm32mp1_power_config.h b/plat/st/stm32mp1/include/stm32mp1_power_config.h new file mode 100644 index 0000000..7c7571a --- /dev/null +++ b/plat/st/stm32mp1/include/stm32mp1_power_config.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2017-2019, STMicroelectronics - All Rights Reserved + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef STM32MP1_POWER_CONFIG_H +#define STM32MP1_POWER_CONFIG_H + +#include +#include + +#define PSCI_MODE_SYSTEM_SUSPEND 0 +#define PSCI_MODE_SYSTEM_OFF 1 + +void stm32mp1_init_lp_states(void); +uint32_t stm32mp1_get_lp_soc_mode(uint32_t psci_mode); + +#endif /* STM32MP1_POWER_CONFIG_H */ diff --git a/plat/st/stm32mp1/include/stm32mp1_private.h b/plat/st/stm32mp1/include/stm32mp1_private.h index 2da64ac..5625e25 100644 --- a/plat/st/stm32mp1/include/stm32mp1_private.h +++ b/plat/st/stm32mp1/include/stm32mp1_private.h @@ -11,6 +11,9 @@ void configure_mmu(void); +void stm32mp_mask_timer(void); +void __dead2 stm32mp_wait_cpu_reset(void); + void stm32mp1_arch_security_setup(void); void stm32mp1_security_setup(void); diff --git a/plat/st/stm32mp1/plat_image_load.c b/plat/st/stm32mp1/plat_image_load.c index 6d7af74..611786d 100644 --- a/plat/st/stm32mp1/plat_image_load.c +++ b/plat/st/stm32mp1/plat_image_load.c @@ -6,7 +6,9 @@ #include +#include #include +#include #include /******************************************************************************* @@ -18,18 +20,73 @@ flush_bl_params_desc(); } +#ifdef AARCH32_SP_OPTEE +static bool addr_inside_backupsram(uintptr_t addr) +{ + return (addr >= STM32MP_BACKUP_RAM_BASE) && + (addr < (STM32MP_BACKUP_RAM_BASE + STM32MP_BACKUP_RAM_SIZE)); +} +#endif + /******************************************************************************* * This function returns the list of loadable images. ******************************************************************************/ bl_load_info_t *plat_get_bl_image_load_info(void) { + boot_api_context_t *boot_context = + (boot_api_context_t *)stm32mp_get_boot_ctx_address(); +#ifdef AARCH32_SP_OPTEE + bl_mem_params_node_t *bl32 = get_bl_mem_params_node(BL32_IMAGE_ID); +#endif bl_mem_params_node_t *bl33 = get_bl_mem_params_node(BL33_IMAGE_ID); uint32_t ddr_ns_size = stm32mp_get_ddr_ns_size(); + uint32_t rstsr = mmio_read_32(stm32mp_rcc_base() + RCC_MP_RSTSCLRR); + uint32_t bkpr_core1_addr = + tamp_bkpr(BOOT_API_CORE1_BRANCH_ADDRESS_TAMP_BCK_REG_IDX); + uintptr_t pwr_base = stm32mp_pwr_base(); /* Max size is non-secure DDR end address minus image_base */ bl33->image_info.image_max_size = STM32MP_DDR_BASE + ddr_ns_size - bl33->image_info.image_base; + /* + * If going back from CSTANDBY / STANDBY and DDR was in Self-Refresh, + * BL33 must not be loaded as it would overwrite the code already + * in DDR. For this, the BL33 part of the bl_mem_params_desc_ptr + * struct should be modified to skip its loading + */ + if (((boot_context->boot_action == + BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY) || + (boot_context->boot_action == + BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY)) && + ((mmio_read_32(pwr_base + PWR_CR3) & PWR_CR3_DDRSREN) != 0U) && + ((rstsr & RCC_MP_RSTSCLRR_PADRSTF) == 0U)) { + stm32mp_clk_enable(RTCAPB); + + if (mmio_read_32(bkpr_core1_addr) != 0U) { + bl33->image_info.h.attr |= IMAGE_ATTRIB_SKIP_LOADING; + +#ifdef AARCH32_SP_OPTEE + bl32->image_info.h.attr |= IMAGE_ATTRIB_SKIP_LOADING; + bl32->ep_info.pc = stm32_pm_get_optee_ep(); + + if (addr_inside_backupsram(bl32->ep_info.pc)) { + stm32mp_clk_enable(BKPSRAM); + } +#else + /* + * Set ep_info PC to 0, to inform BL32 it is a reset + * after STANDBY + */ + bl33->ep_info.pc = 0; +#endif + } + + stm32mp_clk_disable(RTCAPB); + } + + bl33->image_info.image_max_size = dt_get_ddr_size(); + return get_bl_load_info_from_mem_params_desc(); } diff --git a/plat/st/stm32mp1/platform.mk b/plat/st/stm32mp1/platform.mk index 5ce7a9c..c49c928 100644 --- a/plat/st/stm32mp1/platform.mk +++ b/plat/st/stm32mp1/platform.mk @@ -152,6 +152,10 @@ BL2_SOURCES += lib/optee/optee_utils.c endif + +# Do not use neon in TF-A code, it leads to issues in low-power functions +TF_CFLAGS += -mfloat-abi=soft + # Macros and rules to build TF binary STM32_TF_ELF_LDFLAGS := --hash-style=gnu --as-needed STM32_TF_STM32 := $(addprefix ${BUILD_PLAT}/tf-a-, $(patsubst %.dtb,%.stm32,$(DTB_FILE_NAME))) diff --git a/plat/st/stm32mp1/sp_min/sp_min-stm32mp1.mk b/plat/st/stm32mp1/sp_min/sp_min-stm32mp1.mk index 6c7107c..34530f1 100644 --- a/plat/st/stm32mp1/sp_min/sp_min-stm32mp1.mk +++ b/plat/st/stm32mp1/sp_min/sp_min-stm32mp1.mk @@ -6,10 +6,14 @@ SP_MIN_WITH_SECURE_FIQ := 1 +BL32_CFLAGS += -DPLAT_XLAT_TABLES_DYNAMIC=1 + BL32_SOURCES += plat/common/aarch32/platform_mp_stack.S \ drivers/st/rtc/stm32_rtc.c \ plat/st/stm32mp1/sp_min/sp_min_setup.c \ + plat/st/stm32mp1/stm32mp1_low_power.c \ plat/st/stm32mp1/stm32mp1_pm.c \ + plat/st/stm32mp1/stm32mp1_power_config.c \ plat/st/stm32mp1/stm32mp1_topology.c # Generic GIC v2 BL32_SOURCES += drivers/arm/gic/common/gic_common.c \ diff --git a/plat/st/stm32mp1/sp_min/sp_min_setup.c b/plat/st/stm32mp1/sp_min/sp_min_setup.c index 982af80..ea97fa6 100644 --- a/plat/st/stm32mp1/sp_min/sp_min_setup.c +++ b/plat/st/stm32mp1/sp_min/sp_min_setup.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include +#include /****************************************************************************** * Placeholder variables for copying the arguments that have been passed to @@ -38,6 +40,27 @@ static console_t console; +static void stm32_sgi1_it_handler(void) +{ + uint32_t id; + + stm32mp_mask_timer(); + + gicv2_end_of_interrupt(ARM_IRQ_SEC_SGI_1); + + do { + id = plat_ic_get_pending_interrupt_id(); + + if (id <= MAX_SPI_ID) { + gicv2_end_of_interrupt(id); + + plat_ic_disable_interrupt(id); + } + } while (id <= MAX_SPI_ID); + + stm32mp_wait_cpu_reset(); +} + /******************************************************************************* * Interrupt handler for FIQ (secure IRQ) ******************************************************************************/ @@ -48,12 +71,15 @@ ERROR("STM32MP1_IRQ_TZC400 generated\n"); panic(); break; + case ARM_IRQ_SEC_SGI_1: + stm32_sgi1_it_handler(); + break; case STM32MP1_IRQ_AXIERRIRQ: ERROR("STM32MP1_IRQ_AXIERRIRQ generated\n"); panic(); break; default: - ERROR("SECURE IT handler not define for it : %u", id); + ERROR("SECURE IT handler not define for it : %u\n", id); break; } } @@ -67,11 +93,54 @@ entry_point_info_t *sp_min_plat_get_bl33_ep_info(void) { entry_point_info_t *next_image_info; + 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); next_image_info = &bl33_image_ep_info; + /* + * PC is set to 0 when resetting after STANDBY + * The context should be restored, and the image information + * should be filled with what what was saved + */ if (next_image_info->pc == 0U) { - return NULL; + void *cpu_context; + uint32_t magic_nb, saved_pc; + + stm32mp_clk_enable(RTCAPB); + + magic_nb = mmio_read_32(bkpr_core1_magic); + saved_pc = mmio_read_32(bkpr_core1_addr); + + stm32mp_clk_disable(RTCAPB); + + if (stm32_restore_context() != 0) { + panic(); + } + + cpu_context = cm_get_context(NON_SECURE); + + next_image_info->spsr = read_ctx_reg(get_regs_ctx(cpu_context), + CTX_SPSR); + + /* PC should be retrieved in backup register if OK, else it can + * be retrieved from non-secure context + */ + if (magic_nb == BOOT_API_A7_CORE0_MAGIC_NUMBER) { + /* BL33 return address should be in DDR */ + if ((saved_pc < STM32MP_DDR_BASE) || + (saved_pc > (STM32MP_DDR_BASE + + (dt_get_ddr_size() - 1U)))) { + panic(); + } + + next_image_info->pc = saved_pc; + } else { + next_image_info->pc = + read_ctx_reg(get_regs_ctx(cpu_context), CTX_LR); + } } return next_image_info; @@ -145,6 +214,12 @@ #endif console_set_scope(&console, console_flags); } + + if (dt_pmic_status() > 0) { + initialize_pmic(); + } + + stm32mp1_init_lp_states(); } /******************************************************************************* diff --git a/plat/st/stm32mp1/stm32mp1_context.c b/plat/st/stm32mp1/stm32mp1_context.c index cf8a91e..ed54fc6 100644 --- a/plat/st/stm32mp1/stm32mp1_context.c +++ b/plat/st/stm32mp1/stm32mp1_context.c @@ -5,19 +5,171 @@ */ #include +#include #include +#include +#include +#include #include +#include #include +#include #include +#include +#include +#include #include +#include + #define TAMP_BOOT_ITF_BACKUP_REG_ID U(20) #define TAMP_BOOT_ITF_MASK U(0x0000FF00) #define TAMP_BOOT_ITF_SHIFT 8 +#define TRAINING_AREA_SIZE 64 + +#ifdef AARCH32_SP_OPTEE +/* OPTEE_MAILBOX_MAGIC relates to struct backup_data_s as defined */ +#define OPTEE_MAILBOX_MAGIC_V1 0x01 +#define OPTEE_MAILBOX_MAGIC ((OPTEE_MAILBOX_MAGIC_V1 << 16) + \ + TRAINING_AREA_SIZE) +#endif + +struct backup_data_s { +#ifdef AARCH32_SP_OPTEE + uint32_t magic; + uint32_t core0_resume_hint; + uint32_t zq0cr0_zdata; + uint8_t ddr_training_backup[TRAINING_AREA_SIZE]; +#else + smc_ctx_t saved_smc_context[PLATFORM_CORE_COUNT]; + cpu_context_t saved_cpu_context[PLATFORM_CORE_COUNT]; + uint32_t zq0cr0_zdata; + struct stm32_rtc_calendar rtc; + uint8_t ddr_training_backup[TRAINING_AREA_SIZE]; +#endif +}; + +#ifdef AARCH32_SP_OPTEE +uint32_t stm32_pm_get_optee_ep(void) +{ + struct backup_data_s *backup_data; + uint32_t ep; + + stm32mp_clk_enable(BKPSRAM); + + /* Context & Data to be saved at the beginning of Backup SRAM */ + backup_data = (struct backup_data_s *)STM32MP_BACKUP_RAM_BASE; + + if (backup_data->magic != OPTEE_MAILBOX_MAGIC) { + panic(); + } + + ep = backup_data->core0_resume_hint; + + stm32mp_clk_disable(BKPSRAM); + + return ep; +} +#else /*AARCH32_SP_OPTEE*/ +void stm32_clean_context(void) +{ + stm32mp_clk_enable(BKPSRAM); + + zeromem((void *)STM32MP_BACKUP_RAM_BASE, sizeof(struct backup_data_s)); + + stm32mp_clk_disable(BKPSRAM); +} + +int stm32_save_context(uint32_t zq0cr0_zdata) +{ + void *smc_context; + void *cpu_context; + struct backup_data_s *backup_data; + + stm32mp_clk_enable(BKPSRAM); + + /* Context & Data to be saved at the beginning of Backup SRAM */ + backup_data = (struct backup_data_s *)STM32MP_BACKUP_RAM_BASE; + + /* Retrieve smc context struct address */ + smc_context = smc_get_ctx(NON_SECURE); + + /* Retrieve smc context struct address */ + cpu_context = cm_get_context(NON_SECURE); + + /* Save context in Backup SRAM */ + memcpy(&backup_data->saved_smc_context[0], smc_context, + sizeof(smc_ctx_t) * PLATFORM_CORE_COUNT); + memcpy(&backup_data->saved_cpu_context[0], cpu_context, + sizeof(cpu_context_t) * PLATFORM_CORE_COUNT); + + backup_data->zq0cr0_zdata = zq0cr0_zdata; + + stm32_rtc_get_calendar(&backup_data->rtc); + + stm32mp_clk_disable(BKPSRAM); + + return 0; +} + +int stm32_restore_context(void) +{ + void *smc_context; + void *cpu_context; + struct backup_data_s *backup_data; + struct stm32_rtc_calendar current_calendar; + unsigned long long stdby_time_in_ms; + + stm32mp_clk_enable(BKPSRAM); + + /* Context & Data to be saved at the beginning of Backup SRAM */ + backup_data = (struct backup_data_s *)STM32MP_BACKUP_RAM_BASE; + + /* Retrieve smc context struct address */ + smc_context = smc_get_ctx(NON_SECURE); + + /* Retrieve smc context struct address */ + cpu_context = cm_get_context(NON_SECURE); + + /* Restore data from Backup SRAM */ + memcpy(smc_context, backup_data->saved_smc_context, + sizeof(smc_ctx_t) * PLATFORM_CORE_COUNT); + memcpy(cpu_context, backup_data->saved_cpu_context, + sizeof(cpu_context_t) * PLATFORM_CORE_COUNT); + + /* update STGEN counter with standby mode length */ + stm32_rtc_get_calendar(¤t_calendar); + stdby_time_in_ms = stm32_rtc_diff_calendar(¤t_calendar, + &backup_data->rtc); + stm32mp1_stgen_increment(stdby_time_in_ms); + + stm32mp_clk_disable(BKPSRAM); + + return 0; +} +#endif /*AARCH32_SP_OPTEE*/ + +uint32_t stm32_get_zdata_from_context(void) +{ + struct backup_data_s *backup_data; + uint32_t zdata; + + stm32mp_clk_enable(BKPSRAM); + + backup_data = (struct backup_data_s *)STM32MP_BACKUP_RAM_BASE; + + zdata = (backup_data->zq0cr0_zdata >> DDRPHYC_ZQ0CRN_ZDATA_SHIFT) & + DDRPHYC_ZQ0CRN_ZDATA_MASK; + + stm32mp_clk_disable(BKPSRAM); + + return zdata; +} + int stm32_save_boot_interface(uint32_t interface, uint32_t instance) { uint32_t bkpr_itf_idx = tamp_bkpr(TAMP_BOOT_ITF_BACKUP_REG_ID); @@ -33,3 +185,50 @@ return 0; } + +#if defined(IMAGE_BL32) +/* + * When returning from STANDBY, the 64 first bytes of DDR will be overwritten + * during DDR DQS training. This area must then be saved before going to + * standby, and will be restored after + */ +void stm32_save_ddr_training_area(void) +{ + struct backup_data_s *backup_data; + int ret __unused; + + stm32mp_clk_enable(BKPSRAM); + + backup_data = (struct backup_data_s *)STM32MP_BACKUP_RAM_BASE; + + ret = mmap_add_dynamic_region(STM32MP_DDR_BASE, STM32MP_DDR_BASE, + PAGE_SIZE, MT_MEMORY | MT_RW | MT_NS); + assert(ret == 0); + + memcpy(&backup_data->ddr_training_backup, + (const uint32_t *)STM32MP_DDR_BASE, + TRAINING_AREA_SIZE); + dsb(); + + ret = mmap_remove_dynamic_region(STM32MP_DDR_BASE, PAGE_SIZE); + assert(ret == 0); + + stm32mp_clk_disable(BKPSRAM); +} +#endif + +void stm32_restore_ddr_training_area(void) +{ + struct backup_data_s *backup_data; + + stm32mp_clk_enable(BKPSRAM); + + backup_data = (struct backup_data_s *)STM32MP_BACKUP_RAM_BASE; + + memcpy((uint32_t *)STM32MP_DDR_BASE, + &backup_data->ddr_training_backup, + TRAINING_AREA_SIZE); + dsb(); + + stm32mp_clk_disable(BKPSRAM); +} diff --git a/plat/st/stm32mp1/stm32mp1_def.h b/plat/st/stm32mp1/stm32mp1_def.h index 53f1cb1..7f3bbfb 100644 --- a/plat/st/stm32mp1/stm32mp1_def.h +++ b/plat/st/stm32mp1/stm32mp1_def.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #endif @@ -58,6 +59,9 @@ #define STM32MP_SYSRAM_BASE U(0x2FFC0000) #define STM32MP_SYSRAM_SIZE U(0x00040000) +#define STM32MP_BACKUP_RAM_BASE U(0x54000000) +#define STM32MP_BACKUP_RAM_SIZE U(0x00001000) + /* DDR configuration */ #define STM32MP_DDR_BASE U(0xC0000000) #define STM32MP_DDR_MAX_SIZE U(0x40000000) /* Max 1GB */ diff --git a/plat/st/stm32mp1/stm32mp1_low_power.c b/plat/st/stm32mp1/stm32mp1_low_power.c new file mode 100644 index 0000000..36664c2 --- /dev/null +++ b/plat/st/stm32mp1/stm32mp1_low_power.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2017-2019, STMicroelectronics - All Rights Reserved + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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(); + } +} diff --git a/plat/st/stm32mp1/stm32mp1_pm.c b/plat/st/stm32mp1/stm32mp1_pm.c index cf9fa8e..9ebea40 100644 --- a/plat/st/stm32mp1/stm32mp1_pm.c +++ b/plat/st/stm32mp1/stm32mp1_pm.c @@ -13,14 +13,20 @@ #include #include #include +#include #include +#include #include #include #include #include +#include +#include + static uintptr_t stm32_sec_entrypoint; static uint32_t cntfrq_core0; +static uintptr_t saved_entrypoint; /******************************************************************************* * STM32MP1 handler called when a CPU is about to enter standby. @@ -33,11 +39,12 @@ assert(cpu_state == ARM_LOCAL_STATE_RET); /* - * Enter standby state - * dsb is good practice before using wfi to enter low power states + * Enter standby state. + * Synchronize on memory accesses and instruction flow before the WFI + * instruction. */ - isb(); dsb(); + isb(); while (interrupt == GIC_SPURIOUS_INTERRUPT) { wfi(); @@ -64,10 +71,23 @@ uint32_t bkpr_core1_magic = tamp_bkpr(BOOT_API_CORE1_MAGIC_NUMBER_TAMP_BCK_REG_IDX); + if (stm32mp_is_single_core()) { + return PSCI_E_INTERN_FAIL; + } + if (mpidr == current_cpu_mpidr) { return PSCI_E_INVALID_PARAMS; } + /* Reset backup register content */ + mmio_write_32(bkpr_core1_magic, 0); + + /* Need to send additional IT 0 after individual core 1 reset */ + gicv2_raise_sgi(ARM_IRQ_NON_SEC_SGI_0, STM32MP_SECONDARY_CPU); + + /* Wait for this IT to be acknowledged by ROM code. */ + udelay(10); + if ((stm32_sec_entrypoint < STM32MP_SYSRAM_BASE) || (stm32_sec_entrypoint > (STM32MP_SYSRAM_BASE + (STM32MP_SYSRAM_SIZE - 1)))) { @@ -107,7 +127,9 @@ ******************************************************************************/ static void stm32_pwr_domain_suspend(const psci_power_state_t *target_state) { - /* Nothing to do, power domain is not disabled */ + uint32_t soc_mode = stm32mp1_get_lp_soc_mode(PSCI_MODE_SYSTEM_SUSPEND); + + stm32_enter_low_power(soc_mode, saved_entrypoint); } /******************************************************************************* @@ -134,16 +156,59 @@ /* Nothing to do, power domain is not disabled */ } +/******************************************************************************* + * STM32MP1 handler called when a core tries to power itself down. If this + * call is made by core 0, it is a return from stop mode. In this case, we + * should restore previous context and jump to secure entrypoint. + ******************************************************************************/ static void __dead2 stm32_pwr_domain_pwr_down_wfi(const psci_power_state_t *target_state) { - ERROR("stm32mpu1 Power Down WFI: operation not handled.\n"); + if (MPIDR_AFFLVL0_VAL(read_mpidr_el1()) == STM32MP_PRIMARY_CPU) { + void (*warm_entrypoint)(void) = + (void (*)(void))stm32_sec_entrypoint; + + stm32_pwr_down_wfi(); + + stm32_exit_cstop(); + + disable_mmu_icache_secure(); + + warm_entrypoint(); + } + + mmio_write_32(stm32mp_rcc_base() + RCC_MP_GRSTCSETR, + RCC_MP_GRSTCSETR_MPUP1RST); + + /* + * Synchronize on memory accesses and instruction flow before + * auto-reset from the WFI instruction. + */ + dsb(); + isb(); + wfi(); + + /* This shouldn't be reached */ panic(); } static void __dead2 stm32_system_off(void) { - ERROR("stm32mpu1 System Off: operation not handled.\n"); + uint32_t soc_mode = stm32mp1_get_lp_soc_mode(PSCI_MODE_SYSTEM_OFF); + + if (!stm32mp_is_single_core()) { + /* Prepare Core 1 reset */ + mmio_setbits_32(stm32mp_rcc_base() + RCC_MP_GRSTCSETR, + RCC_MP_GRSTCSETR_MPUP1RST); + /* Send IT to core 1 to put itself in WFI */ + gicv2_raise_sgi(ARM_IRQ_SEC_SGI_1, STM32MP_SECONDARY_CPU); + } + + stm32_enter_low_power(soc_mode, 0); + + stm32_pwr_down_wfi(); + + /* This shouldn't be reached */ panic(); } @@ -188,6 +253,8 @@ return PSCI_E_INVALID_ADDRESS; } + saved_entrypoint = entrypoint; + return PSCI_E_SUCCESS; } @@ -211,6 +278,12 @@ return (int)HW_ON; } +static void stm32_get_sys_suspend_power_state(psci_power_state_t *req_state) +{ + req_state->pwr_domain_state[0] = ARM_LOCAL_STATE_OFF; + req_state->pwr_domain_state[1] = ARM_LOCAL_STATE_OFF; +} + /******************************************************************************* * Export the platform handlers. The ARM Standard platform layer will take care * of registering the handlers with PSCI. @@ -227,7 +300,8 @@ .system_reset = stm32_system_reset, .validate_power_state = stm32_validate_power_state, .validate_ns_entrypoint = stm32_validate_ns_entrypoint, - .get_node_hw_state = stm32_node_hw_state + .get_node_hw_state = stm32_node_hw_state, + .get_sys_suspend_power_state = stm32_get_sys_suspend_power_state, }; /******************************************************************************* diff --git a/plat/st/stm32mp1/stm32mp1_power_config.c b/plat/st/stm32mp1/stm32mp1_power_config.c new file mode 100644 index 0000000..93644f4 --- /dev/null +++ b/plat/st/stm32mp1/stm32mp1_power_config.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017-2019, STMicroelectronics - All Rights Reserved + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#define DT_PWR_COMPAT "st,stm32mp1-pwr" +#define SYSTEM_SUSPEND_SUPPORTED_MODES "system_suspend_supported_soc_modes" +#define SYSTEM_OFF_MODE "system_off_soc_mode" + +static uint32_t deepest_system_suspend_mode; +static uint32_t system_off_mode; +static uint8_t stm32mp1_supported_soc_modes[STM32_PM_MAX_SOC_MODE]; + +static int dt_get_pwr_node(void *fdt) +{ + return fdt_node_offset_by_compatible(fdt, -1, DT_PWR_COMPAT); +} + +static void save_supported_mode(void *fdt, int pwr_node) +{ + int len; + uint32_t count; + unsigned int i; + uint32_t supported[ARRAY_SIZE(stm32mp1_supported_soc_modes)]; + const void *prop; + + prop = fdt_getprop(fdt, pwr_node, SYSTEM_SUSPEND_SUPPORTED_MODES, &len); + if (prop == NULL) { + panic(); + } + + count = (uint32_t)len / sizeof(uint32_t); + if (count > STM32_PM_MAX_SOC_MODE) { + panic(); + } + + if (fdt_read_uint32_array(pwr_node, SYSTEM_SUSPEND_SUPPORTED_MODES, + &supported[0], count) < 0) { + ERROR("PWR DT\n"); + panic(); + } + + for (i = 0; i < count; i++) { + if (supported[i] >= STM32_PM_MAX_SOC_MODE) { + ERROR("Invalid mode\n"); + panic(); + } + stm32mp1_supported_soc_modes[supported[i]] = 1U; + } + + /* Initialize to deepest possible mode */ + for (i = STM32_PM_MAX_SOC_MODE - 1U; i != STM32_PM_CSLEEP_RUN; i--) { + if (stm32mp1_supported_soc_modes[i] == 1U) { + deepest_system_suspend_mode = i; + break; + } + } +} + +static int dt_fill_lp_state(uint32_t *lp_state_config, const char *lp_state) +{ + int pwr_node; + void *fdt; + const fdt32_t *cuint; + + if (fdt_get_address(&fdt) == 0) { + return -ENOENT; + } + + pwr_node = dt_get_pwr_node(fdt); + if (pwr_node < 0) { + return -FDT_ERR_NOTFOUND; + } + + cuint = fdt_getprop(fdt, pwr_node, lp_state, NULL); + if (cuint == NULL) { + return -FDT_ERR_NOTFOUND; + } + + *lp_state_config = fdt32_to_cpu(*cuint); + + save_supported_mode(fdt, pwr_node); + + return 0; +} + +void stm32mp1_init_lp_states(void) +{ + if (dt_fill_lp_state(&system_off_mode, SYSTEM_OFF_MODE) < 0) { + ERROR("Node %s not found\n", SYSTEM_OFF_MODE); + panic(); + } +} + +static bool is_allowed_mode(uint32_t soc_mode) +{ + assert(soc_mode < ARRAY_SIZE(stm32mp1_supported_soc_modes)); + + return stm32mp1_supported_soc_modes[soc_mode] == 1U; +} + +uint32_t stm32mp1_get_lp_soc_mode(uint32_t psci_mode) +{ + uint32_t mode; + + if (psci_mode == PSCI_MODE_SYSTEM_OFF) { + return system_off_mode; + } + + mode = deepest_system_suspend_mode; + + while ((mode > STM32_PM_CSLEEP_RUN) && !is_allowed_mode(mode)) { + mode--; + } + + return mode; +} diff --git a/plat/st/stm32mp1/stm32mp1_private.c b/plat/st/stm32mp1/stm32mp1_private.c index ac45195..7e02755 100644 --- a/plat/st/stm32mp1/stm32mp1_private.c +++ b/plat/st/stm32mp1/stm32mp1_private.c @@ -10,8 +10,11 @@ #include +#include +#include #include #include +#include /* Internal layout of the 32bit OTP word board_id */ #define BOARD_ID_BOARD_NB_MASK GENMASK(31, 16) @@ -76,6 +79,42 @@ enable_mmu_svc_mon(0); } +#define ARM_CNTXCTL_IMASK BIT(1) + +void stm32mp_mask_timer(void) +{ + /* Mask timer interrupts */ + write_cntp_ctl(read_cntp_ctl() | ARM_CNTXCTL_IMASK); + write_cntv_ctl(read_cntv_ctl() | ARM_CNTXCTL_IMASK); +} + +void __dead2 stm32mp_wait_cpu_reset(void) +{ + uint32_t id; + + dcsw_op_all(DC_OP_CISW); + write_sctlr(read_sctlr() & ~SCTLR_C_BIT); + dcsw_op_all(DC_OP_CISW); + __asm__("clrex"); + + dsb(); + isb(); + + for ( ; ; ) { + do { + id = plat_ic_get_pending_interrupt_id(); + + if (id <= MAX_SPI_ID) { + gicv2_end_of_interrupt(id); + + plat_ic_disable_interrupt(id); + } + } while (id <= MAX_SPI_ID); + + wfi(); + } +} + unsigned long stm32_get_gpio_bank_clock(unsigned int bank) { if (bank == GPIO_BANK_Z) {