| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright 2018-2019 NXP |
| * |
| * Peng Fan <peng.fan@nxp.com> |
| */ |
| |
| #include <common.h> |
| #include <asm/arch/clock.h> |
| #include <asm/arch/imx-regs.h> |
| #include <asm/arch/sys_proto.h> |
| #include <asm/io.h> |
| #include <clk.h> |
| #include <clk-uclass.h> |
| #include <dt-bindings/clock/imx8mm-clock.h> |
| #include <div64.h> |
| #include <errno.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| static struct anamix_pll *ana_pll = (struct anamix_pll *)ANATOP_BASE_ADDR; |
| |
| void enable_ocotp_clk(unsigned char enable) |
| { |
| struct clk *clkp; |
| int ret; |
| |
| ret = clk_get_by_id(IMX8MM_CLK_OCOTP_ROOT, &clkp); |
| if (ret) { |
| printf("%s: err: %d\n", __func__, ret); |
| return; |
| } |
| |
| enable ? clk_enable(clkp) : clk_disable(clkp); |
| } |
| |
| int enable_i2c_clk(unsigned char enable, unsigned i2c_num) |
| { |
| struct clk *clkp; |
| int ret; |
| |
| ret = clk_get_by_id(IMX8MM_CLK_I2C1_ROOT + i2c_num, &clkp); |
| if (ret) { |
| printf("%s: err: %d\n", __func__, ret); |
| return ret; |
| } |
| |
| return enable ? clk_enable(clkp) : clk_disable(clkp); |
| } |
| |
| #ifdef CONFIG_SPL_BUILD |
| static struct imx_int_pll_rate_table imx8mm_fracpll_tbl[] = { |
| PLL_1443X_RATE(800000000U, 300, 9, 0, 0), |
| PLL_1443X_RATE(750000000U, 250, 8, 0, 0), |
| PLL_1443X_RATE(650000000U, 325, 3, 2, 0), |
| PLL_1443X_RATE(600000000U, 300, 3, 2, 0), |
| PLL_1443X_RATE(594000000U, 99, 1, 2, 0), |
| PLL_1443X_RATE(400000000U, 300, 9, 1, 0), |
| PLL_1443X_RATE(266666667U, 400, 9, 2, 0), |
| PLL_1443X_RATE(167000000U, 334, 3, 4, 0), |
| PLL_1443X_RATE(100000000U, 300, 9, 3, 0), |
| }; |
| |
| int fracpll_configure(enum pll_clocks pll, u32 freq) |
| { |
| int i; |
| u32 tmp, div_val; |
| void *pll_base; |
| struct imx_int_pll_rate_table *rate; |
| |
| for (i = 0; i < ARRAY_SIZE(imx8mm_fracpll_tbl); i++) { |
| if (freq == imx8mm_fracpll_tbl[i].rate) |
| break; |
| } |
| |
| if (i == ARRAY_SIZE(imx8mm_fracpll_tbl)) { |
| printf("No matched freq table %u\n", freq); |
| return -EINVAL; |
| } |
| |
| rate = &imx8mm_fracpll_tbl[i]; |
| |
| switch (pll) { |
| case ANATOP_DRAM_PLL: |
| setbits_le32(GPC_BASE_ADDR + 0xEC, 1 << 7); |
| setbits_le32(GPC_BASE_ADDR + 0xF8, 1 << 5); |
| writel(SRC_DDR1_ENABLE_MASK, SRC_BASE_ADDR + 0x1004); |
| |
| pll_base = &ana_pll->dram_pll_gnrl_ctl; |
| break; |
| case ANATOP_VIDEO_PLL: |
| pll_base = &ana_pll->video_pll1_gnrl_ctl; |
| break; |
| default: |
| return 0; |
| } |
| /* Bypass clock and set lock to pll output lock */ |
| tmp = readl(pll_base); |
| tmp |= BYPASS_MASK; |
| writel(tmp, pll_base); |
| |
| /* Enable RST */ |
| tmp &= ~RST_MASK; |
| writel(tmp, pll_base); |
| |
| div_val = (rate->mdiv << MDIV_SHIFT) | (rate->pdiv << PDIV_SHIFT) | |
| (rate->sdiv << SDIV_SHIFT); |
| writel(div_val, pll_base + 4); |
| writel(rate->kdiv << KDIV_SHIFT, pll_base + 8); |
| |
| __udelay(100); |
| |
| /* Disable RST */ |
| tmp |= RST_MASK; |
| writel(tmp, pll_base); |
| |
| /* Wait Lock*/ |
| while (!(readl(pll_base) & LOCK_STATUS)) |
| ; |
| |
| /* Bypass */ |
| tmp &= ~BYPASS_MASK; |
| writel(tmp, pll_base); |
| |
| return 0; |
| } |
| |
| void dram_pll_init(ulong pll_val) |
| { |
| fracpll_configure(ANATOP_DRAM_PLL, pll_val); |
| } |
| |
| static struct dram_bypass_clk_setting imx8mm_dram_bypass_tbl[] = { |
| DRAM_BYPASS_ROOT_CONFIG(MHZ(100), 2, CLK_ROOT_PRE_DIV1, 2, |
| CLK_ROOT_PRE_DIV2), |
| DRAM_BYPASS_ROOT_CONFIG(MHZ(250), 3, CLK_ROOT_PRE_DIV2, 2, |
| CLK_ROOT_PRE_DIV2), |
| DRAM_BYPASS_ROOT_CONFIG(MHZ(400), 1, CLK_ROOT_PRE_DIV2, 3, |
| CLK_ROOT_PRE_DIV2), |
| }; |
| |
| void dram_enable_bypass(ulong clk_val) |
| { |
| int i; |
| struct dram_bypass_clk_setting *config; |
| |
| for (i = 0; i < ARRAY_SIZE(imx8mm_dram_bypass_tbl); i++) { |
| if (clk_val == imx8mm_dram_bypass_tbl[i].clk) |
| break; |
| } |
| |
| if (i == ARRAY_SIZE(imx8mm_dram_bypass_tbl)) { |
| printf("No matched freq table %lu\n", clk_val); |
| return; |
| } |
| |
| config = &imx8mm_dram_bypass_tbl[i]; |
| |
| clock_set_target_val(DRAM_ALT_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(config->alt_root_sel) | |
| CLK_ROOT_PRE_DIV(config->alt_pre_div)); |
| clock_set_target_val(DRAM_APB_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(config->apb_root_sel) | |
| CLK_ROOT_PRE_DIV(config->apb_pre_div)); |
| clock_set_target_val(DRAM_SEL_CFG, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(1)); |
| } |
| |
| void dram_disable_bypass(void) |
| { |
| clock_set_target_val(DRAM_SEL_CFG, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(0)); |
| clock_set_target_val(DRAM_APB_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(4) | |
| CLK_ROOT_PRE_DIV(CLK_ROOT_PRE_DIV5)); |
| } |
| #endif |
| |
| void init_uart_clk(u32 index) |
| { |
| /* |
| * set uart clock root |
| * 24M OSC |
| */ |
| switch (index) { |
| case 0: |
| clock_enable(CCGR_UART1, 0); |
| clock_set_target_val(UART1_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(0)); |
| clock_enable(CCGR_UART1, 1); |
| return; |
| case 1: |
| clock_enable(CCGR_UART2, 0); |
| clock_set_target_val(UART2_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(0)); |
| clock_enable(CCGR_UART2, 1); |
| return; |
| case 2: |
| clock_enable(CCGR_UART3, 0); |
| clock_set_target_val(UART3_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(0)); |
| clock_enable(CCGR_UART3, 1); |
| return; |
| case 3: |
| clock_enable(CCGR_UART4, 0); |
| clock_set_target_val(UART4_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(0)); |
| clock_enable(CCGR_UART4, 1); |
| return; |
| default: |
| printf("Invalid uart index\n"); |
| return; |
| } |
| } |
| |
| void init_wdog_clk(void) |
| { |
| clock_enable(CCGR_WDOG1, 0); |
| clock_enable(CCGR_WDOG2, 0); |
| clock_enable(CCGR_WDOG3, 0); |
| clock_set_target_val(WDOG_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(0)); |
| clock_enable(CCGR_WDOG1, 1); |
| clock_enable(CCGR_WDOG2, 1); |
| clock_enable(CCGR_WDOG3, 1); |
| } |
| |
| int clock_init(void) |
| { |
| u32 val_cfg0; |
| |
| /* |
| * The gate is not exported to clk tree, so configure them here. |
| * According to ANAMIX SPEC |
| * sys pll1 fixed at 800MHz |
| * sys pll2 fixed at 1GHz |
| * Here we only enable the outputs. |
| */ |
| val_cfg0 = readl(&ana_pll->sys_pll1_gnrl_ctl); |
| val_cfg0 |= INTPLL_CLKE_MASK | INTPLL_DIV2_CLKE_MASK | |
| INTPLL_DIV3_CLKE_MASK | INTPLL_DIV4_CLKE_MASK | |
| INTPLL_DIV5_CLKE_MASK | INTPLL_DIV6_CLKE_MASK | |
| INTPLL_DIV8_CLKE_MASK | INTPLL_DIV10_CLKE_MASK | |
| INTPLL_DIV20_CLKE_MASK; |
| writel(val_cfg0, &ana_pll->sys_pll1_gnrl_ctl); |
| |
| val_cfg0 = readl(&ana_pll->sys_pll2_gnrl_ctl); |
| val_cfg0 |= INTPLL_CLKE_MASK | INTPLL_DIV2_CLKE_MASK | |
| INTPLL_DIV3_CLKE_MASK | INTPLL_DIV4_CLKE_MASK | |
| INTPLL_DIV5_CLKE_MASK | INTPLL_DIV6_CLKE_MASK | |
| INTPLL_DIV8_CLKE_MASK | INTPLL_DIV10_CLKE_MASK | |
| INTPLL_DIV20_CLKE_MASK; |
| writel(val_cfg0, &ana_pll->sys_pll2_gnrl_ctl); |
| |
| /* config GIC to sys_pll2_100m */ |
| clock_enable(CCGR_GIC, 0); |
| clock_set_target_val(GIC_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(3)); |
| clock_enable(CCGR_GIC, 1); |
| |
| clock_set_target_val(NAND_USDHC_BUS_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(1)); |
| |
| clock_enable(CCGR_DDR1, 0); |
| clock_set_target_val(DRAM_ALT_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(1)); |
| clock_set_target_val(DRAM_APB_CLK_ROOT, CLK_ROOT_ON | |
| CLK_ROOT_SOURCE_SEL(1)); |
| clock_enable(CCGR_DDR1, 1); |
| |
| init_wdog_clk(); |
| |
| clock_enable(CCGR_TEMP_SENSOR, 1); |
| |
| clock_enable(CCGR_SEC_DEBUG, 1); |
| |
| return 0; |
| }; |
| |
| u32 imx_get_uartclk(void) |
| { |
| return 24000000U; |
| } |
| |
| u32 mxc_get_clock(enum mxc_clock clk) |
| { |
| struct clk *clkp; |
| int ret; |
| |
| switch (clk) { |
| case MXC_IPG_CLK: |
| ret = clk_get_by_id(IMX8MM_CLK_IPG_ROOT, &clkp); |
| if (ret) |
| return 0; |
| return clk_get_rate(clkp); |
| case MXC_ARM_CLK: |
| ret = clk_get_by_id(IMX8MM_CLK_A53_DIV, &clkp); |
| if (ret) |
| return 0; |
| return clk_get_rate(clkp); |
| default: |
| printf("%s: %d not supported\n", __func__, clk); |
| } |
| |
| return 0; |
| } |