diff options
Diffstat (limited to 'arch/arm/mach-imx/imx9')
-rw-r--r-- | arch/arm/mach-imx/imx9/Kconfig | 35 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/Makefile | 10 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/clock.c | 882 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/clock_root.c | 450 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/imx_bootaux.c | 138 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/lowlevel_init.S | 26 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/soc.c | 614 | ||||
-rw-r--r-- | arch/arm/mach-imx/imx9/trdc.c | 593 |
8 files changed, 2748 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/imx9/Kconfig b/arch/arm/mach-imx/imx9/Kconfig new file mode 100644 index 0000000000..c709448411 --- /dev/null +++ b/arch/arm/mach-imx/imx9/Kconfig @@ -0,0 +1,35 @@ +if ARCH_IMX9 + +config AHAB_BOOT + bool "Support i.MX9 AHAB features" + help + This option enables the support for AHAB secure boot. + +config IMX9 + bool + select ARCH_EARLY_INIT_R + select HAS_CAAM + select ROM_UNIFIED_SECTIONS + +config IMX93 + bool + select IMX9 + select ARMV8_SPL_EXCEPTION_VECTORS + +config SYS_SOC + default "imx9" + +choice + prompt "NXP i.MX9 board select" + optional + +config TARGET_IMX93_11X11_EVK + bool "imx93_11x11_evk" + select IMX93 + +endchoice + +source "board/freescale/imx93_evk/Kconfig" + +endif + diff --git a/arch/arm/mach-imx/imx9/Makefile b/arch/arm/mach-imx/imx9/Makefile new file mode 100644 index 0000000000..e1b09ab534 --- /dev/null +++ b/arch/arm/mach-imx/imx9/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2022 NXP + +obj-y += lowlevel_init.o +obj-y += soc.o clock.o clock_root.o trdc.o + +#ifndef CONFIG_SPL_BUILD +obj-y += imx_bootaux.o +#endif diff --git a/arch/arm/mach-imx/imx9/clock.c b/arch/arm/mach-imx/imx9/clock.c new file mode 100644 index 0000000000..7dc33941a9 --- /dev/null +++ b/arch/arm/mach-imx/imx9/clock.c @@ -0,0 +1,882 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + * + * Peng Fan <peng.fan@nxp.com> + */ + +#include <common.h> +#include <command.h> +#include <asm/arch/clock.h> +#include <asm/arch/imx-regs.h> +#include <asm/arch/ccm_regs.h> +#include <asm/arch/sys_proto.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <div64.h> +#include <errno.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <log.h> + +DECLARE_GLOBAL_DATA_PTR; + +static struct anatop_reg *ana_regs = (struct anatop_reg *)ANATOP_BASE_ADDR; + +static struct imx_intpll_rate_table imx9_intpll_tbl[] = { + INT_PLL_RATE(1800000000U, 1, 150, 2), /* 1.8Ghz */ + INT_PLL_RATE(1700000000U, 1, 141, 2), /* 1.7Ghz */ + INT_PLL_RATE(1400000000U, 1, 175, 3), /* 1.4Ghz */ + INT_PLL_RATE(1000000000U, 1, 166, 4), /* 1000Mhz */ + INT_PLL_RATE(900000000U, 1, 150, 4), /* 900Mhz */ +}; + +static struct imx_fracpll_rate_table imx9_fracpll_tbl[] = { + FRAC_PLL_RATE(1000000000U, 1, 166, 4, 2, 3), /* 1000Mhz */ + FRAC_PLL_RATE(933000000U, 1, 155, 4, 1, 2), /* 933Mhz */ + FRAC_PLL_RATE(700000000U, 1, 145, 5, 5, 6), /* 700Mhz */ + FRAC_PLL_RATE(484000000U, 1, 121, 6, 0, 1), + FRAC_PLL_RATE(445333333U, 1, 167, 9, 0, 1), + FRAC_PLL_RATE(466000000U, 1, 155, 8, 1, 3), /* 466Mhz */ + FRAC_PLL_RATE(400000000U, 1, 200, 12, 0, 1), /* 400Mhz */ +}; + +/* return in khz */ +static u32 decode_pll_vco(struct ana_pll_reg *reg, bool fracpll) +{ + u32 ctrl; + u32 pll_status; + u32 div; + int rdiv, mfi, mfn, mfd; + int clk = 24000; + + ctrl = readl(®->ctrl.reg); + pll_status = readl(®->pll_status); + div = readl(®->div.reg); + + if (!(ctrl & PLL_CTRL_POWERUP)) + return 0; + + if (!(pll_status & PLL_STATUS_PLL_LOCK)) + return 0; + + mfi = (div & GENMASK(24, 16)) >> 16; + rdiv = (div & GENMASK(15, 13)) >> 13; + + if (rdiv == 0) + rdiv = 1; + + if (fracpll) { + mfn = (int)readl(®->num.reg); + mfn >>= 2; + mfd = (int)(readl(®->denom.reg) & GENMASK(29, 0)); + + clk = clk * (mfi * mfd + mfn) / mfd / rdiv; + } else { + clk = clk * mfi / rdiv; + } + + return (u32)clk; +} + +/* return in khz */ +static u32 decode_pll_out(struct ana_pll_reg *reg, bool fracpll) +{ + u32 ctrl = readl(®->ctrl.reg); + u32 div; + + if (ctrl & PLL_CTRL_CLKMUX_BYPASS) + return 24000; + + if (!(ctrl & PLL_CTRL_CLKMUX_EN)) + return 0; + + div = readl(®->div.reg); + div &= 0xff; /* odiv */ + + if (div == 0) + div = 2; + else if (div == 1) + div = 3; + + return decode_pll_vco(reg, fracpll) / div; +} + +/* return in khz */ +static u32 decode_pll_pfd(struct ana_pll_reg *reg, + struct ana_pll_dfs *dfs_reg, bool div2, bool fracpll) +{ + u32 pllvco = decode_pll_vco(reg, fracpll); + u32 dfs_ctrl = readl(&dfs_reg->dfs_ctrl.reg); + u32 dfs_div = readl(&dfs_reg->dfs_div.reg); + u32 mfn, mfi; + u32 output; + + if (dfs_ctrl & PLL_DFS_CTRL_BYPASS) + return pllvco; + + if (!(dfs_ctrl & PLL_DFS_CTRL_ENABLE) || + (div2 && !(dfs_ctrl & PLL_DFS_CTRL_CLKOUT_DIV2)) || + (!div2 && !(dfs_ctrl & PLL_DFS_CTRL_CLKOUT))) + return 0; + + mfn = dfs_div & GENMASK(2, 0); + mfi = (dfs_div & GENMASK(15, 8)) >> 8; + + if (mfn > 3) + return 0; /* valid mfn 0-3 */ + + if (mfi == 0 || mfi == 1) + return 0; /* valid mfi 2-255 */ + + output = (pllvco * 5) / (mfi * 5 + mfn); + + if (div2) + return output >> 1; + + return output; +} + +static u32 decode_pll(enum ccm_clk_src pll) +{ + switch (pll) { + case ARM_PLL_CLK: + return decode_pll_out(&ana_regs->arm_pll, false); + case SYS_PLL_PG: + return decode_pll_out(&ana_regs->sys_pll, false); + case SYS_PLL_PFD0: + return decode_pll_pfd(&ana_regs->sys_pll, + &ana_regs->sys_pll.dfs[0], false, true); + case SYS_PLL_PFD0_DIV2: + return decode_pll_pfd(&ana_regs->sys_pll, + &ana_regs->sys_pll.dfs[0], true, true); + case SYS_PLL_PFD1: + return decode_pll_pfd(&ana_regs->sys_pll, + &ana_regs->sys_pll.dfs[1], false, true); + case SYS_PLL_PFD1_DIV2: + return decode_pll_pfd(&ana_regs->sys_pll, + &ana_regs->sys_pll.dfs[1], true, true); + case SYS_PLL_PFD2: + return decode_pll_pfd(&ana_regs->sys_pll, + &ana_regs->sys_pll.dfs[2], false, true); + case SYS_PLL_PFD2_DIV2: + return decode_pll_pfd(&ana_regs->sys_pll, + &ana_regs->sys_pll.dfs[2], true, true); + case AUDIO_PLL_CLK: + return decode_pll_out(&ana_regs->audio_pll, true); + case DRAM_PLL_CLK: + return decode_pll_out(&ana_regs->dram_pll, true); + case VIDEO_PLL_CLK: + return decode_pll_out(&ana_regs->video_pll, true); + default: + printf("Invalid clock source to decode\n"); + break; + } + + return 0; +} + +int configure_intpll(enum ccm_clk_src pll, u32 freq) +{ + int i; + struct imx_intpll_rate_table *rate; + struct ana_pll_reg *reg; + u32 pll_status; + + for (i = 0; i < ARRAY_SIZE(imx9_intpll_tbl); i++) { + if (freq == imx9_intpll_tbl[i].rate) + break; + } + + if (i == ARRAY_SIZE(imx9_intpll_tbl)) { + debug("No matched freq table %u\n", freq); + return -EINVAL; + } + + rate = &imx9_intpll_tbl[i]; + + /* ROM has configured SYS PLL and PFD, no need for it */ + switch (pll) { + case ARM_PLL_CLK: + reg = &ana_regs->arm_pll; + break; + default: + return -EPERM; + } + + /* Bypass the PLL to ref */ + writel(PLL_CTRL_CLKMUX_BYPASS, ®->ctrl.reg_set); + + /* disable pll and output */ + writel(PLL_CTRL_CLKMUX_EN | PLL_CTRL_POWERUP, ®->ctrl.reg_clr); + + /* Program the ODIV, RDIV, MFI */ + writel((rate->odiv & GENMASK(7, 0)) | + ((rate->rdiv << 13 ) & GENMASK(15, 13)) | + ((rate->mfi << 16) & GENMASK(24, 16)), ®->div.reg); + +#ifndef CONFIG_TARGET_IMX93_EMU + /* wait 5us */ + udelay(5); +#endif + + /* power up the PLL and wait lock (max wait time 100 us) */ + writel(PLL_CTRL_POWERUP, ®->ctrl.reg_set); + +#ifndef CONFIG_TARGET_IMX93_EMU + udelay(100); +#endif + + pll_status = readl(®->pll_status); + if (pll_status & PLL_STATUS_PLL_LOCK) { + writel(PLL_CTRL_CLKMUX_EN, ®->ctrl.reg_set); + + /* clear bypass */ + writel(PLL_CTRL_CLKMUX_BYPASS, ®->ctrl.reg_clr); + + } else { + debug("Fail to lock PLL %u\n", pll); + return -EIO; + } + + return 0; +} + + +int configure_fracpll(enum ccm_clk_src pll, u32 freq) +{ + int i; + struct imx_fracpll_rate_table *rate; + struct ana_pll_reg *reg; + u32 pll_status; + + for (i = 0; i < ARRAY_SIZE(imx9_fracpll_tbl); i++) { + if (freq == imx9_fracpll_tbl[i].rate) + break; + } + + if (i == ARRAY_SIZE(imx9_fracpll_tbl)) { + debug("No matched freq table %u\n", freq); + return -EINVAL; + } + + rate = &imx9_fracpll_tbl[i]; + + switch (pll) { + case SYS_PLL_PG: + reg = &ana_regs->sys_pll; + break; + case DRAM_PLL_CLK: + reg = &ana_regs->dram_pll; + break; + case VIDEO_PLL_CLK: + reg = &ana_regs->video_pll; + break; + default: + return -EPERM; + } + + /* Bypass the PLL to ref */ + writel(PLL_CTRL_CLKMUX_BYPASS, ®->ctrl.reg_set); + + /* disable pll and output */ + writel(PLL_CTRL_CLKMUX_EN | PLL_CTRL_POWERUP, ®->ctrl.reg_clr); + + /* Program the ODIV, RDIV, MFI */ + writel((rate->odiv & GENMASK(7, 0)) | + ((rate->rdiv << 13 ) & GENMASK(15, 13)) | + ((rate->mfi << 16) & GENMASK(24, 16)), ®->div.reg); + + /* Set SPREAD_SPECRUM enable to 0 */ + writel(PLL_SS_EN, ®->ss.reg_clr); + + /* Program NUMERATOR and DENOMINATOR */ + writel((rate->mfn << 2), ®->num.reg); + writel((rate->mfd & GENMASK(29, 0)), ®->denom.reg); + +#ifndef CONFIG_TARGET_IMX93_EMU + /* wait 5us */ + udelay(5); +#endif + + /* power up the PLL and wait lock (max wait time 100 us) */ + writel(PLL_CTRL_POWERUP, ®->ctrl.reg_set); + +#ifndef CONFIG_TARGET_IMX93_EMU + udelay(100); +#endif + + pll_status = readl(®->pll_status); + if (pll_status & PLL_STATUS_PLL_LOCK) { + writel(PLL_CTRL_CLKMUX_EN, ®->ctrl.reg_set); + +#ifndef CONFIG_TARGET_IMX93_EMU + /* check the MFN is updated */ + pll_status = readl(®->pll_status); + if ((pll_status & ~0x3) != (rate->mfn << 2)) { + debug("MFN update not matched, pll_status 0x%x, mfn 0x%x\n", + pll_status, rate->mfn); + return -EIO; + } +#endif + /* clear bypass */ + writel(PLL_CTRL_CLKMUX_BYPASS, ®->ctrl.reg_clr); + + } else { + debug("Fail to lock PLL %u\n", pll); + return -EIO; + } + + return 0; +} + +int configure_pll_pfd(enum ccm_clk_src pll_pfg, u32 mfi, u32 mfn, bool div2_en) +{ + struct ana_pll_dfs *dfs; + struct ana_pll_reg *reg; + u32 dfs_status; + u32 index; + + if (mfn > 3) + return -EINVAL; /* valid mfn 0-3 */ + + if (mfi < 2 || mfi > 255) + return -EINVAL; /* valid mfi 2-255 */ + + switch (pll_pfg) { + case SYS_PLL_PFD0: + reg = &ana_regs->sys_pll; + index = 0; + break; + case SYS_PLL_PFD1: + reg = &ana_regs->sys_pll; + index = 1; + break; + case SYS_PLL_PFD2: + reg = &ana_regs->sys_pll; + index = 2; + break; + default: + return -EPERM; + } + + dfs = ®->dfs[index]; + + /* Bypass the DFS to PLL VCO */ + writel(PLL_DFS_CTRL_BYPASS, &dfs->dfs_ctrl.reg_set); + + /* disable DFS and output */ + writel(PLL_DFS_CTRL_ENABLE | PLL_DFS_CTRL_CLKOUT | + PLL_DFS_CTRL_CLKOUT_DIV2, &dfs->dfs_ctrl.reg_clr); + + writel(((mfi << 8) & GENMASK(15, 8)) | (mfn & GENMASK(2, 0)), + &dfs->dfs_div.reg); + + writel(PLL_DFS_CTRL_CLKOUT, &dfs->dfs_ctrl.reg_set); + if (div2_en) + writel(PLL_DFS_CTRL_CLKOUT_DIV2, &dfs->dfs_ctrl.reg_set); + writel(PLL_DFS_CTRL_ENABLE, &dfs->dfs_ctrl.reg_set); + +#ifndef CONFIG_TARGET_IMX93_EMU + /* + * As HW expert said: after enabling the DFS, clock will start + * coming after 6 cycles output clock period. + * 5us is much bigger than expected, so it will be safe + */ + udelay(5); +#endif + + dfs_status = readl(®->dfs_status); + + if (!(dfs_status & (1 << index))) { + debug("DFS lock failed\n"); + return -EIO; + } + + /* Bypass the DFS to PLL VCO */ + writel(PLL_DFS_CTRL_BYPASS, &dfs->dfs_ctrl.reg_clr); + + return 0; +} + +int update_fracpll_mfn(enum ccm_clk_src pll, int mfn) +{ + struct ana_pll_reg *reg; + bool repoll = false; + u32 pll_status; + int count = 20; + + switch (pll) { + case AUDIO_PLL_CLK: + reg = &ana_regs->audio_pll; + break; + case DRAM_PLL_CLK: + reg = &ana_regs->dram_pll; + break; + case VIDEO_PLL_CLK: + reg = &ana_regs->video_pll; + break; + default: + printf("Invalid pll %u for update FRAC PLL MFN\n", pll); + return -EINVAL; + } + + if (readl(®->pll_status) & PLL_STATUS_PLL_LOCK) + repoll = true; + + mfn <<= 2; + writel(mfn, ®->num); + + if (repoll) { + do { + pll_status = readl(®->pll_status); + udelay(5); + count--; + } while (((pll_status & ~0x3) != (u32)mfn) && count > 0); + + if (count <= 0) { + printf("update MFN timeout, pll_status 0x%x, mfn 0x%x\n", + pll_status, mfn); + return -EIO; + } + } + + return 0; +} + +int update_pll_pfd_mfn(enum ccm_clk_src pll_pfd, u32 mfn) +{ + struct ana_pll_dfs *dfs; + u32 val; + u32 index; + + switch (pll_pfd) { + case SYS_PLL_PFD0: + case SYS_PLL_PFD0_DIV2: + index = 0; + break; + case SYS_PLL_PFD1: + case SYS_PLL_PFD1_DIV2: + index = 1; + break; + case SYS_PLL_PFD2: + case SYS_PLL_PFD2_DIV2: + index = 2; + break; + default: + printf("Invalid pfd %u for update PLL PFD MFN\n", pll_pfd); + return -EINVAL; + } + + dfs = &ana_regs->sys_pll.dfs[index]; + + val = readl(&dfs->dfs_div.reg); + val &= ~0x3; + val |= mfn & 0x3; + writel(val, &dfs->dfs_div.reg); + + return 0; +} + +/* return in khz */ +u32 get_clk_src_rate(enum ccm_clk_src source) +{ + u32 ctrl; + bool clk_on; + + switch (source) { + case ARM_PLL_CLK: + ctrl = readl(&ana_regs->arm_pll.ctrl.reg); + case AUDIO_PLL_CLK: + ctrl = readl(&ana_regs->audio_pll.ctrl.reg); + break; + case DRAM_PLL_CLK: + ctrl = readl(&ana_regs->dram_pll.ctrl.reg); + break; + case VIDEO_PLL_CLK: + ctrl = readl(&ana_regs->video_pll.ctrl.reg); + break; + case SYS_PLL_PFD0: + case SYS_PLL_PFD0_DIV2: + ctrl = readl(&ana_regs->sys_pll.dfs[0].dfs_ctrl.reg); + break; + case SYS_PLL_PFD1: + case SYS_PLL_PFD1_DIV2: + ctrl = readl(&ana_regs->sys_pll.dfs[1].dfs_ctrl.reg); + break; + case SYS_PLL_PFD2: + case SYS_PLL_PFD2_DIV2: + ctrl = readl(&ana_regs->sys_pll.dfs[2].dfs_ctrl.reg); + break; + case OSC_24M_CLK: + return 24000; + default: + printf("Invalid clock source to get rate\n"); + return 0; + } + + if (ctrl & PLL_CTRL_HW_CTRL_SEL) { + /* When using HW ctrl, check OSCPLL */ + clk_on = ccm_clk_src_is_clk_on(source); + if (clk_on) + return decode_pll(source); + else + return 0; + } else { + /* controlled by pll registers */ + return decode_pll(source); + } +} + +u32 get_arm_core_clk(void) +{ + u32 val; + ccm_shared_gpr_get(SHARED_GPR_A55_CLK, &val); + + if (val & SHARED_GPR_A55_CLK_SEL_PLL) + return decode_pll(ARM_PLL_CLK) * 1000; + + return ccm_clk_root_get_rate(ARM_A55_CLK_ROOT); +} + +unsigned int mxc_get_clock(enum mxc_clock clk) +{ + switch (clk) { + case MXC_ARM_CLK: + return get_arm_core_clk(); + case MXC_IPG_CLK: + return ccm_clk_root_get_rate(BUS_WAKEUP_CLK_ROOT); + case MXC_CSPI_CLK: + return ccm_clk_root_get_rate(LPSPI1_CLK_ROOT); + case MXC_ESDHC_CLK: + return ccm_clk_root_get_rate(USDHC1_CLK_ROOT); + case MXC_ESDHC2_CLK: + return ccm_clk_root_get_rate(USDHC2_CLK_ROOT); + case MXC_ESDHC3_CLK: + return ccm_clk_root_get_rate(USDHC3_CLK_ROOT); + case MXC_UART_CLK: + return ccm_clk_root_get_rate(LPUART1_CLK_ROOT); + case MXC_FLEXSPI_CLK: + return ccm_clk_root_get_rate(FLEXSPI1_CLK_ROOT); + default: + return -1; + }; + + return -1; +}; + +int enable_i2c_clk(unsigned char enable, u32 i2c_num) +{ + if (i2c_num > 7) + return -EINVAL; + + if (enable) { + /* 24M */ + ccm_lpcg_on(CCGR_I2C1 + i2c_num, false); + ccm_clk_root_cfg(LPI2C1_CLK_ROOT + i2c_num, OSC_24M_CLK, 1); + ccm_lpcg_on(CCGR_I2C1 + i2c_num, true); + } else { + ccm_lpcg_on(CCGR_I2C1 + i2c_num, false); + } + + return 0; +} + +u32 imx_get_i2cclk(u32 i2c_num) +{ + if (i2c_num > 7) + return -EINVAL; + + return ccm_clk_root_get_rate(LPI2C1_CLK_ROOT + i2c_num); +} + +u32 get_lpuart_clk(void) +{ + return mxc_get_clock(MXC_UART_CLK); +} + +void init_uart_clk(u32 index) +{ + switch(index) { + case LPUART1_CLK_ROOT: + /* 24M */ + ccm_lpcg_on(CCGR_URT1, false); + ccm_clk_root_cfg(LPUART1_CLK_ROOT, OSC_24M_CLK, 1); + ccm_lpcg_on(CCGR_URT1, true); + break; + default: + break; + } +} + +void init_clk_usdhc(u32 index) +{ + /* 400 Mhz */ + switch (index) { + case 0: + ccm_lpcg_on(CCGR_USDHC1, 0); + ccm_clk_root_cfg(USDHC1_CLK_ROOT, SYS_PLL_PFD1, 2); + ccm_lpcg_on(CCGR_USDHC1, 1); + break; + case 1: + ccm_lpcg_on(CCGR_USDHC2, 0); + ccm_clk_root_cfg(USDHC2_CLK_ROOT, SYS_PLL_PFD1, 2); + ccm_lpcg_on(CCGR_USDHC2, 1); + break; + case 2: + ccm_lpcg_on(CCGR_USDHC3, 0); + ccm_clk_root_cfg(USDHC3_CLK_ROOT, SYS_PLL_PFD1, 2); + ccm_lpcg_on(CCGR_USDHC3, 1); + break; + default: + return; + }; +} + +void enable_usboh3_clk(unsigned char enable) +{ + if (enable) { + ccm_clk_root_cfg(HSIO_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + ccm_lpcg_on(CCGR_USBC, 1); + } else { + ccm_lpcg_on(CCGR_USBC, 0); + } +} + +#ifdef CONFIG_SPL_BUILD +void dram_pll_init(ulong pll_val) +{ + configure_fracpll(DRAM_PLL_CLK, pll_val); +} + +void dram_enable_bypass(ulong clk_val) +{ + switch (clk_val) { + case MHZ(400): + ccm_clk_root_cfg(DRAM_ALT_CLK_ROOT, SYS_PLL_PFD1, 2); + break; + case MHZ(333): + ccm_clk_root_cfg(DRAM_ALT_CLK_ROOT, SYS_PLL_PFD0, 3); + break; + case MHZ(200): + ccm_clk_root_cfg(DRAM_ALT_CLK_ROOT, SYS_PLL_PFD1, 4); + break; + case MHZ(100): + ccm_clk_root_cfg(DRAM_ALT_CLK_ROOT, SYS_PLL_PFD1, 8); + break; + default: + printf("No matched freq table %lu\n", clk_val); + return; + } + + /* Set DRAM APB to 133Mhz */ + ccm_clk_root_cfg(DRAM_APB_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + /* Switch from DRAM clock root from PLL to CCM */ + ccm_shared_gpr_set(SHARED_GPR_DRAM_CLK, SHARED_GPR_DRAM_CLK_SEL_CCM); +} + +void dram_disable_bypass(void) +{ + /* Set DRAM APB to 133Mhz */ + ccm_clk_root_cfg(DRAM_APB_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + /* Switch from DRAM clock root from CCM to PLL */ + ccm_shared_gpr_set(SHARED_GPR_DRAM_CLK, SHARED_GPR_DRAM_CLK_SEL_PLL); +} +#endif + +int clock_init(void) +{ + /* Set A55 periphal to 333M */ + ccm_clk_root_cfg(ARM_A55_PERIPH_CLK_ROOT, SYS_PLL_PFD0, 3); + /* Set A55 mtr bus to 133M */ + ccm_clk_root_cfg(ARM_A55_MTR_BUS_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + + /* Sentinel to 200M */ + ccm_clk_root_cfg(SENTINEL_CLK_ROOT, SYS_PLL_PFD1_DIV2, 2); + /* Bus_wakeup to 133M */ + ccm_clk_root_cfg(BUS_WAKEUP_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + /* Bus_AON to 133M */ + ccm_clk_root_cfg(BUS_AON_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + /* M33 to 200M */ + ccm_clk_root_cfg(M33_CLK_ROOT, SYS_PLL_PFD1_DIV2, 2); + /* WAKEUP_AXI to 312.5M, because of FEC only can support to 320M for generating MII clock at 2.5M */ + ccm_clk_root_cfg(WAKEUP_AXI_CLK_ROOT, SYS_PLL_PFD2, 2); + /* SWO TRACE to 133M */ + ccm_clk_root_cfg(SWO_TRACE_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + /* M33 systetick to 133M */ + ccm_clk_root_cfg(M33_SYSTICK_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + /* NIC to 400M */ + ccm_clk_root_cfg(NIC_CLK_ROOT, SYS_PLL_PFD1, 2); + /* NIC_APB to 133M */ + ccm_clk_root_cfg(NIC_APB_CLK_ROOT, SYS_PLL_PFD1_DIV2, 3); + + /* allow for non-secure access */ + int i; + for (i = 0; i < OSCPLL_END; i++) + ccm_clk_src_tz_access(i, true, false, false); + + for (i = 0; i < CLK_ROOT_NUM; i++) + ccm_clk_root_tz_access(i, true, false, false); + + for (i = 0; i < CCGR_NUM; i++) + ccm_lpcg_tz_access(i, true, false, false); + + for (i = 0; i < SHARED_GPR_NUM; i++) + ccm_shared_gpr_tz_access(i, true, false, false); + + return 0; +} + +int set_clk_eqos(enum enet_freq type) +{ + u32 eqos_post_div; + + switch (type) { + case ENET_125MHZ: + eqos_post_div = 2; /* 250M clock */ + break; + case ENET_50MHZ: + eqos_post_div = 5; /* 100M clock */ + break; + case ENET_25MHZ: + eqos_post_div = 10; /* 50M clock*/ + break; + default: + return -EINVAL; + } + + /* disable the clock first */ + ccm_lpcg_on(CCGR_ENETQOS, false); + + ccm_clk_root_cfg(ENET_CLK_ROOT, SYS_PLL_PFD0_DIV2, eqos_post_div); + ccm_clk_root_cfg(ENET_TIMER2_CLK_ROOT, SYS_PLL_PFD0_DIV2, 5); + + /* enable clock */ + ccm_lpcg_on(CCGR_ENETQOS, true); + + return 0; +} + +u32 imx_get_eqos_csr_clk(void) +{ + return ccm_clk_root_get_rate(WAKEUP_AXI_CLK_ROOT); +} + +u32 imx_get_fecclk(void) +{ + return ccm_clk_root_get_rate(WAKEUP_AXI_CLK_ROOT); +} + +int set_clk_enet(enum enet_freq type) +{ + u32 div; + + /* disable the clock first */ + ccm_lpcg_on(CCGR_ENET1, false); + + switch (type) { + case ENET_125MHZ: + div = 2; /* 250Mhz */ + break; + case ENET_50MHZ: + div = 5; /* 100Mhz */ + break; + case ENET_25MHZ: + div = 10; /* 50Mhz */ + break; + default: + return -EINVAL; + } + + ccm_clk_root_cfg(ENET_REF_CLK_ROOT, SYS_PLL_PFD0_DIV2, div); + ccm_clk_root_cfg(ENET_TIMER1_CLK_ROOT, SYS_PLL_PFD0_DIV2, 5); + +#ifdef CONFIG_FEC_MXC_25M_REF_CLK + ccm_clk_root_cfg(ENET_REF_PHY_CLK_ROOT, SYS_PLL_PFD0_DIV2, 20); +#endif + + /* enable clock */ + ccm_lpcg_on(CCGR_ENET1, true); + + return 0; +} + +void mxs_set_lcdclk(u32 base_addr, u32 freq) +{ + u32 div, i, krate, temp; + u32 best = 0, best_div = 0, best_pll = 0; + + debug("%s to set rate to %dkhz\n", __func__, freq); + + for (i = 0; i < ARRAY_SIZE(imx9_fracpll_tbl); i++) { + krate = imx9_fracpll_tbl[i].rate / 1000; + div = (krate + freq - 1) / freq; + + if (div > 256) + continue; + + temp = krate / div; + if (best == 0 || temp > best) { + best = temp; + best_div = div; + best_pll = imx9_fracpll_tbl[i].rate; + } + } + + if (best == 0) { + printf("Can't find parent clock for LCDIF, target freq: %u\n", freq); + return; + } + + /* Select to video PLL */ + debug("%s, best_pll = %u, div = %u\n", __func__, best_pll, best_div); + + configure_fracpll(VIDEO_PLL_CLK, best_pll); + ccm_clk_root_cfg(MEDIA_DISP_PIX_CLK_ROOT, VIDEO_PLL_CLK, best_div); +} + +/* + * Dump some clockes. + */ +#ifndef CONFIG_SPL_BUILD +int do_showclocks(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + u32 freq; + + freq = decode_pll(ARM_PLL_CLK); + printf("ARM_PLL %8d MHz\n", freq / 1000); + freq = decode_pll(DRAM_PLL_CLK); + printf("DRAM_PLL %8d MHz\n", freq / 1000); + freq = decode_pll(SYS_PLL_PFD0); + printf("SYS_PLL_PFD0 %8d MHz\n", freq / 1000); + freq = decode_pll(SYS_PLL_PFD0_DIV2); + printf("SYS_PLL_PFD0_DIV2 %8d MHz\n", freq / 1000); + freq = decode_pll(SYS_PLL_PFD1); + printf("SYS_PLL_PFD1 %8d MHz\n", freq / 1000); + freq = decode_pll(SYS_PLL_PFD1_DIV2); + printf("SYS_PLL_PFD1_DIV2 %8d MHz\n", freq / 1000); + freq = decode_pll(SYS_PLL_PFD2); + printf("SYS_PLL_PFD2 %8d MHz\n", freq / 1000); + freq = decode_pll(SYS_PLL_PFD2_DIV2); + printf("SYS_PLL_PFD2_DIV2 %8d MHz\n", freq / 1000); + freq = mxc_get_clock(MXC_ARM_CLK); + printf("ARM CORE %8d MHz\n", freq / 1000000); + freq = mxc_get_clock(MXC_IPG_CLK); + printf("IPG %8d MHz\n", freq / 1000000); + freq = mxc_get_clock(MXC_UART_CLK); + printf("UART3 %8d MHz\n", freq / 1000000); + freq = mxc_get_clock(MXC_ESDHC_CLK); + printf("USDHC1 %8d MHz\n", freq / 1000000); + freq = mxc_get_clock(MXC_FLEXSPI_CLK); + printf("FLEXSPI %8d MHz\n", freq / 1000000); + + return 0; +} + +U_BOOT_CMD( + clocks, CONFIG_SYS_MAXARGS, 1, do_showclocks, + "display clocks", + "" +); +#endif + diff --git a/arch/arm/mach-imx/imx9/clock_root.c b/arch/arm/mach-imx/imx9/clock_root.c new file mode 100644 index 0000000000..5748e28ff0 --- /dev/null +++ b/arch/arm/mach-imx/imx9/clock_root.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + * + * Peng Fan <peng.fan@nxp.com> + */ + +#include <common.h> +#include <command.h> +#include <asm/arch/clock.h> +#include <asm/arch/sys_proto.h> +#include <asm/arch/ccm_regs.h> +#include <asm/global_data.h> +#include <linux/iopoll.h> + +DECLARE_GLOBAL_DATA_PTR; + +static struct ccm_reg *ccm_reg = (struct ccm_reg *)CCM_BASE_ADDR; + +static enum ccm_clk_src clk_root_mux[][4] = { + { OSC_24M_CLK, SYS_PLL_PFD0, SYS_PLL_PFD1, SYS_PLL_PFD2 }, /* bus */ + { OSC_24M_CLK, SYS_PLL_PFD0_DIV2, SYS_PLL_PFD1_DIV2, SYS_PLL_PFD2_DIV2 }, /* non-IO */ + { OSC_24M_CLK, SYS_PLL_PFD0_DIV2, SYS_PLL_PFD1_DIV2, VIDEO_PLL_CLK }, /* IO*/ + { OSC_24M_CLK, SYS_PLL_PFD0, AUDIO_PLL_CLK, EXT_CLK }, /* TPM */ + { OSC_24M_CLK, AUDIO_PLL_CLK, VIDEO_PLL_CLK, EXT_CLK }, /* Audio */ + { OSC_24M_CLK, AUDIO_PLL_CLK, VIDEO_PLL_CLK, SYS_PLL_PFD0 }, /* Video */ + { OSC_24M_CLK, SYS_PLL_PFD0, SYS_PLL_PFD1, AUDIO_PLL_CLK }, /* CKO1 */ + { OSC_24M_CLK, SYS_PLL_PFD0, SYS_PLL_PFD1, VIDEO_PLL_CLK }, /* CKO2 */ + { OSC_24M_CLK, AUDIO_PLL_CLK, VIDEO_PLL_CLK, SYS_PLL_PFD2 }, /* CAMSCAN */ +}; + +static struct clk_root_map clk_root_array[] = { + { ARM_A55_PERIPH_CLK_ROOT, 0 }, + { ARM_A55_MTR_BUS_CLK_ROOT, 2 }, + { ARM_A55_CLK_ROOT, 0 }, + { M33_CLK_ROOT, 2 }, + { SENTINEL_CLK_ROOT, 2 }, + { BUS_WAKEUP_CLK_ROOT, 2 }, + { BUS_AON_CLK_ROOT, 2 }, + { WAKEUP_AXI_CLK_ROOT, 0 }, + { SWO_TRACE_CLK_ROOT, 2 }, + { M33_SYSTICK_CLK_ROOT, 2 }, + { FLEXIO1_CLK_ROOT, 2 }, + { FLEXIO2_CLK_ROOT, 2 }, + { LPIT1_CLK_ROOT, 2 }, + { LPIT2_CLK_ROOT, 2 }, + { LPTMR1_CLK_ROOT, 2 }, + { LPTMR2_CLK_ROOT, 2 }, + { TPM1_CLK_ROOT, 3 }, + { TPM2_CLK_ROOT, 3 }, + { TPM3_CLK_ROOT, 3 }, + { TPM4_CLK_ROOT, 3 }, + { TPM5_CLK_ROOT, 3 }, + { TPM6_CLK_ROOT, 3 }, + { FLEXSPI1_CLK_ROOT, 0 }, + { CAN1_CLK_ROOT, 2 }, + { CAN2_CLK_ROOT, 2 }, + { LPUART1_CLK_ROOT, 2 }, + { LPUART2_CLK_ROOT, 2 }, + { LPUART3_CLK_ROOT, 2 }, + { LPUART4_CLK_ROOT, 2 }, + { LPUART5_CLK_ROOT, 2 }, + { LPUART6_CLK_ROOT, 2 }, + { LPUART7_CLK_ROOT, 2 }, + { LPUART8_CLK_ROOT, 2 }, + { LPI2C1_CLK_ROOT, 2 }, + { LPI2C2_CLK_ROOT, 2 }, + { LPI2C3_CLK_ROOT, 2 }, + { LPI2C4_CLK_ROOT, 2 }, + { LPI2C5_CLK_ROOT, 2 }, + { LPI2C6_CLK_ROOT, 2 }, + { LPI2C7_CLK_ROOT, 2 }, + { LPI2C8_CLK_ROOT, 2 }, + { LPSPI1_CLK_ROOT, 2 }, + { LPSPI2_CLK_ROOT, 2 }, + { LPSPI3_CLK_ROOT, 2 }, + { LPSPI4_CLK_ROOT, 2 }, + { LPSPI5_CLK_ROOT, 2 }, + { LPSPI6_CLK_ROOT, 2 }, + { LPSPI7_CLK_ROOT, 2 }, + { LPSPI8_CLK_ROOT, 2 }, + { I3C1_CLK_ROOT, 2 }, + { I3C2_CLK_ROOT, 2 }, + { USDHC1_CLK_ROOT, 0 }, + { USDHC2_CLK_ROOT, 0 }, + { USDHC3_CLK_ROOT, 0 }, + { SAI1_CLK_ROOT, 4 }, + { SAI2_CLK_ROOT, 4 }, + { SAI3_CLK_ROOT, 4 }, + { CCM_CKO1_CLK_ROOT, 6 }, + { CCM_CKO2_CLK_ROOT, 7 }, + { CCM_CKO3_CLK_ROOT, 6 }, + { CCM_CKO4_CLK_ROOT, 7 }, + { HSIO_CLK_ROOT, 2 }, + { HSIO_USB_TEST_60M_CLK_ROOT, 2 }, + { HSIO_ACSCAN_80M_CLK_ROOT, 2 }, + { HSIO_ACSCAN_480M_CLK_ROOT, 0 }, + { NIC_CLK_ROOT, 0 }, + { NIC_APB_CLK_ROOT, 2 }, + { ML_APB_CLK_ROOT, 2 }, + { ML_CLK_ROOT, 0 }, + { MEDIA_AXI_CLK_ROOT, 0 }, + { MEDIA_APB_CLK_ROOT, 2 }, + { MEDIA_LDB_CLK_ROOT, 5 }, + { MEDIA_DISP_PIX_CLK_ROOT, 5 }, + { CAM_PIX_CLK_ROOT, 5 }, + { MIPI_TEST_BYTE_CLK_ROOT, 5 }, + { MIPI_PHY_CFG_CLK_ROOT, 5 }, + { DRAM_ALT_CLK_ROOT, 0 }, + { DRAM_APB_CLK_ROOT, 1 }, + { ADC_CLK_ROOT, 2 }, + { PDM_CLK_ROOT, 4 }, + { TSTMR1_CLK_ROOT, 2 }, + { TSTMR2_CLK_ROOT, 2 }, + { MQS1_CLK_ROOT, 4 }, + { MQS2_CLK_ROOT, 4 }, + { AUDIO_XCVR_CLK_ROOT, 1 }, + { SPDIF_CLK_ROOT, 4 }, + { ENET_CLK_ROOT, 1 }, + { ENET_TIMER1_CLK_ROOT, 2 }, + { ENET_TIMER2_CLK_ROOT, 2 }, + { ENET_REF_CLK_ROOT, 1 }, + { ENET_REF_PHY_CLK_ROOT, 2 }, + { I3C1_SLOW_CLK_ROOT, 2 }, + { I3C2_SLOW_CLK_ROOT, 2 }, + { USB_PHY_BURUNIN_CLK_ROOT, 2 }, + { PAL_CAME_SCAN_CLK_ROOT, 8 }, +}; + +int ccm_clk_src_on(enum ccm_clk_src oscpll, bool enable) +{ + u32 authen; + + if (oscpll >= OSCPLL_END) + return -EINVAL; + + authen = readl(&ccm_reg->clk_oscplls[oscpll].authen); + + /* If using cpulpm, need disable it first */ + if (authen & CCM_AUTHEN_CPULPM_MODE) + return -EPERM; + + if (enable) + writel(1, &ccm_reg->clk_oscplls[oscpll].direct); + else + writel(0, &ccm_reg->clk_oscplls[oscpll].direct); + + return 0; +} + +/* auto mode, enable = DIRECT[ON] | STATUS0[IN_USE] */ +int ccm_clk_src_auto(enum ccm_clk_src oscpll, bool enable) +{ + u32 authen; + + if (oscpll >= OSCPLL_END) + return -EINVAL; + + authen = readl(&ccm_reg->clk_oscplls[oscpll].authen); + + /* AUTO CTRL and CPULPM are mutual exclusion, need disable CPULPM first */ + if (authen & CCM_AUTHEN_CPULPM_MODE) + return -EPERM; + + if (enable) { + writel(authen | CCM_AUTHEN_AUTO_CTRL, + &ccm_reg->clk_oscplls[oscpll].authen); + } else + writel((authen & ~CCM_AUTHEN_AUTO_CTRL), + &ccm_reg->clk_oscplls[oscpll].authen); + + return 0; +} + +int ccm_clk_src_lpm(enum ccm_clk_src oscpll, bool enable) +{ + u32 authen; + + if (oscpll >= OSCPLL_END) + return -EINVAL; + + authen = readl(&ccm_reg->clk_oscplls[oscpll].authen); + + /* AUTO CTRL and CPULPM are mutual exclusion, need disable AUTO CTRL first */ + if (authen & CCM_AUTHEN_AUTO_CTRL) + return -EPERM; + + if (enable) + writel(authen | CCM_AUTHEN_CPULPM_MODE, + &ccm_reg->clk_oscplls[oscpll].authen); + else + writel((authen & ~CCM_AUTHEN_CPULPM_MODE), + &ccm_reg->clk_oscplls[oscpll].authen); + + return 0; +} + +int ccm_clk_src_config_lpm(enum ccm_clk_src oscpll, u32 domain, u32 lpm_val) +{ + u32 lpm, authen; + + if (oscpll >= OSCPLL_END || domain >= 16) + return -EINVAL; + + authen = readl(&ccm_reg->clk_oscplls[oscpll].authen); + if (!(authen & CCM_AUTHEN_CPULPM_MODE)) + return -EPERM; + + if (domain > 7) { + lpm = readl(&ccm_reg->clk_oscplls[oscpll].lpm1); + lpm &= ~(0x3 << ((domain - 8) * 4)); + lpm |= (lpm_val & 0x3) << ((domain - 8) * 4); + writel(lpm, &ccm_reg->clk_oscplls[oscpll].lpm1); + } else { + lpm = readl(&ccm_reg->clk_oscplls[oscpll].lpm0); + lpm &= ~(0x3 << (domain * 4)); + lpm |= (lpm_val & 0x3) << (domain * 4); + writel(lpm, &ccm_reg->clk_oscplls[oscpll].lpm0); + } + + return 0; +} + +bool ccm_clk_src_is_clk_on(enum ccm_clk_src oscpll) +{ + return !!(readl(&ccm_reg->clk_oscplls[oscpll].status0) & 0x1); +} + +int ccm_clk_src_tz_access(enum ccm_clk_src oscpll, + bool non_secure, bool user_mode, bool lock_tz) +{ + u32 authen; + + if (oscpll >= OSCPLL_END) + return -EINVAL; + + authen = readl(&ccm_reg->clk_oscplls[oscpll].authen); + + authen |= non_secure ? CCM_AUTHEN_TZ_NS : 0; + authen |= user_mode ? CCM_AUTHEN_TZ_USER : 0; + authen |= lock_tz ? CCM_AUTHEN_LOCK_TZ : 0; + + writel(authen, &ccm_reg->clk_oscplls[oscpll].authen); + + return 0; +} + +int ccm_clk_root_cfg(u32 clk_root_id, enum ccm_clk_src src, u32 div) +{ + int i; + int ret; + u32 mux, status; + + if (clk_root_id >= CLK_ROOT_NUM || div > 256 || div == 0) + return -EINVAL; + + mux = clk_root_array[clk_root_id].mux_type; + + for (i = 0; i < 4; i++) { + if (src == clk_root_mux[mux][i]) + break; + } + + if (i == 4) { + printf("Invalid source [%u] for this clk root\n", src); + return -EINVAL; + } + + writel((i << 8) | (div - 1), &ccm_reg->clk_roots[clk_root_id].control); + + ret = readl_poll_timeout(&ccm_reg->clk_roots[clk_root_id].status0, status, + !(status & CLK_ROOT_STATUS_CHANGING), 200000); + if (ret) + printf("%s: failed, status: 0x%x\n", __func__, + readl(&ccm_reg->clk_roots[clk_root_id].status0)); + + return ret; +}; + +u32 ccm_clk_root_get_rate(u32 clk_root_id) +{ + u32 mux, status, div, rate; + enum ccm_clk_src src; + + if (clk_root_id >= CLK_ROOT_NUM) + return 0; + + status = readl(&ccm_reg->clk_roots[clk_root_id].control); + + if (status & CLK_ROOT_STATUS_OFF) + return 0; /* clock is off */ + + mux = (status & CLK_ROOT_MUX_MASK) >> CLK_ROOT_MUX_SHIFT; + div = status & CLK_ROOT_DIV_MASK; + src = clk_root_mux[clk_root_array[clk_root_id].mux_type][mux]; + + rate = get_clk_src_rate(src) * 1000; + + return rate / (div + 1); /* return in hz */ +} + +int ccm_clk_root_tz_access(u32 clk_root_id, + bool non_secure, bool user_mode, bool lock_tz) +{ + u32 authen; + + if (clk_root_id >= CLK_ROOT_NUM) + return -EINVAL; + + authen = readl(&ccm_reg->clk_roots[clk_root_id].authen); + + authen |= non_secure ? CCM_AUTHEN_TZ_NS : 0; + authen |= user_mode ? CCM_AUTHEN_TZ_USER : 0; + authen |= lock_tz ? CCM_AUTHEN_LOCK_TZ : 0; + + writel(authen, &ccm_reg->clk_roots[clk_root_id].authen); + + return 0; +} + +int ccm_lpcg_on(u32 lpcg, bool enable) +{ + u32 authen; + + if (lpcg >= CCGR_NUM) + return -EINVAL; + + authen = readl(&ccm_reg->clk_lpcgs[lpcg].authen); + + /* If using cpulpm, need disable it first */ + if (authen & CCM_AUTHEN_CPULPM_MODE) + return -EPERM; + + if (enable) + writel(1, &ccm_reg->clk_lpcgs[lpcg].direct); + else + writel(0, &ccm_reg->clk_lpcgs[lpcg].direct); + + return 0; + +} + +int ccm_lpcg_lpm(u32 lpcg, bool enable) +{ + u32 authen; + + if (lpcg >= CCGR_NUM) + return -EINVAL; + + authen = readl(&ccm_reg->clk_lpcgs[lpcg].authen); + + if (enable) + writel(authen | CCM_AUTHEN_CPULPM_MODE, + &ccm_reg->clk_lpcgs[lpcg].authen); + else + writel((authen & ~CCM_AUTHEN_CPULPM_MODE), + &ccm_reg->clk_lpcgs[lpcg].authen); + + return 0; +} + +int ccm_lpcg_config_lpm(u32 lpcg, u32 domain, u32 lpm_val) +{ + u32 lpm, authen; + + if (lpcg >= CCGR_NUM || domain >= 16) + return -EINVAL; + + authen = readl(&ccm_reg->clk_lpcgs[lpcg].authen); + if (!(authen & CCM_AUTHEN_CPULPM_MODE)) + return -EPERM; + + if (domain > 7) { + lpm = readl(&ccm_reg->clk_lpcgs[lpcg].lpm1); + lpm &= ~(0x3 << ((domain - 8) * 4)); + lpm |= (lpm_val & 0x3) << ((domain - 8) * 4); + writel(lpm, &ccm_reg->clk_lpcgs[lpcg].lpm1); + } else { + lpm = readl(&ccm_reg->clk_lpcgs[lpcg].lpm0); + lpm &= ~(0x3 << (domain * 4)); + lpm |= (lpm_val & 0x3) << (domain * 4); + writel(lpm, &ccm_reg->clk_lpcgs[lpcg].lpm0); + } + + return 0; +} + +bool ccm_lpcg_is_clk_on(u32 lpcg) +{ + return !!(readl(&ccm_reg->clk_lpcgs[lpcg].status0) & 0x1); +} + +int ccm_lpcg_tz_access(u32 lpcg, + bool non_secure, bool user_mode, bool lock_tz) +{ + u32 authen; + + if (lpcg >= CCGR_NUM) + return -EINVAL; + + authen = readl(&ccm_reg->clk_lpcgs[lpcg].authen); + + authen |= non_secure ? CCM_AUTHEN_TZ_NS : 0; + authen |= user_mode ? CCM_AUTHEN_TZ_USER : 0; + authen |= lock_tz ? CCM_AUTHEN_LOCK_TZ : 0; + + writel(authen, &ccm_reg->clk_lpcgs[lpcg].authen); + + return 0; +} + +int ccm_shared_gpr_set(u32 gpr, u32 val) +{ + if (gpr >= SHARED_GPR_NUM) + return -EINVAL; + + writel(val, &ccm_reg->clk_shared_gpr[gpr].gpr); + + return 0; +} + +int ccm_shared_gpr_get(u32 gpr, u32 *val) +{ + if (gpr >= SHARED_GPR_NUM || !val) + return -EINVAL; + + *val = readl(&ccm_reg->clk_shared_gpr[gpr].gpr); + + return 0; +} + + +int ccm_shared_gpr_tz_access(u32 gpr, + bool non_secure, bool user_mode, bool lock_tz) +{ + u32 authen; + + if (gpr >= SHARED_GPR_NUM) + return -EINVAL; + + authen = readl(&ccm_reg->clk_shared_gpr[gpr].authen); + + authen |= non_secure ? CCM_AUTHEN_TZ_NS : 0; + authen |= user_mode ? CCM_AUTHEN_TZ_USER : 0; + authen |= lock_tz ? CCM_AUTHEN_LOCK_TZ : 0; + + writel(authen, &ccm_reg->clk_shared_gpr[gpr].authen); + + return 0; +} diff --git a/arch/arm/mach-imx/imx9/imx_bootaux.c b/arch/arm/mach-imx/imx9/imx_bootaux.c new file mode 100644 index 0000000000..721e77193e --- /dev/null +++ b/arch/arm/mach-imx/imx9/imx_bootaux.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + */ + +#include <common.h> +#include <log.h> +#include <asm/io.h> +#include <asm/mach-imx/sys_proto.h> +#include <command.h> +#include <elf.h> +#include <imx_sip.h> +#include <linux/arm-smccc.h> +#include <linux/compiler.h> +#include <cpu_func.h> + +int arch_auxiliary_core_check_up(u32 core_id) +{ + struct arm_smccc_res res; + + arm_smccc_smc(IMX_SIP_SRC, IMX_SIP_SRC_MCU_STARTED, 0, 0, + 0, 0, 0, 0, &res); + + return res.a0; +} + +int arch_auxiliary_core_down(u32 core_id) +{ + struct arm_smccc_res res; + + printf("## Stopping auxiliary core\n"); + + arm_smccc_smc(IMX_SIP_SRC, IMX_SIP_SRC_MCU_STOP, 0, 0, + 0, 0, 0, 0, &res); + + return 0; +} + +int arch_auxiliary_core_up(u32 core_id, ulong addr) +{ + struct arm_smccc_res res; + u32 stack, pc; + + if (!addr) + return -EINVAL; + + stack = *(u32 *)addr; + pc = *(u32 *)(addr + 4); + + printf("## Starting auxiliary core stack = 0x%08X, pc = 0x%08X...\n", stack, pc); + + arm_smccc_smc(IMX_SIP_SRC, IMX_SIP_SRC_MCU_START, 0, 0, + 0, 0, 0, 0, &res); + + return 0; +} + +/* + * To i.MX6SX and i.MX7D, the image supported by bootaux needs + * the reset vector at the head for the image, with SP and PC + * as the first two words. + * + * Per the cortex-M reference manual, the reset vector of M4/M7 needs + * to exist at 0x0 (TCMUL/IDTCM). The PC and SP are the first two addresses + * of that vector. So to boot M4/M7, the A core must build the M4/M7's reset + * vector with getting the PC and SP from image and filling them to + * TCMUL/IDTCM. When M4/M7 is kicked, it will load the PC and SP by itself. + * The TCMUL/IDTCM is mapped to (MCU_BOOTROM_BASE_ADDR) at A core side for + * accessing the M4/M7 TCMUL/IDTCM. + */ +static int do_bootaux(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + ulong addr; + int ret, up; + u32 core = 0; + u32 stop = 0; + + if (argc < 2) + return CMD_RET_USAGE; + + if (argc > 2) + core = simple_strtoul(argv[2], NULL, 10); + + if (argc > 3) + stop = simple_strtoul(argv[3], NULL, 10); + + up = arch_auxiliary_core_check_up(core); + if (up) { + printf("## Auxiliary core is already up\n"); + return CMD_RET_SUCCESS; + } + + addr = simple_strtoul(argv[1], NULL, 16); + + if (!addr) + return CMD_RET_FAILURE; + + ret = arch_auxiliary_core_up(core, addr); + if (ret) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +static int do_stopaux(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + int ret, up; + + up = arch_auxiliary_core_check_up(0); + if (!up) { + printf("## Auxiliary core is already down\n"); + return CMD_RET_SUCCESS; + } + + ret = arch_auxiliary_core_down(0); + if (ret) + return CMD_RET_FAILURE; + + return CMD_RET_SUCCESS; +} + +U_BOOT_CMD( + stopaux, CONFIG_SYS_MAXARGS, 1, do_stopaux, + "Start auxiliary core", + "<address> [<core>]\n" + " - start auxiliary core [<core>] (default 0),\n" + " at address <address>\n" +); + +U_BOOT_CMD( + bootaux, CONFIG_SYS_MAXARGS, 1, do_bootaux, + "Start auxiliary core", + "<address> [<core>]\n" + " - start auxiliary core [<core>] (default 0),\n" + " at address <address>\n" +); diff --git a/arch/arm/mach-imx/imx9/lowlevel_init.S b/arch/arm/mach-imx/imx9/lowlevel_init.S new file mode 100644 index 0000000000..1dc1dbfcdd --- /dev/null +++ b/arch/arm/mach-imx/imx9/lowlevel_init.S @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2022 NXP + */ + +#include <config.h> + +.align 8 +.global rom_pointer +rom_pointer: + .space 256 + +/* + * Routine: save_boot_params (called after reset from start.S) + */ + +.global save_boot_params +save_boot_params: +#ifndef CONFIG_SPL_BUILD + /* The firmware provided ATAG/FDT address can be found in r2/x0 */ + adr x0, rom_pointer + stp x1, x2, [x0], #16 + stp x3, x4, [x0], #16 +#endif + /* Returns */ + b save_boot_params_ret diff --git a/arch/arm/mach-imx/imx9/soc.c b/arch/arm/mach-imx/imx9/soc.c new file mode 100644 index 0000000000..66200dbe3c --- /dev/null +++ b/arch/arm/mach-imx/imx9/soc.c @@ -0,0 +1,614 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + * + * Peng Fan <peng.fan@nxp.com> + */ + +#include <common.h> +#include <cpu_func.h> +#include <init.h> +#include <log.h> +#include <asm/arch/imx-regs.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/ccm_regs.h> +#include <asm/arch/sys_proto.h> +#include <asm/arch/trdc.h> +#include <asm/mach-imx/boot_mode.h> +#include <asm/mach-imx/syscounter.h> +#include <asm/armv8/mmu.h> +#include <dm/uclass.h> +#include <env.h> +#include <env_internal.h> +#include <errno.h> +#include <fdt_support.h> +#include <linux/bitops.h> +#include <asm/setup.h> +#include <asm/bootm.h> +#include <asm/arch-imx/cpu.h> +#include <asm/mach-imx/s400_api.h> +#include <asm/mach-imx/optee.h> +#include <linux/delay.h> +#include <fuse.h> + +DECLARE_GLOBAL_DATA_PTR; + +struct rom_api *g_rom_api = (struct rom_api *)0x1980; + +enum boot_device get_boot_device(void) +{ + volatile gd_t *pgd = gd; + int ret; + u32 boot; + u16 boot_type; + u8 boot_instance; + enum boot_device boot_dev = SD1_BOOT; + + ret = g_rom_api->query_boot_infor(QUERY_BT_DEV, &boot, + ((uintptr_t)&boot) ^ QUERY_BT_DEV); + set_gd(pgd); + + if (ret != ROM_API_OKAY) { + puts("ROMAPI: failure at query_boot_info\n"); + return -1; + } + + boot_type = boot >> 16; + boot_instance = (boot >> 8) & 0xff; + + switch (boot_type) { + case BT_DEV_TYPE_SD: + boot_dev = boot_instance + SD1_BOOT; + break; + case BT_DEV_TYPE_MMC: + boot_dev = boot_instance + MMC1_BOOT; + break; + case BT_DEV_TYPE_NAND: + boot_dev = NAND_BOOT; + break; + case BT_DEV_TYPE_FLEXSPINOR: + boot_dev = QSPI_BOOT; + break; + case BT_DEV_TYPE_USB: + boot_dev = boot_instance + USB_BOOT; + break; + default: + break; + } + + debug("boot dev %d\n", boot_dev); + + return boot_dev; +} + +bool is_usb_boot(void) +{ + enum boot_device bt_dev = get_boot_device(); + return (bt_dev == USB_BOOT || bt_dev == USB2_BOOT); +} + +void disconnect_from_pc(void) +{ + enum boot_device bt_dev = get_boot_device(); + + if (bt_dev == USB_BOOT) + writel(0x0, USB1_BASE_ADDR + 0x140); + else if (bt_dev == USB2_BOOT) + writel(0x0, USB2_BASE_ADDR + 0x140); + + return; +} + +#ifdef CONFIG_ENV_IS_IN_MMC +__weak int board_mmc_get_env_dev(int devno) +{ + return devno; +} + +int mmc_get_env_dev(void) +{ + volatile gd_t *pgd = gd; + int ret; + u32 boot; + u16 boot_type; + u8 boot_instance; + + ret = g_rom_api->query_boot_infor(QUERY_BT_DEV, &boot, + ((uintptr_t)&boot) ^ QUERY_BT_DEV); + set_gd(pgd); + + if (ret != ROM_API_OKAY) { + puts("ROMAPI: failure at query_boot_info\n"); + return CONFIG_SYS_MMC_ENV_DEV; + } + + boot_type = boot >> 16; + boot_instance = (boot >> 8) & 0xff; + + debug("boot_type %d, instance %d\n", boot_type, boot_instance); + + /* If not boot from sd/mmc, use default value */ + if ((boot_type != BOOT_TYPE_SD) && (boot_type != BOOT_TYPE_MMC)) + return env_get_ulong("mmcdev", 10, CONFIG_SYS_MMC_ENV_DEV); + + return board_mmc_get_env_dev(boot_instance); + +} +#endif + +#ifdef CONFIG_USB_PORT_AUTO +int board_usb_gadget_port_auto(void) +{ + enum boot_device bt_dev = get_boot_device(); + int usb_boot_index = 0; + + if (bt_dev == USB2_BOOT) + usb_boot_index = 1; + + printf("auto usb %d\n", usb_boot_index); + + return usb_boot_index; +} +#endif + +static void set_cpu_info(struct sentinel_get_info_data *info) +{ + gd->arch.soc_rev = info->soc; + gd->arch.lifecycle = info->lc; + memcpy((void *)&gd->arch.uid, &info->uid, 4 * sizeof(u32)); +} + +u32 get_cpu_rev(void) +{ + u32 rev = (gd->arch.soc_rev >> 24) - 0xa0; + return (MXC_CPU_IMX93 << 12) | (CHIP_REV_1_0 + rev); +} + +#define UNLOCK_WORD 0xD928C520 /* unlock word */ +#define REFRESH_WORD 0xB480A602 /* refresh word */ + +static void disable_wdog(void __iomem *wdog_base) +{ + u32 val_cs = readl(wdog_base + 0x00); + + if (!(val_cs & 0x80)) + return; + + /* default is 32bits cmd */ + writel(REFRESH_WORD, (wdog_base + 0x04)); /* Refresh the CNT */ + + if (!(val_cs & 0x800)) { + writel(UNLOCK_WORD, (wdog_base + 0x04)); + while (!(readl(wdog_base + 0x00) & 0x800)) + ; + } + writel(0x0, (wdog_base + 0x0C)); /* Set WIN to 0 */ + writel(0x400, (wdog_base + 0x08)); /* Set timeout to default 0x400 */ + writel(0x2120, (wdog_base + 0x00)); /* Disable it and set update */ + + while (!(readl(wdog_base + 0x00) & 0x400)) + ; +} + +void init_wdog(void) +{ + u32 src_val; + + disable_wdog((void __iomem *)WDG3_BASE_ADDR); + disable_wdog((void __iomem *)WDG4_BASE_ADDR); + disable_wdog((void __iomem *)WDG5_BASE_ADDR); + + src_val = readl(0x54460018); /* reset mask */ + src_val &= ~0x1c; + writel(src_val, 0x54460018); +} + +static struct mm_region imx93_mem_map[] = { + { + /* ROM */ + .virt = 0x0UL, + .phys = 0x0UL, + .size = 0x100000UL, + .attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | + PTE_BLOCK_OUTER_SHARE + }, { + /* TCM */ + .virt = 0x201c0000UL, + .phys = 0x201c0000UL, + .size = 0x80000UL, + .attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) | + PTE_BLOCK_NON_SHARE | + PTE_BLOCK_PXN | PTE_BLOCK_UXN + }, { + /* OCRAM */ + .virt = 0x20480000UL, + .phys = 0x20480000UL, + .size = 0xA0000UL, + .attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | + PTE_BLOCK_OUTER_SHARE + }, { + /* AIPS */ + .virt = 0x40000000UL, + .phys = 0x40000000UL, + .size = 0x40000000UL, + .attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) | + PTE_BLOCK_NON_SHARE | + PTE_BLOCK_PXN | PTE_BLOCK_UXN + }, { + /* Flexible Serial Peripheral Interface */ + .virt = 0x28000000UL, + .phys = 0x28000000UL, + .size = 0x30000000UL, + .attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) | + PTE_BLOCK_NON_SHARE | + PTE_BLOCK_PXN | PTE_BLOCK_UXN + }, { + /* DRAM1 */ + .virt = 0x80000000UL, + .phys = 0x80000000UL, + .size = PHYS_SDRAM_SIZE, + .attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) | + PTE_BLOCK_OUTER_SHARE + }, { + /* empty entrie to split table entry 5 if needed when TEEs are used */ + 0, + }, { + /* List terminator */ + 0, + } +}; + +struct mm_region *mem_map = imx93_mem_map; + +int dram_init(void) +{ + gd->ram_size = PHYS_SDRAM_SIZE; + + return 0; +} + +void imx_get_mac_from_fuse(int dev_id, unsigned char *mac) +{ + u32 val[2] = {}; + int ret; + + if (dev_id == 0) { + ret = fuse_read(39, 3, &val[0]); + if (ret) + goto err; + + ret = fuse_read(39, 4, &val[1]); + if (ret) + goto err; + + mac[0] = val[1] >> 8; + mac[1] = val[1]; + mac[2] = val[0] >> 24; + mac[3] = val[0] >> 16; + mac[4] = val[0] >> 8; + mac[5] = val[0]; + + } else { + ret = fuse_read(39, 5, &val[0]); + if (ret) + goto err; + + ret = fuse_read(39, 4, &val[1]); + if (ret) + goto err; + + mac[0] = val[1] >> 24; + mac[1] = val[1] >> 16; + mac[2] = val[0] >> 24; + mac[3] = val[0] >> 16; + mac[4] = val[0] >> 8; + mac[5] = val[0]; + } + + debug("%s: MAC%d: %02x.%02x.%02x.%02x.%02x.%02x\n", + __func__, dev_id, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return; +err: + memset(mac, 0, 6); + printf("%s: fuse read err: %d\n", __func__, ret); +} + +int print_cpuinfo(void) +{ + u32 cpurev; + + cpurev = get_cpu_rev(); + + printf("CPU: i.MX93 rev%d.%d at %d MHz\n", + (cpurev & 0x000F0) >> 4, (cpurev & 0x0000F) >> 0, + mxc_get_clock(MXC_ARM_CLK) / 1000000); + + return 0; +} + +int arch_misc_init(void) +{ + return 0; +} + +int ft_system_setup(void *blob, struct bd_info *bd) +{ + return ft_add_optee_node(blob, bd); +} + +#if defined(CONFIG_SERIAL_TAG) || defined(CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG) +void get_board_serial(struct tag_serialnr *serialnr) +{ + printf("UID: 0x%x 0x%x 0x%x 0x%x\n", + gd->arch.uid[0], gd->arch.uid[1], gd->arch.uid[2], gd->arch.uid[3]); + + serialnr->low = gd->arch.uid[0]; + serialnr->high = gd->arch.uid[3]; +} +#endif + +int arch_cpu_init(void) +{ + if (IS_ENABLED(CONFIG_SPL_BUILD)) { + /* Disable wdog */ + init_wdog(); + + clock_init(); + + trdc_early_init(); + } + + return 0; +} + +int arch_cpu_init_dm(void) +{ + struct udevice *devp; + int node, ret; + u32 res; + struct sentinel_get_info_data info; + + node = fdt_node_offset_by_compatible(gd->fdt_blob, -1, "fsl,imx93-mu-s4"); + + ret = uclass_get_device_by_of_offset(UCLASS_MISC, node, &devp); + if (ret) + return ret; + + ret = ahab_get_info(&info, &res); + if (ret) + return ret; + + set_cpu_info(&info); + + return 0; +} + +#ifdef CONFIG_ARCH_EARLY_INIT_R +int arch_early_init_r(void) +{ + struct udevice *devp; + int node, ret; + + node = fdt_node_offset_by_compatible(gd->fdt_blob, -1, "fsl,imx93-mu-s4"); + + ret = uclass_get_device_by_of_offset(UCLASS_MISC, node, &devp); + if (ret) { + printf("could not get S400 mu %d\n", ret); + return ret; + } + + return 0; +} +#endif + +int timer_init(void) +{ +#ifdef CONFIG_SPL_BUILD + struct sctr_regs *sctr = (struct sctr_regs *)SYSCNT_CTRL_BASE_ADDR; + unsigned long freq = readl(&sctr->cntfid0); + + /* Update with accurate clock frequency */ + asm volatile("msr cntfrq_el0, %0" : : "r" (freq) : "memory"); + + clrsetbits_le32(&sctr->cntcr, SC_CNTCR_FREQ0 | SC_CNTCR_FREQ1, + SC_CNTCR_FREQ0 | SC_CNTCR_ENABLE | SC_CNTCR_HDBG); +#endif + + gd->arch.tbl = 0; + gd->arch.tbu = 0; + + return 0; +} + +enum env_location env_get_location(enum env_operation op, int prio) +{ + enum boot_device dev = get_boot_device(); + enum env_location env_loc = ENVL_UNKNOWN; + + if (prio) + return env_loc; + + switch (dev) { +#ifdef CONFIG_ENV_IS_IN_SPI_FLASH + case QSPI_BOOT: + env_loc = ENVL_SPI_FLASH; + break; +#endif +#ifdef CONFIG_ENV_IS_IN_MMC + case SD1_BOOT: + case SD2_BOOT: + case SD3_BOOT: + case MMC1_BOOT: + case MMC2_BOOT: + case MMC3_BOOT: + env_loc = ENVL_MMC; + break; +#endif + default: +#if defined(CONFIG_ENV_IS_NOWHERE) + env_loc = ENVL_NOWHERE; +#endif + break; + } + + return env_loc; +} + +int mix_power_init(enum mix_power_domain pd) +{ + enum src_mix_slice_id mix_id; + enum src_mem_slice_id mem_id; + struct src_mix_slice_regs *mix_regs; + struct src_mem_slice_regs *mem_regs; + struct src_general_regs *global_regs; + u32 scr, val; + + switch (pd) { + case MIX_PD_MEDIAMIX: + mix_id = SRC_MIX_MEDIA; + mem_id = SRC_MEM_MEDIA; + scr = BIT(5); + + /* Enable S400 handshake */ + struct blk_ctrl_s_aonmix_regs *s_regs = + (struct blk_ctrl_s_aonmix_regs *)BLK_CTRL_S_ANOMIX_BASE_ADDR; + + setbits_le32(&s_regs->lp_handshake[0], BIT(13)); + break; + case MIX_PD_MLMIX: + mix_id = SRC_MIX_ML; + mem_id = SRC_MEM_ML; + scr = BIT(4); + break; + case MIX_PD_DDRMIX: + mix_id = SRC_MIX_DDRMIX; + mem_id = SRC_MEM_DDRMIX; + scr = BIT(6); + break; + default: + return -EINVAL; + } + + mix_regs = (struct src_mix_slice_regs *)(ulong)(SRC_IPS_BASE_ADDR + 0x400 * (mix_id + 1)); + mem_regs = (struct src_mem_slice_regs *)(ulong)(SRC_IPS_BASE_ADDR + 0x3800 + 0x400 * mem_id); + global_regs = (struct src_general_regs *)(ulong)SRC_GLOBAL_RBASE; + + /* Allow NS to set it */ + setbits_le32(&mix_regs->authen_ctrl, BIT(9)); + + clrsetbits_le32(&mix_regs->psw_ack_ctrl[0], BIT(28), BIT(29)); + + /* mix reset will be held until boot core write this bit to 1 */ + setbits_le32(&global_regs->scr, scr); + + /* Enable mem in Low power auto sequence */ + setbits_le32(&mem_regs->mem_ctrl, BIT(2)); + + /* Set the power down state */ + val = readl(&mix_regs->func_stat); + if (val & SRC_MIX_SLICE_FUNC_STAT_PSW_STAT) { + /* The mix is default power off, power down it to make PDN_SFT bit + * aligned with FUNC STAT + */ + setbits_le32(&mix_regs->slice_sw_ctrl, BIT(31)); + val = readl(&mix_regs->func_stat); + + /* Since PSW_STAT is 1, can't be used for power off status (SW_CTRL BIT31 set)) */ + /* Check the MEM STAT change to ensure SSAR is completed */ + while (!(val & SRC_MIX_SLICE_FUNC_STAT_MEM_STAT)) { + val = readl(&mix_regs->func_stat); + } + + /* wait few ipg clock cycles to ensure FSM done and power off status is correct */ + /* About 5 cycles at 24Mhz, 1us is enough */ + udelay(1); + } else { + /* The mix is default power on, Do mix power cycle */ + setbits_le32(&mix_regs->slice_sw_ctrl, BIT(31)); + val = readl(&mix_regs->func_stat); + while (!(val & SRC_MIX_SLICE_FUNC_STAT_PSW_STAT)) { + val = readl(&mix_regs->func_stat); + } + } + + /* power on */ + clrbits_le32(&mix_regs->slice_sw_ctrl, BIT(31)); + val = readl(&mix_regs->func_stat); + while (val & SRC_MIX_SLICE_FUNC_STAT_ISO_STAT) { + val = readl(&mix_regs->func_stat); + } + + return 0; +} + +void disable_isolation(void) +{ + struct src_general_regs *global_regs = (struct src_general_regs *)(ulong)SRC_GLOBAL_RBASE; + /* clear isolation for usbphy, dsi, csi*/ + writel(0x0, &global_regs->sp_iso_ctrl); +} + +void soc_power_init(void) +{ + mix_power_init(MIX_PD_MEDIAMIX); + mix_power_init(MIX_PD_MLMIX); + + disable_isolation(); +} + +bool m33_is_rom_kicked(void) +{ + struct blk_ctrl_s_aonmix_regs *s_regs = + (struct blk_ctrl_s_aonmix_regs *)BLK_CTRL_S_ANOMIX_BASE_ADDR; + + if (!(readl(&s_regs->m33_cfg) & BCTRL_S_ANOMIX_M33_CPU_WAIT_MASK)) + return true; + + return false; +} + +int m33_prepare(void) +{ + struct src_mix_slice_regs *mix_regs = + (struct src_mix_slice_regs *)(ulong)(SRC_IPS_BASE_ADDR + 0x400 * (SRC_MIX_CM33 + 1)); + struct src_general_regs *global_regs = + (struct src_general_regs *)(ulong)SRC_GLOBAL_RBASE; + struct blk_ctrl_s_aonmix_regs *s_regs = + (struct blk_ctrl_s_aonmix_regs *)BLK_CTRL_S_ANOMIX_BASE_ADDR; + u32 val; + + /* Allow NS to set it */ + setbits_le32(&mix_regs->authen_ctrl, BIT(9)); + + if (m33_is_rom_kicked()) + return -EPERM; + + /* Release reset of M33 */ + setbits_le32(&global_regs->scr, BIT(0)); + + /* Check the reset released in M33 MIX func stat */ + val = readl(&mix_regs->func_stat); + while (!(val & SRC_MIX_SLICE_FUNC_STAT_RST_STAT)) { + val = readl(&mix_regs->func_stat); + } + + /* Because CPUWAIT is default set, so M33 won't run, Clear it when kick M33 */ + /* Release Sentinel TROUT */ + ahab_release_m33_trout(); + + /* Mask WDOG1 IRQ from A55, we use it for M33 reset */ + setbits_le32(&s_regs->ca55_irq_mask[1], BIT(6)); + + /* Turn on WDOG1 clock */ + ccm_lpcg_on(CCGR_WDG1, 1); + + /* Set sentinel LP handshake for M33 reset */ + setbits_le32(&s_regs->lp_handshake[0], BIT(6)); + + /* Clear M33 TCM for ECC */ + memset((void *)(ulong)0x201e0000, 0, 0x40000); + + return 0; +} diff --git a/arch/arm/mach-imx/imx9/trdc.c b/arch/arm/mach-imx/imx9/trdc.c new file mode 100644 index 0000000000..bb137a7912 --- /dev/null +++ b/arch/arm/mach-imx/imx9/trdc.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2022 NXP + */ + +#include <common.h> +#include <log.h> +#include <asm/io.h> +#include <asm/types.h> +#include <asm/arch/imx-regs.h> +#include <asm/arch/sys_proto.h> +#include <div64.h> +#include <asm/mach-imx/s400_api.h> +#include <asm/mach-imx/mu_hal.h> + +#define DID_NUM 16 +#define MBC_MAX_NUM 4 +#define MRC_MAX_NUM 2 +#define MBC_NUM(HWCFG) ((HWCFG >> 16) & 0xF) +#define MRC_NUM(HWCFG) ((HWCFG >> 24) & 0x1F) + +struct mbc_mem_dom { + u32 mem_glbcfg[4]; + u32 nse_blk_index; + u32 nse_blk_set; + u32 nse_blk_clr; + u32 nsr_blk_clr_all; + u32 memn_glbac[8]; + /* The upper only existed in the beginning of each MBC */ + u32 mem0_blk_cfg_w[64]; + u32 mem0_blk_nse_w[16]; + u32 mem1_blk_cfg_w[8]; + u32 mem1_blk_nse_w[2]; + u32 mem2_blk_cfg_w[8]; + u32 mem2_blk_nse_w[2]; + u32 mem3_blk_cfg_w[8]; + u32 mem3_blk_nse_w[2];/*0x1F0, 0x1F4 */ + u32 reserved[2]; +}; + +struct mrc_rgn_dom { + u32 mrc_glbcfg[4]; + u32 nse_rgn_indirect; + u32 nse_rgn_set; + u32 nse_rgn_clr; + u32 nse_rgn_clr_all; + u32 memn_glbac[8]; + /* The upper only existed in the beginning of each MRC */ + u32 rgn_desc_words[16][2]; /* 16 regions at max, 2 words per region */ + u32 rgn_nse; + u32 reserved2[15]; +}; + +struct mda_inst { + u32 mda_w[8]; +}; + +struct trdc_mgr { + u32 trdc_cr; + u32 res0[59]; + u32 trdc_hwcfg0; + u32 trdc_hwcfg1; + u32 res1[450]; + struct mda_inst mda[8]; + u32 res2[15808]; +}; + +struct trdc_mbc { + struct mbc_mem_dom mem_dom[DID_NUM]; +}; + +struct trdc_mrc { + struct mrc_rgn_dom mrc_dom[DID_NUM]; +}; + + +int trdc_mda_set_cpu(ulong trdc_reg, u32 mda_inst, u32 mda_reg, u8 sa, u8 dids, u8 did, u8 pe, u8 pidm, u8 pid) +{ + struct trdc_mgr *trdc_base = (struct trdc_mgr *)trdc_reg; + u32 *mda_w = &trdc_base->mda[mda_inst].mda_w[mda_reg]; + u32 val = readl(mda_w); + + if (val & BIT(29)) /* non-cpu */ + return -EINVAL; + + val = BIT(31) | ((pid & 0x3f) << 16) | ((pidm & 0x3f) << 8) | ((pe & 0x3) << 6) | ((sa & 0x3) << 14) | ((dids & 0x3) << 4) | (did & 0xf); + + writel(val, mda_w); + + return 0; +} + +int trdc_mda_set_noncpu(ulong trdc_reg, u32 mda_inst, u32 mda_reg, bool did_bypass, u8 sa, u8 pa, u8 did) +{ + struct trdc_mgr *trdc_base = (struct trdc_mgr *)trdc_reg; + u32 *mda_w = &trdc_base->mda[mda_inst].mda_w[mda_reg]; + u32 val = readl(mda_w); + + if (!(val & BIT(29))) /* cpu */ + return -EINVAL; + + val = BIT(31) | ((sa & 0x3) << 6) | ((pa & 0x3) << 4) | (did & 0xf); + if (did_bypass) + val |= BIT(8); + + writel(val, mda_w); + + return 0; +} + +static ulong trdc_get_mbc_base(ulong trdc_reg, u32 mbc_x) +{ + struct trdc_mgr *trdc_base = (struct trdc_mgr *)trdc_reg; + u32 mbc_num = MBC_NUM(trdc_base->trdc_hwcfg0); + + if (mbc_x >= mbc_num) + return 0; + + return trdc_reg + 0x10000 + 0x2000 * mbc_x; +} + +static ulong trdc_get_mrc_base(ulong trdc_reg, u32 mrc_x) +{ + struct trdc_mgr *trdc_base = (struct trdc_mgr *)trdc_reg; + u32 mbc_num = MBC_NUM(trdc_base->trdc_hwcfg0); + u32 mrc_num = MRC_NUM(trdc_base->trdc_hwcfg0); + + if (mrc_x >= mrc_num) + return 0; + + return trdc_reg + 0x10000 + 0x2000 * mbc_num + 0x1000 * mrc_x; +} + +int trdc_mbc_set_control(ulong trdc_reg, u32 mbc_x, u32 glbac_id, u32 glbac_val) +{ + struct trdc_mbc *mbc_base = (struct trdc_mbc *)trdc_get_mbc_base(trdc_reg, mbc_x); + struct mbc_mem_dom *mbc_dom; + + if (mbc_base == 0 || glbac_id >= 8) + return -EINVAL; + + /* only first dom has the glbac */ + mbc_dom = &mbc_base->mem_dom[0]; + + debug("mbc 0x%lx\n", (ulong)mbc_dom); + + writel(glbac_val, &mbc_dom->memn_glbac[glbac_id]); + + return 0; +} + +int trdc_mbc_blk_config(ulong trdc_reg, u32 mbc_x, u32 dom_x, u32 mem_x, u32 blk_x, bool sec_access, u32 glbac_id) +{ + struct trdc_mbc *mbc_base = (struct trdc_mbc *)trdc_get_mbc_base(trdc_reg, mbc_x); + struct mbc_mem_dom *mbc_dom; + u32 *cfg_w, *nse_w; + u32 index, offset, val; + + if (mbc_base == 0 || glbac_id >= 8) + return -EINVAL; + + mbc_dom = &mbc_base->mem_dom[dom_x]; + + debug("mbc 0x%lx\n", (ulong)mbc_dom); + + switch (mem_x) { + case 0: + cfg_w = &mbc_dom->mem0_blk_cfg_w[blk_x / 8]; + nse_w = &mbc_dom->mem0_blk_nse_w[blk_x / 32]; + break; + case 1: + cfg_w = &mbc_dom->mem1_blk_cfg_w[blk_x / 8]; + nse_w = &mbc_dom->mem1_blk_nse_w[blk_x / 32]; + break; + case 2: + cfg_w = &mbc_dom->mem2_blk_cfg_w[blk_x / 8]; + nse_w = &mbc_dom->mem2_blk_nse_w[blk_x / 32]; + break; + case 3: + cfg_w = &mbc_dom->mem3_blk_cfg_w[blk_x / 8]; + nse_w = &mbc_dom->mem3_blk_nse_w[blk_x / 32]; + break; + default: + return -EINVAL; + }; + + index = blk_x % 8; + offset = index * 4; + + val = readl((void __iomem *)cfg_w); + + val &= ~(0xFU << offset); + + /* MBC0-3 + * Global 0, 0x7777 secure pri/user read/write/execute, S400 has already set it. + * So select MBC0_MEMN_GLBAC0 + */ + if (sec_access) { + val |= ((0x0 | (glbac_id & 0x7)) << offset); + writel(val, (void __iomem *)cfg_w); + } else { + val |= ((0x8 | (glbac_id & 0x7)) << offset); /* nse bit set */ + writel(val, (void __iomem *)cfg_w); + } + + return 0; +} + +int trdc_mrc_set_control(ulong trdc_reg, u32 mrc_x, u32 glbac_id, u32 glbac_val) +{ + struct trdc_mrc *mrc_base = (struct trdc_mrc *)trdc_get_mrc_base(trdc_reg, mrc_x); + struct mrc_rgn_dom *mrc_dom; + + if (mrc_base == 0 || glbac_id >= 8) + return -EINVAL; + + /* only first dom has the glbac */ + mrc_dom = &mrc_base->mrc_dom[0]; + + debug("mrc_dom 0x%lx\n", (ulong)mrc_dom); + + writel(glbac_val, &mrc_dom->memn_glbac[glbac_id]); + + return 0; +} + +int trdc_mrc_region_config(ulong trdc_reg, u32 mrc_x, u32 dom_x, u32 addr_start, u32 addr_end, bool sec_access, u32 glbac_id) +{ + struct trdc_mrc *mrc_base = (struct trdc_mrc *)trdc_get_mrc_base(trdc_reg, mrc_x); + struct mrc_rgn_dom *mrc_dom; + u32 *desc_w; + u32 start, end; + u32 i, free = 8;; + bool vld, hit = false; + + if (mrc_base == 0 || glbac_id >= 8) + return -EINVAL; + + mrc_dom = &mrc_base->mrc_dom[dom_x]; + + addr_start &= ~0x3fff; + addr_end &= ~0x3fff; + + debug("mrc_dom 0x%lx\n", (ulong)mrc_dom); + + for (i = 0; i < 8; i++) { + desc_w = &mrc_dom->rgn_desc_words[i][0]; + + debug("desc_w 0x%lx\n", (ulong)desc_w); + + start = readl((void __iomem *)desc_w) & (~0x3fff); + end = readl((void __iomem *)(desc_w + 1)); + vld = end & 0x1; + end = end & (~0x3fff); + + if (start == 0 && end == 0 && !vld && free >= 8) + free = i; + + /* Check all the region descriptors, even overlap */ + if (addr_start >= end || addr_end <= start || !vld) + continue; + + /* MRC0,1 + * Global 0, 0x7777 secure pri/user read/write/execute, S400 has already set it. + * So select MRCx_MEMN_GLBAC0 + */ + if (sec_access) { + writel(start | (glbac_id & 0x7), (void __iomem *)desc_w); + writel(end | 0x1, (void __iomem *)(desc_w + 1)); + } else { + writel(start | (glbac_id & 0x7), (void __iomem *)desc_w); + writel(end | 0x1 | 0x10, (void __iomem *)(desc_w + 1)); + } + + if (addr_start >= start && addr_end <= end) + hit = true; + } + + if (!hit) { + if (free >= 8) + return -EFAULT; + + desc_w = &mrc_dom->rgn_desc_words[free][0]; + + debug("free desc_w 0x%lx\n", (ulong)desc_w); + debug("[0x%x] [0x%x]\n", addr_start | (glbac_id & 0x7), addr_end | 0x1); + + if (sec_access) { + writel(addr_start | (glbac_id & 0x7), (void __iomem *)desc_w); + writel(addr_end | 0x1, (void __iomem *)(desc_w + 1)); + } else { + writel(addr_start | (glbac_id & 0x7), (void __iomem *)desc_w); + writel((addr_end | 0x1 | 0x10), (void __iomem *)(desc_w + 1)); + } + } + + return 0; +} + +bool trdc_mrc_enabled(ulong trdc_base) +{ + return (!!(readl((void __iomem *)trdc_base) & 0x8000)); +} + +bool trdc_mbc_enabled(ulong trdc_base) +{ + return (!!(readl((void __iomem *)trdc_base) & 0x4000)); +} + +int release_rdc(u8 xrdc) +{ + ulong s_mu_base = 0x47520000UL; + struct sentinel_msg msg; + int ret; + u32 rdc_id; + + switch (xrdc) { + case 0: + rdc_id = 0x74; + break; + case 1: + rdc_id = 0x78; + break; + case 2: + rdc_id = 0x82; + break; + case 3: + rdc_id = 0x86; + break; + default: + return -EINVAL; + } + + msg.version = AHAB_VERSION; + msg.tag = AHAB_CMD_TAG; + msg.size = 2; + msg.command = ELE_RELEASE_RDC_REQ; + msg.data[0] = (rdc_id << 8) | 0x2; /* A55 */ + + mu_hal_init(s_mu_base); + mu_hal_sendmsg(s_mu_base, 0, *((u32 *)&msg)); + mu_hal_sendmsg(s_mu_base, 1, msg.data[0]); + + ret = mu_hal_receivemsg(s_mu_base, 0, (u32 *)&msg); + if (!ret) { + ret = mu_hal_receivemsg(s_mu_base, 1, &msg.data[0]); + if (!ret) { + if ((msg.data[0] & 0xff) == 0xd6) + return 0; + } + + return -EIO; + } + + return ret; +} + +void trdc_early_init(void) +{ + int ret = 0, i; + ret |= release_rdc(0); + ret |= release_rdc(2); + ret |= release_rdc(1); + ret |= release_rdc(3); + + if (!ret) { + /* Set OCRAM to RWX for secure, when OEM_CLOSE, the image is RX only */ + trdc_mbc_set_control(0x49010000, 3, 0, 0x7700); + + for (i = 0; i < 40; i++) { + trdc_mbc_blk_config(0x49010000, 3, 3, 0, i, true, 0); + } + + for (i = 0; i < 40; i++) { + trdc_mbc_blk_config(0x49010000, 3, 3, 1, i, true, 0); + } + + for (i = 0; i < 40; i++) { + trdc_mbc_blk_config(0x49010000, 3, 0, 0, i, true, 0); + } + + for (i = 0; i < 40; i++) { + trdc_mbc_blk_config(0x49010000, 3, 0, 1, i, true, 0); + } + } +} + +void trdc_init(void) +{ + /* TRDC mega */ + if (trdc_mrc_enabled(0x49010000)) { + + /* DDR */ + trdc_mrc_set_control(0x49010000, 0, 0, 0x7777); + + /* S400*/ + trdc_mrc_region_config(0x49010000, 0, 0, 0x80000000, 0xFFFFFFFF, false, 0); + + /* MTR */ + trdc_mrc_region_config(0x49010000, 0, 1, 0x80000000, 0xFFFFFFFF, false, 0); + + /* M33 */ + trdc_mrc_region_config(0x49010000, 0, 2, 0x80000000, 0xFFFFFFFF, false, 0); + + /* A55*/ + trdc_mrc_region_config(0x49010000, 0, 3, 0x80000000, 0xFFFFFFFF, false, 0); + + /* For USDHC1 to DDR, USDHC1 is default force to non-secure */ + trdc_mrc_region_config(0x49010000, 0, 5, 0x80000000, 0xFFFFFFFF, false, 0); + + /* For USDHC2 to DDR, USDHC2 is default force to non-secure */ + trdc_mrc_region_config(0x49010000, 0, 6, 0x80000000, 0xFFFFFFFF, false, 0); + + /* eDMA */ + trdc_mrc_region_config(0x49010000, 0, 7, 0x80000000, 0xFFFFFFFF, false, 0); + + /*CoreSight, TestPort*/ + trdc_mrc_region_config(0x49010000, 0, 8, 0x80000000, 0xFFFFFFFF, false, 0); + + /* DAP */ + trdc_mrc_region_config(0x49010000, 0, 9, 0x80000000, 0xFFFFFFFF, false, 0); + + /*SoC masters */ + trdc_mrc_region_config(0x49010000, 0, 10, 0x80000000, 0xFFFFFFFF, false, 0); + + /*USB*/ + trdc_mrc_region_config(0x49010000, 0, 11, 0x80000000, 0xFFFFFFFF, false, 0); + + } +} + +#if DEBUG +int trdc_mbc_control_dump(ulong trdc_reg, u32 mbc_x, u32 glbac_id) +{ + struct trdc_mbc *mbc_base = (struct trdc_mbc *)trdc_get_mbc_base(trdc_reg, mbc_x); + struct mbc_mem_dom *mbc_dom; + + if (mbc_base == 0 || glbac_id >= 8) + return -EINVAL; + + /* only first dom has the glbac */ + mbc_dom = &mbc_base->mem_dom[0]; + + printf("mbc_dom %u glbac %u: 0x%x\n", mbc_x, glbac_id, readl(&mbc_dom->memn_glbac[glbac_id])); + + return 0; +} + +int trdc_mbc_mem_dump(ulong trdc_reg, u32 mbc_x, u32 dom_x, u32 mem_x, u32 word) +{ + struct trdc_mbc *mbc_base = (struct trdc_mbc *)trdc_get_mbc_base(trdc_reg, mbc_x); + struct mbc_mem_dom *mbc_dom; + u32 *cfg_w; + + if (mbc_base == 0) + return -EINVAL; + + mbc_dom = &mbc_base->mem_dom[dom_x]; + + switch (mem_x) { + case 0: + cfg_w = &mbc_dom->mem0_blk_cfg_w[word]; + break; + case 1: + cfg_w = &mbc_dom->mem1_blk_cfg_w[word]; + break; + case 2: + cfg_w = &mbc_dom->mem2_blk_cfg_w[word]; + break; + case 3: + cfg_w = &mbc_dom->mem3_blk_cfg_w[word]; + break; + default: + return -EINVAL; + }; + + printf("mbc_dom %u dom %u mem %u word %u: 0x%x\n", mbc_x, dom_x, mem_x, word, readl((void __iomem *)cfg_w)); + + return 0; +} + +int trdc_mrc_control_dump(ulong trdc_reg, u32 mrc_x, u32 glbac_id) +{ + struct trdc_mrc *mrc_base = (struct trdc_mrc *)trdc_get_mrc_base(trdc_reg, mrc_x); + struct mrc_rgn_dom *mrc_dom; + + if (mrc_base == 0 || glbac_id >= 8) + return -EINVAL; + + /* only first dom has the glbac */ + mrc_dom = &mrc_base->mrc_dom[0]; + + printf("mrc_dom %u glbac %u: 0x%x\n", mrc_x, glbac_id, readl(&mrc_dom->memn_glbac[glbac_id])); + + return 0; +} + +void trdc_dump(void) +{ + u32 i; + printf("TRDC AONMIX MBC\n"); + + trdc_mbc_control_dump(0x44270000, 0, 0); + trdc_mbc_control_dump(0x44270000, 1, 0); + + for (i = 0; i < 11; i++) { + trdc_mbc_mem_dump(0x44270000, 0, 3, 0, i); + } + for (i = 0; i < 1; i++) { + trdc_mbc_mem_dump(0x44270000, 0, 3, 1, i); + } + + for (i = 0; i < 4; i++) { + trdc_mbc_mem_dump(0x44270000, 1, 3, 0, i); + } + for (i = 0; i < 4; i++) { + trdc_mbc_mem_dump(0x44270000, 1, 3, 1, i); + } + + printf("TRDC WAKEUP MBC\n"); + + trdc_mbc_control_dump(0x42460000, 0, 0); + trdc_mbc_control_dump(0x42460000, 1, 0); + + for (i = 0; i < 15; i++) { + trdc_mbc_mem_dump(0x42460000, 0, 3, 0, i); + } + trdc_mbc_mem_dump(0x42460000, 0, 3, 1, 0); + trdc_mbc_mem_dump(0x42460000, 0, 3, 2, 0); + + for (i = 0; i < 2; i++) { + trdc_mbc_mem_dump(0x42460000, 1, 3, 0, i); + } + trdc_mbc_mem_dump(0x42460000, 1, 3, 1, 0); + trdc_mbc_mem_dump(0x42460000, 1, 3, 2, 0); + trdc_mbc_mem_dump(0x42460000, 1, 3, 3, 0); + + printf("TRDC NICMIX MBC\n"); + + trdc_mbc_control_dump(0x49010000, 0, 0); + trdc_mbc_control_dump(0x49010000, 1, 0); + trdc_mbc_control_dump(0x49010000, 2, 0); + trdc_mbc_control_dump(0x49010000, 3, 0); + + for (i = 0; i < 7; i++) { + trdc_mbc_mem_dump(0x49010000, 0, 3, 0, i); + } + + for (i = 0; i < 2; i++) { + trdc_mbc_mem_dump(0x49010000, 0, 3, 1, i); + } + + for (i = 0; i < 5; i++) { + trdc_mbc_mem_dump(0x49010000, 0, 3, 2, i); + } + + for (i = 0; i < 6; i++) { + trdc_mbc_mem_dump(0x49010000, 0, 3, 3, i); + } + + for (i = 0; i < 1; i++) { + trdc_mbc_mem_dump(0x49010000, 1, 3, 0, i); + } + + for (i = 0; i < 1; i++) { + trdc_mbc_mem_dump(0x49010000, 1, 3, 1, i); + } + + for (i = 0; i < 3; i++) { + trdc_mbc_mem_dump(0x49010000, 1, 3, 2, i); + } + + for (i = 0; i < 3; i++) { + trdc_mbc_mem_dump(0x49010000, 1, 3, 3, i); + } + + for (i = 0; i < 2; i++) { + trdc_mbc_mem_dump(0x49010000, 2, 3, 0, i); + } + + for (i = 0; i < 2; i++) { + trdc_mbc_mem_dump(0x49010000, 2, 3, 1, i); + } + + for (i = 0; i < 5; i++) { + trdc_mbc_mem_dump(0x49010000, 3, 3, 0, i); + } + + for (i = 0; i < 5; i++) { + trdc_mbc_mem_dump(0x49010000, 3, 3, 1, i); + } +} +#endif |