| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * clock.c |
| * |
| * Clock initialization for AM33XX boards. |
| * Derived from OMAP4 boards |
| * |
| * Copyright (C) 2013, Texas Instruments, Incorporated - https://www.ti.com/ |
| */ |
| #include <common.h> |
| #include <hang.h> |
| #include <init.h> |
| #include <log.h> |
| #include <asm/arch/cpu.h> |
| #include <asm/arch/clock.h> |
| #include <asm/arch/hardware.h> |
| #include <asm/arch/sys_proto.h> |
| #include <asm/io.h> |
| |
| static void setup_post_dividers(const struct dpll_regs *dpll_regs, |
| const struct dpll_params *params) |
| { |
| /* Setup post-dividers */ |
| if (params->m2 >= 0) |
| writel(params->m2, dpll_regs->cm_div_m2_dpll); |
| if (params->m3 >= 0) |
| writel(params->m3, dpll_regs->cm_div_m3_dpll); |
| if (params->m4 >= 0) |
| writel(params->m4, dpll_regs->cm_div_m4_dpll); |
| if (params->m5 >= 0) |
| writel(params->m5, dpll_regs->cm_div_m5_dpll); |
| if (params->m6 >= 0) |
| writel(params->m6, dpll_regs->cm_div_m6_dpll); |
| } |
| |
| static inline void do_lock_dpll(const struct dpll_regs *dpll_regs) |
| { |
| clrsetbits_le32(dpll_regs->cm_clkmode_dpll, |
| CM_CLKMODE_DPLL_DPLL_EN_MASK, |
| DPLL_EN_LOCK << CM_CLKMODE_DPLL_EN_SHIFT); |
| } |
| |
| static inline void wait_for_lock(const struct dpll_regs *dpll_regs) |
| { |
| if (!wait_on_value(ST_DPLL_CLK_MASK, ST_DPLL_CLK_MASK, |
| (void *)dpll_regs->cm_idlest_dpll, LDELAY)) { |
| printf("DPLL locking failed for 0x%x\n", |
| dpll_regs->cm_clkmode_dpll); |
| hang(); |
| } |
| } |
| |
| static inline void do_bypass_dpll(const struct dpll_regs *dpll_regs) |
| { |
| clrsetbits_le32(dpll_regs->cm_clkmode_dpll, |
| CM_CLKMODE_DPLL_DPLL_EN_MASK, |
| DPLL_EN_MN_BYPASS << CM_CLKMODE_DPLL_EN_SHIFT); |
| } |
| |
| static inline void wait_for_bypass(const struct dpll_regs *dpll_regs) |
| { |
| if (!wait_on_value(ST_DPLL_CLK_MASK, 0, |
| (void *)dpll_regs->cm_idlest_dpll, LDELAY)) { |
| printf("Bypassing DPLL failed 0x%x\n", |
| dpll_regs->cm_clkmode_dpll); |
| } |
| } |
| |
| static void bypass_dpll(const struct dpll_regs *dpll_regs) |
| { |
| do_bypass_dpll(dpll_regs); |
| wait_for_bypass(dpll_regs); |
| } |
| |
| void do_setup_dpll(const struct dpll_regs *dpll_regs, |
| const struct dpll_params *params) |
| { |
| u32 temp; |
| |
| if (!params) |
| return; |
| |
| temp = readl(dpll_regs->cm_clksel_dpll); |
| |
| bypass_dpll(dpll_regs); |
| |
| /* Set M & N */ |
| temp &= ~CM_CLKSEL_DPLL_M_MASK; |
| temp |= (params->m << CM_CLKSEL_DPLL_M_SHIFT) & CM_CLKSEL_DPLL_M_MASK; |
| |
| temp &= ~CM_CLKSEL_DPLL_N_MASK; |
| temp |= (params->n << CM_CLKSEL_DPLL_N_SHIFT) & CM_CLKSEL_DPLL_N_MASK; |
| |
| writel(temp, dpll_regs->cm_clksel_dpll); |
| |
| setup_post_dividers(dpll_regs, params); |
| |
| /* Wait till the DPLL locks */ |
| do_lock_dpll(dpll_regs); |
| wait_for_lock(dpll_regs); |
| } |
| |
| static void setup_dplls(void) |
| { |
| const struct dpll_params *params; |
| |
| params = get_dpll_core_params(); |
| do_setup_dpll(&dpll_core_regs, params); |
| |
| params = get_dpll_mpu_params(); |
| do_setup_dpll(&dpll_mpu_regs, params); |
| |
| params = get_dpll_per_params(); |
| do_setup_dpll(&dpll_per_regs, params); |
| writel(0x300, &cmwkup->clkdcoldodpllper); |
| |
| params = get_dpll_ddr_params(); |
| do_setup_dpll(&dpll_ddr_regs, params); |
| } |
| |
| static inline void wait_for_clk_enable(u32 *clkctrl_addr) |
| { |
| u32 clkctrl, idlest = MODULE_CLKCTRL_IDLEST_DISABLED; |
| u32 bound = LDELAY; |
| |
| while ((idlest == MODULE_CLKCTRL_IDLEST_DISABLED) || |
| (idlest == MODULE_CLKCTRL_IDLEST_TRANSITIONING)) { |
| clkctrl = readl(clkctrl_addr); |
| idlest = (clkctrl & MODULE_CLKCTRL_IDLEST_MASK) >> |
| MODULE_CLKCTRL_IDLEST_SHIFT; |
| if (--bound == 0) { |
| printf("Clock enable failed for 0x%p idlest 0x%x\n", |
| clkctrl_addr, clkctrl); |
| return; |
| } |
| } |
| } |
| |
| static inline void enable_clock_module(u32 *const clkctrl_addr, u32 enable_mode, |
| u32 wait_for_enable) |
| { |
| clrsetbits_le32(clkctrl_addr, MODULE_CLKCTRL_MODULEMODE_MASK, |
| enable_mode << MODULE_CLKCTRL_MODULEMODE_SHIFT); |
| debug("Enable clock module - %p\n", clkctrl_addr); |
| if (wait_for_enable) |
| wait_for_clk_enable(clkctrl_addr); |
| } |
| |
| static inline void wait_for_clk_disable(u32 *clkctrl_addr) |
| { |
| u32 clkctrl, idlest = MODULE_CLKCTRL_IDLEST_FULLY_FUNCTIONAL; |
| u32 bound = LDELAY; |
| |
| while ((idlest != MODULE_CLKCTRL_IDLEST_DISABLED)) { |
| clkctrl = readl(clkctrl_addr); |
| idlest = (clkctrl & MODULE_CLKCTRL_IDLEST_MASK) >> |
| MODULE_CLKCTRL_IDLEST_SHIFT; |
| if (--bound == 0) { |
| printf("Clock disable failed for 0x%p idlest 0x%x\n", |
| clkctrl_addr, clkctrl); |
| return; |
| } |
| } |
| } |
| static inline void disable_clock_module(u32 *const clkctrl_addr, |
| u32 wait_for_disable) |
| { |
| clrsetbits_le32(clkctrl_addr, MODULE_CLKCTRL_MODULEMODE_MASK, |
| MODULE_CLKCTRL_MODULEMODE_SW_DISABLE << |
| MODULE_CLKCTRL_MODULEMODE_SHIFT); |
| debug("Disable clock module - %p\n", clkctrl_addr); |
| if (wait_for_disable) |
| wait_for_clk_disable(clkctrl_addr); |
| } |
| |
| static inline void enable_clock_domain(u32 *const clkctrl_reg, u32 enable_mode) |
| { |
| clrsetbits_le32(clkctrl_reg, CD_CLKCTRL_CLKTRCTRL_MASK, |
| enable_mode << CD_CLKCTRL_CLKTRCTRL_SHIFT); |
| debug("Enable clock domain - %p\n", clkctrl_reg); |
| } |
| |
| static inline void disable_clock_domain(u32 *const clkctrl_reg) |
| { |
| clrsetbits_le32(clkctrl_reg, CD_CLKCTRL_CLKTRCTRL_MASK, |
| CD_CLKCTRL_CLKTRCTRL_SW_SLEEP << |
| CD_CLKCTRL_CLKTRCTRL_SHIFT); |
| debug("Disable clock domain - %p\n", clkctrl_reg); |
| } |
| |
| void do_enable_clocks(u32 *const *clk_domains, |
| u32 *const *clk_modules_explicit_en, u8 wait_for_enable) |
| { |
| u32 i, max = 100; |
| |
| /* Put the clock domains in SW_WKUP mode */ |
| for (i = 0; (i < max) && clk_domains && clk_domains[i]; i++) { |
| enable_clock_domain(clk_domains[i], |
| CD_CLKCTRL_CLKTRCTRL_SW_WKUP); |
| } |
| |
| /* Clock modules that need to be put in SW_EXPLICIT_EN mode */ |
| for (i = 0; (i < max) && clk_modules_explicit_en && |
| clk_modules_explicit_en[i]; i++) { |
| enable_clock_module(clk_modules_explicit_en[i], |
| MODULE_CLKCTRL_MODULEMODE_SW_EXPLICIT_EN, |
| wait_for_enable); |
| }; |
| } |
| |
| void do_disable_clocks(u32 *const *clk_domains, |
| u32 *const *clk_modules_disable, |
| u8 wait_for_disable) |
| { |
| u32 i, max = 100; |
| |
| |
| /* Clock modules that need to be put in SW_DISABLE */ |
| for (i = 0; (i < max) && clk_modules_disable && clk_modules_disable[i]; |
| i++) |
| disable_clock_module(clk_modules_disable[i], |
| wait_for_disable); |
| |
| /* Put the clock domains in SW_SLEEP mode */ |
| for (i = 0; (i < max) && clk_domains && clk_domains[i]; i++) |
| disable_clock_domain(clk_domains[i]); |
| } |
| |
| /* |
| * Before scaling up the clocks we need to have the PMIC scale up the |
| * voltages first. This will be dependent on which PMIC is in use |
| * and in some cases we may not be scaling things up at all and thus not |
| * need to do anything here. |
| */ |
| __weak void scale_vcores(void) |
| { |
| } |
| |
| void setup_early_clocks(void) |
| { |
| setup_clocks_for_console(); |
| enable_basic_clocks(); |
| timer_init(); |
| } |
| |
| void prcm_init(void) |
| { |
| scale_vcores(); |
| setup_dplls(); |
| } |
| |
| void rtc_only_prcm_init(void) |
| { |
| const struct dpll_params *params; |
| |
| rtc_only_enable_basic_clocks(); |
| |
| params = get_dpll_ddr_params(); |
| do_setup_dpll(&dpll_ddr_regs, params); |
| } |