/* * Copyright (c) 2024, STMicroelectronics - All Rights Reserved * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include #include #include #include #include #include /* HW idle period (unit: Multiples of 32 DFI clock cycles) */ #define HW_IDLE_PERIOD 0x3U static enum stm32mp2_ddr_sr_mode saved_ddr_sr_mode; #pragma weak stm32_ddrdbg_get_base uintptr_t stm32_ddrdbg_get_base(void) { return 0U; } static void set_qd1_qd3_update_conditions(struct stm32mp_ddrctl *ctl) { mmio_setbits_32((uintptr_t)&ctl->dbg1, DDRCTRL_DBG1_DIS_DQ); stm32mp_ddr_set_qd3_update_conditions(ctl); } static void unset_qd1_qd3_update_conditions(struct stm32mp_ddrctl *ctl) { stm32mp_ddr_unset_qd3_update_conditions(ctl); mmio_clrbits_32((uintptr_t)&ctl->dbg1, DDRCTRL_DBG1_DIS_DQ); } static void wait_dfi_init_complete(struct stm32mp_ddrctl *ctl) { uint64_t timeout; uint32_t dfistat; timeout = timeout_init_us(DDR_TIMEOUT_US_1S); do { dfistat = mmio_read_32((uintptr_t)&ctl->dfistat); VERBOSE("[0x%lx] dfistat = 0x%x ", (uintptr_t)&ctl->dfistat, dfistat); if (timeout_elapsed(timeout)) { panic(); } } while ((dfistat & DDRCTRL_DFISTAT_DFI_INIT_COMPLETE) == 0U); VERBOSE("[0x%lx] dfistat = 0x%x\n", (uintptr_t)&ctl->dfistat, dfistat); } static void disable_dfi_low_power_interface(struct stm32mp_ddrctl *ctl) { uint64_t timeout; uint32_t dfistat; uint32_t stat; mmio_clrbits_32((uintptr_t)&ctl->dfilpcfg0, DDRCTRL_DFILPCFG0_DFI_LP_EN_SR); timeout = timeout_init_us(DDR_TIMEOUT_US_1S); do { dfistat = mmio_read_32((uintptr_t)&ctl->dfistat); stat = mmio_read_32((uintptr_t)&ctl->stat); VERBOSE("[0x%lx] dfistat = 0x%x ", (uintptr_t)&ctl->dfistat, dfistat); VERBOSE("[0x%lx] stat = 0x%x ", (uintptr_t)&ctl->stat, stat); if (timeout_elapsed(timeout)) { panic(); } } while (((dfistat & DDRCTRL_DFISTAT_DFI_LP_ACK) != 0U) || ((stat & DDRCTRL_STAT_OPERATING_MODE_MASK) == DDRCTRL_STAT_OPERATING_MODE_SR)); VERBOSE("[0x%lx] dfistat = 0x%x\n", (uintptr_t)&ctl->dfistat, dfistat); VERBOSE("[0x%lx] stat = 0x%x\n", (uintptr_t)&ctl->stat, stat); } void ddr_activate_controller(struct stm32mp_ddrctl *ctl, bool sr_entry) { /* * Manage quasi-dynamic registers modification * dfimisc.dfi_frequency : Group 1 * dfimisc.dfi_init_complete_en and dfimisc.dfi_init_start : Group 3 */ set_qd1_qd3_update_conditions(ctl); if (sr_entry) { mmio_setbits_32((uintptr_t)&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_FREQUENCY); } else { mmio_clrbits_32((uintptr_t)&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_FREQUENCY); } mmio_setbits_32((uintptr_t)&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_START); mmio_clrbits_32((uintptr_t)&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_START); wait_dfi_init_complete(ctl); udelay(DDR_DELAY_1US); if (sr_entry) { mmio_clrbits_32((uintptr_t)&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN); } else { mmio_setbits_32((uintptr_t)&ctl->dfimisc, DDRCTRL_DFIMISC_DFI_INIT_COMPLETE_EN); } udelay(DDR_DELAY_1US); unset_qd1_qd3_update_conditions(ctl); } #if STM32MP_LPDDR4_TYPE static void disable_phy_ddc(void) { /* Enable APB access to internal CSR registers */ mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_APBONLY0_MICROCONTMUXSEL, 0U); mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_DRTUB0_UCCLKHCLKENABLES, DDRPHY_DRTUB0_UCCLKHCLKENABLES_UCCLKEN | DDRPHY_DRTUB0_UCCLKHCLKENABLES_HCLKEN); /* Disable DRAM drift compensation */ mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_INITENG0_P0_SEQ0BDISABLEFLAG6, 0xFFFFU); /* Disable APB access to internal CSR registers */ mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_DRTUB0_UCCLKHCLKENABLES, DDRPHY_DRTUB0_UCCLKHCLKENABLES_HCLKEN); mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_APBONLY0_MICROCONTMUXSEL, DDRPHY_APBONLY0_MICROCONTMUXSEL_MICROCONTMUXSEL); } #endif /* STM32MP_LPDDR4_TYPE */ void ddr_wait_lp3_mode(bool sr_entry) { uint64_t timeout; bool repeat_loop = false; /* Enable APB access to internal CSR registers */ mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_APBONLY0_MICROCONTMUXSEL, 0U); mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_DRTUB0_UCCLKHCLKENABLES, DDRPHY_DRTUB0_UCCLKHCLKENABLES_UCCLKEN | DDRPHY_DRTUB0_UCCLKHCLKENABLES_HCLKEN); timeout = timeout_init_us(DDR_TIMEOUT_US_1S); do { uint16_t phyinlpx = mmio_read_32(stm32mp_ddrphyc_base() + DDRPHY_INITENG0_P0_PHYINLPX); if (timeout_elapsed(timeout)) { panic(); } if (sr_entry) { repeat_loop = (phyinlpx & DDRPHY_INITENG0_P0_PHYINLPX_PHYINLP3) == 0U; } else { repeat_loop = (phyinlpx & DDRPHY_INITENG0_P0_PHYINLPX_PHYINLP3) != 0U; } } while (repeat_loop); /* Disable APB access to internal CSR registers */ #if STM32MP_DDR3_TYPE || STM32MP_DDR4_TYPE mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_DRTUB0_UCCLKHCLKENABLES, 0U); #else /* STM32MP_LPDDR4_TYPE */ mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_DRTUB0_UCCLKHCLKENABLES, DDRPHY_DRTUB0_UCCLKHCLKENABLES_HCLKEN); #endif /* STM32MP_DDR3_TYPE || STM32MP_DDR4_TYPE */ mmio_write_32(stm32mp_ddrphyc_base() + DDRPHY_APBONLY0_MICROCONTMUXSEL, DDRPHY_APBONLY0_MICROCONTMUXSEL_MICROCONTMUXSEL); } static int sr_loop(bool is_entry) { uint32_t type; uint32_t state __maybe_unused; uint64_t timeout = timeout_init_us(DDR_TIMEOUT_US_1S); bool repeat_loop = false; /* * Wait for DDRCTRL to be out of or back to "normal/mission mode". * Consider also SRPD mode for LPDDR4 only. */ do { type = mmio_read_32(stm32mp_ddrctrl_base() + DDRCTRL_STAT) & DDRCTRL_STAT_SELFREF_TYPE_MASK; #if STM32MP_LPDDR4_TYPE state = mmio_read_32(stm32mp_ddrctrl_base() + DDRCTRL_STAT) & DDRCTRL_STAT_SELFREF_STATE_MASK; #endif /* STM32MP_LPDDR4_TYPE */ if (timeout_elapsed(timeout)) { return -ETIMEDOUT; } if (is_entry) { #if STM32MP_LPDDR4_TYPE repeat_loop = (type == 0x0U) || (state != DDRCTRL_STAT_SELFREF_STATE_SRPD); #else /* !STM32MP_LPDDR4_TYPE */ repeat_loop = (type == 0x0U); #endif /* STM32MP_LPDDR4_TYPE */ } else { #if STM32MP_LPDDR4_TYPE repeat_loop = (type != 0x0U) || (state != 0x0U); #else /* !STM32MP_LPDDR4_TYPE */ repeat_loop = (type != 0x0U); #endif /* STM32MP_LPDDR4_TYPE */ } } while (repeat_loop); return 0; } static int sr_entry_loop(void) { return sr_loop(true); } int ddr_sr_exit_loop(void) { return sr_loop(false); } static int sr_ssr_set(void) { uintptr_t ddrctrl_base = stm32mp_ddrctrl_base(); /* * Disable Clock disable with LP modes * (used in RUN mode for LPDDR2 with specific timing). */ mmio_clrbits_32(ddrctrl_base + DDRCTRL_PWRCTL, DDRCTRL_PWRCTL_EN_DFI_DRAM_CLK_DISABLE); /* Disable automatic Self-Refresh mode */ mmio_clrbits_32(ddrctrl_base + DDRCTRL_PWRCTL, DDRCTRL_PWRCTL_SELFREF_EN); mmio_write_32(stm32_ddrdbg_get_base() + DDRDBG_LP_DISABLE, DDRDBG_LP_DISABLE_LPI_XPI_DISABLE | DDRDBG_LP_DISABLE_LPI_DDRC_DISABLE); return 0; } static int sr_ssr_entry(bool standby) { uintptr_t ddrctrl_base = stm32mp_ddrctrl_base(); uintptr_t rcc_base = stm32mp_rcc_base(); if (stm32mp_ddr_disable_axi_port((struct stm32mp_ddrctl *)ddrctrl_base) != 0) { panic(); } #if STM32MP_LPDDR4_TYPE if (standby) { /* Disable DRAM drift compensation */ disable_phy_ddc(); } #endif /* STM32MP_LPDDR4_TYPE */ disable_dfi_low_power_interface((struct stm32mp_ddrctl *)ddrctrl_base); /* SW self refresh entry prequested */ mmio_setbits_32(ddrctrl_base + DDRCTRL_PWRCTL, DDRCTRL_PWRCTL_SELFREF_SW); #if STM32MP_LPDDR4_TYPE mmio_clrbits_32(ddrctrl_base + DDRCTRL_PWRCTL, DDRCTRL_PWRCTL_STAY_IN_SELFREF); #endif /* STM32MP_LPDDR4_TYPE */ if (sr_entry_loop() != 0) { return -1; } ddr_activate_controller((struct stm32mp_ddrctl *)ddrctrl_base, true); /* Poll on ddrphy_initeng0_phyinlpx.phyinlp3 = 1 */ ddr_wait_lp3_mode(true); if (standby) { mmio_clrbits_32(stm32mp_pwr_base() + PWR_CR11, PWR_CR11_DDRRETDIS); } mmio_clrsetbits_32(rcc_base + RCC_DDRCPCFGR, RCC_DDRCPCFGR_DDRCPLPEN, RCC_DDRCPCFGR_DDRCPEN); mmio_setbits_32(rcc_base + RCC_DDRPHYCCFGR, RCC_DDRPHYCCFGR_DDRPHYCEN); mmio_setbits_32(rcc_base + RCC_DDRITFCFGR, RCC_DDRITFCFGR_DDRPHYDLP); return 0; } static int sr_ssr_exit(void) { uintptr_t ddrctrl_base = stm32mp_ddrctrl_base(); uintptr_t rcc_base = stm32mp_rcc_base(); mmio_setbits_32(rcc_base + RCC_DDRCPCFGR, RCC_DDRCPCFGR_DDRCPLPEN | RCC_DDRCPCFGR_DDRCPEN); mmio_clrbits_32(rcc_base + RCC_DDRITFCFGR, RCC_DDRITFCFGR_DDRPHYDLP); mmio_setbits_32(rcc_base + RCC_DDRPHYCCFGR, RCC_DDRPHYCCFGR_DDRPHYCEN); udelay(DDR_DELAY_1US); ddr_activate_controller((struct stm32mp_ddrctl *)ddrctrl_base, false); /* Poll on ddrphy_initeng0_phyinlpx.phyinlp3 = 0 */ ddr_wait_lp3_mode(false); /* SW self refresh exit prequested */ mmio_clrbits_32(ddrctrl_base + DDRCTRL_PWRCTL, DDRCTRL_PWRCTL_SELFREF_SW); if (ddr_sr_exit_loop() != 0) { return -1; } /* Re-enable DFI low-power interface */ mmio_setbits_32(ddrctrl_base + DDRCTRL_DFILPCFG0, DDRCTRL_DFILPCFG0_DFI_LP_EN_SR); stm32mp_ddr_enable_axi_port((struct stm32mp_ddrctl *)ddrctrl_base); return 0; } static int sr_hsr_set(void) { uintptr_t ddrctrl_base = stm32mp_ddrctrl_base(); mmio_clrsetbits_32(stm32mp_rcc_base() + RCC_DDRITFCFGR, RCC_DDRITFCFGR_DDRCKMOD_MASK, RCC_DDRITFCFGR_DDRCKMOD_HSR); /* * manage quasi-dynamic registers modification * hwlpctl.hw_lp_en : Group 2 */ if (stm32mp_ddr_sw_selfref_entry((struct stm32mp_ddrctl *)ddrctrl_base) != 0) { panic(); } stm32mp_ddr_start_sw_done((struct stm32mp_ddrctl *)ddrctrl_base); mmio_write_32(ddrctrl_base + DDRCTRL_HWLPCTL, DDRCTRL_HWLPCTL_HW_LP_EN | DDRCTRL_HWLPCTL_HW_LP_EXIT_IDLE_EN | (HW_IDLE_PERIOD << DDRCTRL_HWLPCTL_HW_LP_IDLE_X32_SHIFT)); stm32mp_ddr_wait_sw_done_ack((struct stm32mp_ddrctl *)ddrctrl_base); stm32mp_ddr_sw_selfref_exit((struct stm32mp_ddrctl *)ddrctrl_base); return 0; } static int sr_hsr_entry(void) { mmio_write_32(stm32mp_rcc_base() + RCC_DDRCPCFGR, RCC_DDRCPCFGR_DDRCPLPEN); return sr_entry_loop(); /* read_data should be equal to 0x223 */ } static int sr_hsr_exit(void) { mmio_write_32(stm32mp_rcc_base() + RCC_DDRCPCFGR, RCC_DDRCPCFGR_DDRCPLPEN | RCC_DDRCPCFGR_DDRCPEN); /* TODO: check if ddr_sr_exit_loop() is needed here */ return 0; } static int sr_asr_set(void) { mmio_write_32(stm32_ddrdbg_get_base() + DDRDBG_LP_DISABLE, 0U); return 0; } static int sr_asr_entry(void) { /* * Automatically enter into self refresh when there is no ddr traffic * for the delay programmed into SYSCONF_DDRC_AUTO_SR_DELAY register. * Default value is 0x20 (unit: Multiples of 32 DFI clock cycles). */ return sr_entry_loop(); } static int sr_asr_exit(void) { return ddr_sr_exit_loop(); } uint32_t ddr_get_io_calibration_val(void) { /* TODO create related service */ return 0U; } int ddr_sr_entry(bool standby) { int ret = -EINVAL; switch (saved_ddr_sr_mode) { case DDR_SSR_MODE: ret = sr_ssr_entry(standby); break; case DDR_HSR_MODE: ret = sr_hsr_entry(); break; case DDR_ASR_MODE: ret = sr_asr_entry(); break; default: break; } return ret; } int ddr_sr_exit(void) { int ret = -EINVAL; switch (saved_ddr_sr_mode) { case DDR_SSR_MODE: ret = sr_ssr_exit(); break; case DDR_HSR_MODE: ret = sr_hsr_exit(); break; case DDR_ASR_MODE: ret = sr_asr_exit(); break; default: break; } return ret; } enum stm32mp2_ddr_sr_mode ddr_read_sr_mode(void) { uint32_t pwrctl = mmio_read_32(stm32mp_ddrctrl_base() + DDRCTRL_PWRCTL); enum stm32mp2_ddr_sr_mode mode = DDR_SR_MODE_INVALID; switch (pwrctl & (DDRCTRL_PWRCTL_EN_DFI_DRAM_CLK_DISABLE | DDRCTRL_PWRCTL_SELFREF_EN)) { case 0U: mode = DDR_SSR_MODE; break; case DDRCTRL_PWRCTL_EN_DFI_DRAM_CLK_DISABLE: mode = DDR_HSR_MODE; break; case DDRCTRL_PWRCTL_EN_DFI_DRAM_CLK_DISABLE | DDRCTRL_PWRCTL_SELFREF_EN: mode = DDR_ASR_MODE; break; default: break; } return mode; } void ddr_set_sr_mode(enum stm32mp2_ddr_sr_mode mode) { int ret = -EINVAL; if (mode == saved_ddr_sr_mode) { return; } switch (mode) { case DDR_SSR_MODE: ret = sr_ssr_set(); break; case DDR_HSR_MODE: ret = sr_hsr_set(); break; case DDR_ASR_MODE: ret = sr_asr_set(); break; default: break; } if (ret != 0) { ERROR("Unknown Self Refresh mode\n"); panic(); } saved_ddr_sr_mode = mode; } void ddr_save_sr_mode(void) { saved_ddr_sr_mode = ddr_read_sr_mode(); } void ddr_restore_sr_mode(void) { ddr_set_sr_mode(saved_ddr_sr_mode); } void ddr_sub_system_clk_init(void) { mmio_write_32(stm32mp_rcc_base() + RCC_DDRCPCFGR, RCC_DDRCPCFGR_DDRCPEN | RCC_DDRCPCFGR_DDRCPLPEN); } void ddr_sub_system_clk_off(void) { uintptr_t rcc_base = stm32mp_rcc_base(); /* Clear DDR IO retention */ mmio_clrbits_32(stm32mp_pwr_base() + PWR_CR11, PWR_CR11_DDRRETDIS); /* Reset DDR sub system */ mmio_write_32(rcc_base + RCC_DDRCPCFGR, RCC_DDRCPCFGR_DDRCPRST); mmio_write_32(rcc_base + RCC_DDRITFCFGR, RCC_DDRITFCFGR_DDRRST); mmio_write_32(rcc_base + RCC_DDRPHYCAPBCFGR, RCC_DDRPHYCAPBCFGR_DDRPHYCAPBRST); mmio_write_32(rcc_base + RCC_DDRCAPBCFGR, RCC_DDRCAPBCFGR_DDRCAPBRST); /* Deactivate clocks and PLL2 */ mmio_clrbits_32(rcc_base + RCC_DDRPHYCCFGR, RCC_DDRPHYCCFGR_DDRPHYCEN); mmio_clrbits_32(rcc_base + RCC_PLL2CFGR1, RCC_PLL2CFGR1_PLLEN); }