stm32h7: Updated pwr and rcc configs to support devices with SMPS.

Worked in nuances for differences between versions of STM32H7 devices, such as
handling of ODEN, explicit SCUEN bit, and different VOS mappings. This has
been validated on the STM32H7A3 and STM32H743 MCUs.
This commit is contained in:
Brian Viele 2021-02-03 17:21:09 -05:00 committed by Karl Palsson
parent 7b88c2d9d2
commit c2dbea012b
5 changed files with 252 additions and 29 deletions

View File

@ -154,4 +154,7 @@
#define TIM3_BASE 0x40000400U
#define TIM2_BASE 0x40000000U
/* Debug/Trace Peripherals */
#define DBGMCU_BASE 0x5C001000U
#endif

View File

@ -47,8 +47,12 @@ LGPL License Terms @ref lgpl_license
/** CPU Power control register 3. */
#define PWR_CPUCR MMIO32(POWER_CONTROL_BASE + 0x10)
/** D3 Domain Power Control register. */
/** D3 Domain Power Control register.
* Note: Referred to as PWR_SRDCR (SmartRun Domain Control) on LP devices.
* The VOS bitfield differs between the two implementations (unfortunately).
*/
#define PWR_D3CR MMIO32(POWER_CONTROL_BASE + 0x18)
#define PWR_SRDCR MMIO32(POWER_CONTROL_BASE + 0x18)
/** Wakeup Domain Power Control register. */
#define PWR_WKUPCR MMIO32(POWER_CONTROL_BASE + 0x20)
@ -57,39 +61,133 @@ LGPL License Terms @ref lgpl_license
/** VOS[15:14]: Regulator voltage scaling output selection */
#define PWR_CR1_SVOS_SHIFT 14
#define PWR_CR1_SVOS_MASK (0x3)
#define PWR_CR1_SVOS_SCALE_3 (0x3)
#define PWR_CR1_SVOS_SCALE_4 (0x2)
#define PWR_CR1_SVOS_SCALE_5 (0x1)
#define PWR_CR1_SVOS_MASK (0x3)
/** SmartRun domain AHB memory shut-off in DStop/DStop2 low-power mode */
#define PWR_CR1_SRDRAMSO BIT27
/** high-speed interfaces USB and FDCAN memory shut-off in DStop/DStop2 mode */
#define PWR_CR1_HSITFSO BIT26
/** GFXMMU and JPEG memory shut-off in DStop/DStop2 mode */
#define PWR_CR1_GFXSO BIT25
/** instruction TCM and ETM memory shut-off in DStop/DStop2 mode */
#define PWR_CR1_ITCMSO BIT24
/** AHB SRAM2 shut-off in DStop/DStop2 mode */
#define PWR_CR1_AHBRAM2SO BIT23
/** AHB SRAM1 shut-off in DStop/DStop2 mode */
#define PWR_CR1_AHBRAM1SO BIT22
/** AXI SRAM3 shut-off in DStop/DStop2 mode */
#define PWR_CR1_AXIRAM3SO BIT21
/** AXI SRAM2 shut-off in DStop/DStop2 mode */
#define PWR_CR1_AXIRAM2SO BIT20
/** AXI SRAM1 shut-off in DStop/DStop2 mode */
#define PWR_CR1_AXIRAM1SO BIT19
/** voltage threshold detected by the AVD. */
#define PWR_CR1_ALS_SHIFT 17
#define PWR_CR1_ALS_MASK 0x3
#define PWR_CR1_ALS_1P7V 0x0
#define PWR_CR1_ALS_2P1V 0x1
#define PWR_CR1_ALS_2P5V 0x2
#define PWR_CR1_ALS_2P8V 0x3
/** peripheral voltage monitor on V DDA enable */
#define PWR_CR1_AVDEN BIT16
/** analog voltage ready
* This bit is only used when the analog switch boost needs to be enabled (see BOOSTE bit).
* It must be set by software when the expected V DDA analog supply level is available.
* The correct analog supply level is indicated by the AVDO bit (PWR_CSR1 register) after
* setting the AVDEN bit and selecting the supply level to be monitored (ALS bits).
*/
#define PWR_CR1_AVD_READY BIT13
/** analog switch VBoost control
* This bit enables the booster to guarantee the analog switch AC performance when the V DD
* supply voltage is below 2.7 V (reduction of the total harmonic distortion to have the same
* switch performance over the full supply voltage range)
* The V DD supply voltage can be monitored through the PVD and the PLS bits.
*/
#define PWR_CR1_BOOSTE BIT12
/** DBP[8]: Disable backup domain write protection. */
#define PWR_CR1_DBP (1 << 8)
#define PWR_CR1_DBP BIT8
/** CSR1 Register Bits */
#define PWR_CSR1_MMCVDO BIT17
#define PWR_CSR1_AVDO BIT16
#define PWR_CSR1_ACTVOS_SHIFT 14
#define PWR_CSR1_ACTVOSRDY BIT13
#define PWR_CSR1_PVDO BIT4
/** CR2 Register Bits */
/** temperature level monitoring versus high threshold */
#define PWR_CR2_TEMPH BIT23
/** temperature level monitoring versus low threshold */
#define PWR_CR2_TEMPL BIT22
/** backup regulator ready */
#define PWR_CR2_BRRDY BIT16
/** V BAT and temperature monitoring enable */
#define PWR_CR2_MONEN BIT4
/** backup regulator enable */
#define PWR_CR2_BREN BIT0
/** CR3 Register Bits */
#define PWR_CR3_USB33RDY BIT26
#define PWR_CR3_USBREGEN BIT25
#define PWR_CR3_USB33DEN BIT24
/** SMPS step-down converter external supply ready */
#define PWR_CR3_SMPSEXTRDY BIT16
/** V BAT charging resistor selection */
#define PWR_CR3_VBRS BIT9
/** V BAT charging enable */
#define PWR_CR3_VBE BIT8
/** SMPS step-down converter voltage output level selection
* This setting is used when both the LDO and SMPS step-down converter are enabled with SMPSEN and
* LDOEN enabled or when SMPSEXTHP is enabled. In this case SMPSLEVEL must be written with
* a value different than 00 to reach the appropriate voltage, based on VOS or external supply.
*/
#define PWR_CR3_SMPSLEVEL_SHIFT 4
#define PWR_CR3_SMPSLEVEL_MASK 0x3
#define PWR_CR3_SMPSLEVEL_VOS 0x0
#define PWR_CR3_SMPSLEVEL_1P8V 0x1
#define PWR_CR3_SMPSLEVEL_2P5V 0x2
/** SMPS step-down converter external power delivery selection */
#define PWR_CR3_SMPSEXTHP BIT3
#define PWR_CR3_SCUEN BIT2
/* BIT2 Is overloaded on devices with SMPS as the SMPSEN bit. */
#define PWR_CR3_SMPSEN BIT2
#define PWR_CR3_LDOEN BIT1
#define PWR_CR3_BYPASS BIT0
/** D3CR Register Bits */
#define PWR_D3CR_VOS_SHIFT 14
#define PWR_D3CR_VOSRDY BIT13
#define PWR_D3CR_VOS_SHIFT 14
#define PWR_D3CR_VOS_MASK (0x03)
/** VOS0 is implemented on STM32H72x/3x with simple VOS setting.
* STM32H742/43/45/47/50/53/55/57 SCALE0 this as SCALE1 + SYSCFG.ODEN */
#define PWR_D3CR_VOS_SCALE_0 (0x0)
#define PWR_D3CR_VOS_SCALE_3 (0x1)
#define PWR_D3CR_VOS_SCALE_2 (0x2)
#define PWR_D3CR_VOS_SCALE_1 (0x3)
#define PWR_D3CR_VOS_MASK (0x03)
/** SRDCR Register Bits */
#define PWR_SRDCR_VOSRDY BIT13
#define PWR_SRDCR_VOS_SHIFT 14
#define PWR_SRDCR_VOS_MASK (0x03)
#define PWR_SRDCR_VOS_SCALE_3 (0x0)
#define PWR_SRDCR_VOS_SCALE_2 (0x1)
#define PWR_SRDCR_VOS_SCALE_1 (0x2)
#define PWR_SRDCR_VOS_SCALE_0 (0x3)
enum pwr_sys_mode {
PWR_SYS_SCU_LDO = 0, /**< STM32H742/43/50/53 has special SCUEN handling, use for LDO. */
PWR_SYS_SCU_BYPASS, /**< STM32H742/43/50/53 has special SCUEN handling, use for bypass. */
PWR_SYS_LDO, /**< Devices with SMPS use this to run from LDO only. */
PWR_SYS_SMPS_DIRECT, /**< Disable LDO, apply SMPS direct to CPU using VOS. */
PWR_SYS_SMPS_LDO, /**< SMPS supplies internal LDO. */
PWR_SYS_EXT_SMPS_LDO, /**< SMPS supplies external power, and CPU through LDO. */
PWR_SYS_EXT_SMPS_LDO_BYP, /**< SMPS supplies external power, bypasses LDO (e.g. external LDO) */
PWR_SYS_BYPASS /**< Disable all internal power supplies. */
};
enum pwr_svos_scale {
PWR_SVOS_SCALE3 = PWR_CR1_SVOS_SCALE_3 << PWR_CR1_SVOS_SHIFT,
@ -98,10 +196,11 @@ enum pwr_svos_scale {
};
enum pwr_vos_scale {
PWR_VOS_SCALE_0 = 0, /* Note: This state requires SYSCFG ODEN set. */
PWR_VOS_SCALE_1 = (PWR_D3CR_VOS_SCALE_1 << PWR_D3CR_VOS_SHIFT),
PWR_VOS_SCALE_2 = (PWR_D3CR_VOS_SCALE_2 << PWR_D3CR_VOS_SHIFT),
PWR_VOS_SCALE_3 = (PWR_D3CR_VOS_SCALE_3 << PWR_D3CR_VOS_SHIFT)
PWR_VOS_SCALE_UNDEFINED = 0,
PWR_VOS_SCALE_0,
PWR_VOS_SCALE_1,
PWR_VOS_SCALE_2,
PWR_VOS_SCALE_3,
};
BEGIN_DECLS
@ -109,16 +208,37 @@ BEGIN_DECLS
/** @defgroup pwr_peripheral_api PWR Peripheral API
* @ingroup peripheral_apis
@{*/
/** Set power subsystem to utilize the LDO for CPU. */
void pwr_set_mode_ldo(void);
/** Set the voltage scaling/strength for the internal LDO when in Stop mode.
/** Specific STM32H742/43/50/53 LDO mode setting.. */
void pwr_set_mode_scu_ldo(void);
/** Set power subsystem to utilize the SMPS run through the LDO for CPU.
* @param[in] supply_external Supply is powering external circuits, enable high power mode.
* @param[in] smps_level Voltage level from available PWR_CR3_SMPSLEVEL_XXX settings (1.8V/2.5V)
* @param[in] use_ldo Set this value to true if the internal LDO should be enabled.
*/
void pwr_set_mode_smps_ldo(bool supply_external, uint32_t smps_level, bool use_ldo);
/** Set power system based on "System Supply Configurations" table in reference manual.
* @param[in] mode Mode mapping to a mode in the system configuration. Note special SCU modes.
* @param[in] smps_level Optional, only needed if using an EXT_SMPS or SMPS_LDO mode.
*/
void pwr_set_mode(enum pwr_sys_mode mode, uint8_t smps_level);
/** Set power subsystem to bypass all internal supplies. */
void pwr_set_mode_bypass(void);
/** Specific STM32H742/43/50/53 Bypsass mode setting.. */
void pwr_set_mode_scu_bypass(void);
/** Set the voltage scaling/strength for the internal SMPS/LDO when in Stop mode.
* @param[in] scale Voltage scale value to set.
*/
void pwr_set_svos_scale(enum pwr_svos_scale scale);
/** Set the voltage scaling/strength for the internal LDO while running.
/** Set the voltage scaling/strength for the internal SMPS/LDO while running.
* @param[in] scale Voltage scale value to set.
*/
void pwr_set_vos_scale(enum pwr_vos_scale scale);

View File

@ -462,7 +462,9 @@ struct rcc_pll_config {
uint8_t ppre3; /**< APB3 Peripheral prescaler note: domain 1. */
uint8_t ppre4; /**< APB4 Peripheral prescaler note: domain 3. */
uint8_t flash_waitstates; /**< Latency Value to set for flahs. */
enum pwr_vos_scale voltage_scale; /**< LDO Voltage scale used for this frequency. */
enum pwr_vos_scale voltage_scale; /**< LDO/SMPS Voltage scale used for this frequency. */
enum pwr_sys_mode power_mode; /**< LDO/SMPS configuration for device. */
uint8_t smps_level; /**< If using SMPS, voltage level to set. */
};
#define _REG_BIT(base, bit) (((base) << 5) + (bit))

View File

@ -30,15 +30,80 @@
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libopencm3/cm3/assert.h>
#include <libopencm3/stm32/dbgmcu.h>
#include <libopencm3/stm32/pwr.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/syscfg.h>
/* DBGMCU_IDC DEV ID values needed to account for variations between part types. */
#define DBGMCU_IDCODE_DEV_ID_STM32H74X_5X 0x450
#define DBGMCU_IDCODE_DEV_ID_STM32H7A3_B3_B0 0x480
void pwr_set_mode_ldo(void) {
const uint32_t ldo_mask = (PWR_CR3_SCUEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
PWR_CR3 = (PWR_CR3 & ~ldo_mask) | (PWR_CR3_SCUEN | PWR_CR3_LDOEN);
/* Per table in manual for SMPS, mask and set SMPSEN=0 : LDOEN=1 : BYPASS=0. */
const uint32_t cr3_mask = (PWR_CR3_SMPSEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | (PWR_CR3_LDOEN);
}
void pwr_set_mode_scu_ldo(void) {
const uint32_t cr3_mask = (PWR_CR3_SCUEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | (PWR_CR3_SCUEN | PWR_CR3_LDOEN);
}
void pwr_set_mode_smps_ldo(bool supply_external, uint32_t smps_level, bool use_ldo) {
uint32_t cr3_mask, cr3_set;
cr3_mask = (PWR_CR3_SMPSEXTHP | PWR_CR3_SMPSEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
cr3_mask |= PWR_CR3_SMPSLEVEL_MASK << PWR_CR3_SMPSLEVEL_SHIFT;
/* Default, take in unconditional settings, will OR in the rest. */
cr3_set = PWR_CR3_SMPSEN | (smps_level << PWR_CR3_SMPSLEVEL_SHIFT);
if (supply_external) {
cm3_assert(smps_level != PWR_CR3_SMPSLEVEL_VOS); /* Unsupported setting! */
cr3_set |= PWR_CR3_SMPSEXTHP;
}
if (use_ldo) {
cr3_set |= PWR_CR3_LDOEN;
}
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | cr3_set;
}
void pwr_set_mode_bypass(void) {
const uint32_t cr3_mask = (PWR_CR3_SMPSEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | PWR_CR3_BYPASS;
}
void pwr_set_mode_scu_bypass(void) {
const uint32_t cr3_mask = (PWR_CR3_SCUEN | PWR_CR3_LDOEN | PWR_CR3_BYPASS);
PWR_CR3 = (PWR_CR3 & ~cr3_mask) | (PWR_CR3_SCUEN | PWR_CR3_BYPASS);
}
void pwr_set_mode(enum pwr_sys_mode mode, uint8_t smps_level) {
switch (mode) {
case PWR_SYS_SCU_LDO:
pwr_set_mode_scu_ldo();
break;
case PWR_SYS_SCU_BYPASS:
pwr_set_mode_scu_bypass();
break;
case PWR_SYS_LDO:
pwr_set_mode_ldo();
break;
case PWR_SYS_SMPS_DIRECT:
case PWR_SYS_SMPS_LDO:
pwr_set_mode_smps_ldo(false, PWR_CR3_SMPSLEVEL_VOS, mode == PWR_SYS_SMPS_LDO);
break;
case PWR_SYS_EXT_SMPS_LDO:
case PWR_SYS_EXT_SMPS_LDO_BYP:
pwr_set_mode_smps_ldo(false, smps_level, mode == PWR_SYS_EXT_SMPS_LDO);
break;
case PWR_SYS_BYPASS:
pwr_set_mode_bypass();
break;
}
/* Wait for power supply status to state ready. */
while (!(PWR_CSR1 & PWR_CSR1_ACTVOSRDY));
}
@ -50,16 +115,49 @@ void pwr_set_svos_scale(enum pwr_svos_scale scale)
}
void pwr_set_vos_scale(enum pwr_vos_scale scale) {
rcc_periph_clock_enable(RCC_SYSCFG); /* Ensure we can access ODEN. */
uint32_t d3cr_masked = PWR_D3CR & ~(PWR_D3CR_VOS_MASK << PWR_D3CR_VOS_SHIFT);
static const uint8_t srdcr_vos_values[] = {
PWR_SRDCR_VOS_SCALE_0,
PWR_SRDCR_VOS_SCALE_1,
PWR_SRDCR_VOS_SCALE_2,
PWR_SRDCR_VOS_SCALE_3,
};
static const uint8_t d3cr_vos_values[] = {
PWR_D3CR_VOS_SCALE_0,
PWR_D3CR_VOS_SCALE_1,
PWR_D3CR_VOS_SCALE_2,
PWR_D3CR_VOS_SCALE_3,
};
cm3_assert(scale != PWR_VOS_SCALE_UNDEFINED); /* Make sure this has been set. */
/* Per the manual, VOS0 is implemented as VOS1 + ODEN. Handle this case. */
if (scale == PWR_VOS_SCALE_0) {
PWR_D3CR = d3cr_masked | PWR_VOS_SCALE_1;
SYSCFG_PWRCR |= SYSCFG_PWRCR_ODEN;
/* "SmartRun Domain" devices (presently only know of A3/B3/B0) have different mapping.
* Note: DBGMCU_IDCODE_DEV_ID_STM32H7A3 covers all three of these models.
*/
uint32_t devid = DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK;
if (devid == DBGMCU_IDCODE_DEV_ID_STM32H7A3_B3_B0) {
const uint32_t srdcr_vos_mask = (PWR_SRDCR_VOS_MASK << PWR_SRDCR_VOS_SHIFT);
const uint32_t vos_value = srdcr_vos_values[scale - 1] << PWR_SRDCR_VOS_SHIFT;
PWR_SRDCR = (PWR_SRDCR & ~srdcr_vos_mask) | vos_value;
} else {
SYSCFG_PWRCR &= ~SYSCFG_PWRCR_ODEN;
PWR_D3CR = d3cr_masked | scale;
/* Get the VOS value for the non-smart domain types. */
uint32_t d3cr_vos = (uint32_t)d3cr_vos_values[scale - 1] << PWR_D3CR_VOS_SHIFT;
uint32_t d3cr_masked = PWR_D3CR & ~(PWR_D3CR_VOS_MASK << PWR_D3CR_VOS_SHIFT);
/* STM32H742/43/45/47/50/53/55/57 have special handling of VOS0, which is to set
* VOS1, and also enable the ODEN in the SYSCFG_PWRCR.
* Note: Conveniently, all devices with this setup share a devid, so pick one.
*/
if (devid == DBGMCU_IDCODE_DEV_ID_STM32H74X_5X) {
rcc_periph_clock_enable(RCC_SYSCFG); /* Ensure we can access ODEN. */
/* Per the manual, VOS0 is implemented as VOS1 + ODEN. Handle this case. */
if (scale == PWR_VOS_SCALE_0) {
PWR_D3CR = d3cr_masked | (PWR_D3CR_VOS_SCALE_1 << PWR_SRDCR_VOS_SHIFT);
SYSCFG_PWRCR |= SYSCFG_PWRCR_ODEN;
} else {
SYSCFG_PWRCR &= ~SYSCFG_PWRCR_ODEN;
PWR_D3CR = d3cr_masked | d3cr_vos;
}
} else {
PWR_D3CR = d3cr_masked | d3cr_vos;
}
}
while (!(PWR_D3CR & PWR_D3CR_VOSRDY));
while (!(PWR_D3CR & PWR_D3CR_VOSRDY)); /* VOSRDY bit is same between D3CR and SRDCR. */
}

View File

@ -185,8 +185,8 @@ void rcc_clock_setup_pll(const struct rcc_pll_config *config) {
while (((RCC_CFGR >> RCC_CFGR_SWS_SHIFT) & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS_HSI);
RCC_CR = RCC_CR_HSION;
/* Now that we're safely running on HSI, let's setup the LDO. */
pwr_set_mode_ldo();
/* Now that we're safely running on HSI, let's setup the power system for scaling. */
pwr_set_mode(config->power_mode, config->smps_level);
pwr_set_vos_scale(config->voltage_scale);
/* Set flash waitstates. Enable flash prefetch if we have at least 1WS */