diff options
Diffstat (limited to 'drivers/clk/imx/clk-imx6q.c')
-rw-r--r-- | drivers/clk/imx/clk-imx6q.c | 543 |
1 files changed, 502 insertions, 41 deletions
diff --git a/drivers/clk/imx/clk-imx6q.c b/drivers/clk/imx/clk-imx6q.c index d83f6221f1b0..57e14143f5b5 100644 --- a/drivers/clk/imx/clk-imx6q.c +++ b/drivers/clk/imx/clk-imx6q.c @@ -1,6 +1,7 @@ /* - * Copyright 2011-2013 Freescale Semiconductor, Inc. + * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. * Copyright 2011 Linaro Ltd. + * Copyright 2017 NXP. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License @@ -31,7 +32,8 @@ static const char *periph_clk2_sels[] = { "pll3_usb_otg", "osc", "osc", "dummy", static const char *periph2_clk2_sels[] = { "pll3_usb_otg", "pll2_bus", }; static const char *periph_sels[] = { "periph_pre", "periph_clk2", }; static const char *periph2_sels[] = { "periph2_pre", "periph2_clk2", }; -static const char *axi_sels[] = { "periph", "pll2_pfd2_396m", "periph", "pll3_pfd1_540m", }; +static const char *axi_alt_sels[] = { "pll2_pfd2_396m", "pll3_pfd1_540m", }; +static const char *axi_sels[] = { "periph", "axi_alt_sel", }; static const char *audio_sels[] = { "pll4_audio_div", "pll3_pfd2_508m", "pll3_pfd3_454m", "pll3_usb_otg", }; static const char *gpu_axi_sels[] = { "axi", "ahb", }; static const char *pre_axi_sels[] = { "axi", "ahb", }; @@ -41,15 +43,17 @@ static const char *gpu3d_core_sels[] = { "mmdc_ch0_axi", "pll3_usb_otg", "pll2_p static const char *gpu3d_shader_sels[] = { "mmdc_ch0_axi", "pll3_usb_otg", "pll2_pfd1_594m", "pll3_pfd0_720m", }; static const char *ipu_sels[] = { "mmdc_ch0_axi", "pll2_pfd2_396m", "pll3_120m", "pll3_pfd1_540m", }; static const char *ldb_di_sels[] = { "pll5_video_div", "pll2_pfd0_352m", "pll2_pfd2_396m", "mmdc_ch1_axi", "pll3_usb_otg", }; +static const char *ldb_di0_div_sels[] = { "ldb_di0_div_3_5", "ldb_di0_div_7", }; +static const char *ldb_di1_div_sels[] = { "ldb_di1_div_3_5", "ldb_di1_div_7", }; static const char *ipu_di_pre_sels[] = { "mmdc_ch0_axi", "pll3_usb_otg", "pll5_video_div", "pll2_pfd0_352m", "pll2_pfd2_396m", "pll3_pfd1_540m", }; static const char *ipu1_di0_sels[] = { "ipu1_di0_pre", "dummy", "dummy", "ldb_di0", "ldb_di1", }; static const char *ipu1_di1_sels[] = { "ipu1_di1_pre", "dummy", "dummy", "ldb_di0", "ldb_di1", }; static const char *ipu2_di0_sels[] = { "ipu2_di0_pre", "dummy", "dummy", "ldb_di0", "ldb_di1", }; static const char *ipu2_di1_sels[] = { "ipu2_di1_pre", "dummy", "dummy", "ldb_di0", "ldb_di1", }; -static const char *ipu1_di0_sels_2[] = { "ipu1_di0_pre", "dummy", "dummy", "ldb_di0_podf", "ldb_di1_podf", }; -static const char *ipu1_di1_sels_2[] = { "ipu1_di1_pre", "dummy", "dummy", "ldb_di0_podf", "ldb_di1_podf", }; -static const char *ipu2_di0_sels_2[] = { "ipu2_di0_pre", "dummy", "dummy", "ldb_di0_podf", "ldb_di1_podf", }; -static const char *ipu2_di1_sels_2[] = { "ipu2_di1_pre", "dummy", "dummy", "ldb_di0_podf", "ldb_di1_podf", }; +static const char *ipu1_di0_sels_2[] = { "ipu1_di0_pre", "dummy", "dummy", "ldb_di0_div_sel", "ldb_di1_div_sel", }; +static const char *ipu1_di1_sels_2[] = { "ipu1_di1_pre", "dummy", "dummy", "ldb_di0_div_sel", "ldb_di1_div_sel", }; +static const char *ipu2_di0_sels_2[] = { "ipu2_di0_pre", "dummy", "dummy", "ldb_di0_div_sel", "ldb_di1_div_sel", }; +static const char *ipu2_di1_sels_2[] = { "ipu2_di1_pre", "dummy", "dummy", "ldb_di0_div_sel", "ldb_di1_div_sel", }; static const char *hsi_tx_sels[] = { "pll3_120m", "pll2_pfd2_396m", }; static const char *pcie_axi_sels[] = { "axi", "ahb", }; static const char *ssi_sels[] = { "pll3_pfd2_508m", "pll3_pfd3_454m", "pll4_audio_div", }; @@ -95,11 +99,14 @@ static const char *pll7_bypass_sels[] = { "pll7", "pll7_bypass_src", }; static struct clk *clk[IMX6QDL_CLK_END]; static struct clk_onecell_data clk_data; +static void __iomem *ccm_base; static unsigned int const clks_init_on[] __initconst = { IMX6QDL_CLK_MMDC_CH0_AXI, IMX6QDL_CLK_ROM, IMX6QDL_CLK_ARM, + IMX6QDL_CLK_OCRAM, + IMX6QDL_CLK_AXI, }; static struct clk_div_table clk_enet_ref_table[] = { @@ -156,12 +163,335 @@ static struct clk ** const uart_clks[] __initconst = { NULL }; +static int ldb_di_sel_by_clock_id(int clock_id) +{ + switch (clock_id) { + case IMX6QDL_CLK_PLL5_VIDEO_DIV: + if (clk_on_imx6q() && + imx_get_soc_revision() == IMX_CHIP_REVISION_1_0) + return -ENOENT; + return 0; + case IMX6QDL_CLK_PLL2_PFD0_352M: + return 1; + case IMX6QDL_CLK_PLL2_PFD2_396M: + return 2; + case IMX6QDL_CLK_MMDC_CH1_AXI: + return 3; + case IMX6QDL_CLK_PLL3_USB_OTG: + return 4; + default: + return -ENOENT; + } +} + +static void of_assigned_ldb_sels(struct device_node *node, + unsigned int *ldb_di0_sel, + unsigned int *ldb_di1_sel) +{ + struct of_phandle_args clkspec; + int index, rc, num_parents; + int parent, child, sel; + + num_parents = of_count_phandle_with_args(node, "assigned-clock-parents", + "#clock-cells"); + for (index = 0; index < num_parents; index++) { + rc = of_parse_phandle_with_args(node, "assigned-clock-parents", + "#clock-cells", index, &clkspec); + if (rc < 0) { + /* skip empty (null) phandles */ + if (rc == -ENOENT) + continue; + else + return; + } + if (clkspec.np != node || clkspec.args[0] >= IMX6QDL_CLK_END) { + pr_err("ccm: parent clock %d not in ccm\n", index); + return; + } + parent = clkspec.args[0]; + + rc = of_parse_phandle_with_args(node, "assigned-clocks", + "#clock-cells", index, &clkspec); + if (rc < 0) + return; + if (clkspec.np != node || clkspec.args[0] >= IMX6QDL_CLK_END) { + pr_err("ccm: child clock %d not in ccm\n", index); + return; + } + child = clkspec.args[0]; + + if (child != IMX6QDL_CLK_LDB_DI0_SEL && + child != IMX6QDL_CLK_LDB_DI1_SEL) + continue; + + sel = ldb_di_sel_by_clock_id(parent); + if (sel < 0) { + pr_err("ccm: invalid ldb_di%d parent clock: %d\n", + child == IMX6QDL_CLK_LDB_DI1_SEL, parent); + continue; + } + + if (child == IMX6QDL_CLK_LDB_DI0_SEL) + *ldb_di0_sel = sel; + if (child == IMX6QDL_CLK_LDB_DI1_SEL) + *ldb_di1_sel = sel; + } +} + +#define CCM_CCDR 0x04 +#define CCM_CCSR 0x0c +#define CCM_CS2CDR 0x2c +#define CCM_CSCDR3 0x3c +#define CCM_CCGR0 0x68 +#define CCM_CCGR3 0x74 + +#define ANATOP_PLL3_PFD 0xf0 + + +#define CCDR_MMDC_CH1_MASK BIT(16) +#define CCSR_PLL3_SW_CLK_SEL BIT(0) + +#define CS2CDR_LDB_DI0_CLK_SEL_SHIFT 9 +#define CS2CDR_LDB_DI1_CLK_SEL_SHIFT 12 + +#define OCOTP_CFG3 0x440 +#define OCOTP_CFG3_SPEED_SHIFT 16 +#define OCOTP_CFG3_SPEED_1P2GHZ 0x3 + +static void __init imx6q_mmdc_ch1_mask_handshake(void __iomem *ccm_base) +{ + unsigned int reg; + + reg = readl_relaxed(ccm_base + CCM_CCDR); + reg |= CCDR_MMDC_CH1_MASK; + writel_relaxed(reg, ccm_base + CCM_CCDR); +} + +/* + * The only way to disable the MMDC_CH1 clock is to move it to pll3_sw_clk + * via periph2_clk2_sel and then to disable pll3_sw_clk by selecting the + * bypass clock source, since there is no CG bit for mmdc_ch1. + */ +static void mmdc_ch1_disable(void __iomem *ccm_base) +{ + unsigned int reg; + + clk_set_parent(clk[IMX6QDL_CLK_PERIPH2_CLK2_SEL], + clk[IMX6QDL_CLK_PLL3_USB_OTG]); + + /* + * Handshake with mmdc_ch1 module must be masked when changing + * periph2_clk_sel. + */ + clk_set_parent(clk[IMX6QDL_CLK_PERIPH2], clk[IMX6QDL_CLK_PERIPH2_CLK2]); + + /* Disable pll3_sw_clk by selecting the bypass clock source */ + reg = readl_relaxed(ccm_base + CCM_CCSR); + reg |= CCSR_PLL3_SW_CLK_SEL; + writel_relaxed(reg, ccm_base + CCM_CCSR); +} + +static void mmdc_ch1_reenable(void __iomem *ccm_base) +{ + unsigned int reg; + + /* Enable pll3_sw_clk by disabling the bypass */ + reg = readl_relaxed(ccm_base + CCM_CCSR); + reg &= ~CCSR_PLL3_SW_CLK_SEL; + writel_relaxed(reg, ccm_base + CCM_CCSR); + + clk_set_parent(clk[IMX6QDL_CLK_PERIPH2], clk[IMX6QDL_CLK_PERIPH2_PRE]); +} + +/* + * We have to follow a strict procedure when changing the LDB clock source, + * otherwise we risk introducing a glitch that can lock up the LDB divider. + * Things to keep in mind: + * + * 1. The current and new parent clock inputs to the mux must be disabled. + * 2. The default clock input for ldb_di0/1_clk_sel is mmdc_ch1_axi, which + * has no CG bit. + * 3. pll2_pfd2_396m can not be gated if it is used as memory clock. + * 4. In the RTL implementation of the LDB_DI_CLK_SEL muxes the top four + * options are in one mux and the PLL3 option along with three unused + * inputs is in a second mux. There is a third mux with two inputs used + * to decide between the first and second 4-port mux: + * + * pll5_video_div 0 --|\ + * pll2_pfd0_352m 1 --| |_ + * pll2_pfd2_396m 2 --| | `-|\ + * mmdc_ch1_axi 3 --|/ | | + * | |-- + * pll3_usb_otg 4 --|\ | | + * 5 --| |_,-|/ + * 6 --| | + * 7 --|/ + * + * The ldb_di0/1_clk_sel[1:0] bits control both 4-port muxes at the same time. + * The ldb_di0/1_clk_sel[2] bit controls the 2-port mux. The code below + * switches the parent to the bottom mux first and then manipulates the top + * mux to ensure that no glitch will enter the divider. + */ +static void init_ldb_clks(struct device_node *np, void __iomem *ccm_base) +{ + unsigned int reg; + unsigned int sel[2][4]; + int i; + + reg = readl_relaxed(ccm_base + CCM_CS2CDR); + sel[0][0] = (reg >> CS2CDR_LDB_DI0_CLK_SEL_SHIFT) & 7; + sel[1][0] = (reg >> CS2CDR_LDB_DI1_CLK_SEL_SHIFT) & 7; + + sel[0][3] = sel[0][2] = sel[0][1] = sel[0][0]; + sel[1][3] = sel[1][2] = sel[1][1] = sel[1][0]; + + of_assigned_ldb_sels(np, &sel[0][3], &sel[1][3]); + + for (i = 0; i < 2; i++) { + /* Warn if a glitch might have been introduced already */ + if (sel[i][0] != 3) { + pr_warn("ccm: ldb_di%d_sel already changed from reset value: %d\n", + i, sel[i][0]); + } + + if (sel[i][0] == sel[i][3]) + continue; + + /* Only switch to or from pll2_pfd2_396m if it is disabled */ + if ((sel[i][0] == 2 || sel[i][3] == 2) && + (clk_get_parent(clk[IMX6QDL_CLK_PERIPH_PRE]) == + clk[IMX6QDL_CLK_PLL2_PFD2_396M])) { + pr_err("ccm: ldb_di%d_sel: couldn't disable pll2_pfd2_396m\n", + i); + sel[i][3] = sel[i][2] = sel[i][1] = sel[i][0]; + continue; + } + + /* First switch to the bottom mux */ + sel[i][1] = sel[i][0] | 4; + + /* Then configure the top mux before switching back to it */ + sel[i][2] = sel[i][3] | 4; + + pr_debug("ccm: switching ldb_di%d_sel: %d->%d->%d->%d\n", i, + sel[i][0], sel[i][1], sel[i][2], sel[i][3]); + } + + if (sel[0][0] == sel[0][3] && sel[1][0] == sel[1][3]) + return; + + mmdc_ch1_disable(ccm_base); + + for (i = 1; i < 4; i++) { + reg = readl_relaxed(ccm_base + CCM_CS2CDR); + reg &= ~((7 << CS2CDR_LDB_DI0_CLK_SEL_SHIFT) | + (7 << CS2CDR_LDB_DI1_CLK_SEL_SHIFT)); + reg |= ((sel[0][i] << CS2CDR_LDB_DI0_CLK_SEL_SHIFT) | + (sel[1][i] << CS2CDR_LDB_DI1_CLK_SEL_SHIFT)); + writel_relaxed(reg, ccm_base + CCM_CS2CDR); + } + + mmdc_ch1_reenable(ccm_base); +} + +#define CCM_ANALOG_PLL_VIDEO 0xa0 +#define CCM_ANALOG_PFD_480 0xf0 +#define CCM_ANALOG_PFD_528 0x100 + +#define PLL_ENABLE BIT(13) + +#define PFD0_CLKGATE BIT(7) +#define PFD1_CLKGATE BIT(15) +#define PFD2_CLKGATE BIT(23) +#define PFD3_CLKGATE BIT(31) + +/* + * workaround for ERR010579, when switching the clock source of IPU clock + * root in CCM. even setting CCGR3[CG0]=0x0 to gate off clock before + * switching, IPU may hang due to no IPU clock from CCM. + */ +static void __init init_ipu_clk(void __iomem *anatop_base) +{ + u32 val, origin_podf; + + /* gate off the IPU1_IPU clock */ + val = readl_relaxed(ccm_base + CCM_CCGR3); + val &= ~0x3; + writel_relaxed(val, ccm_base + CCM_CCGR3); + + /* gate off IPU DCIC1/2 clocks */ + val = readl_relaxed(ccm_base + CCM_CCGR0); + val &= ~(0xf << 24); + writel_relaxed(val, ccm_base + CCM_CCGR0); + + /* set IPU_PODF to 3'b000 */ + val = readl_relaxed(ccm_base + CCM_CSCDR3); + origin_podf = val & (0x7 << 11); + val &= ~(0x7 << 11); + writel_relaxed(val, ccm_base + CCM_CSCDR3); + + /* disable PLL3_PFD1 */ + val = readl_relaxed(anatop_base + ANATOP_PLL3_PFD); + val &= ~(0x1 << 15); + writel_relaxed(val, anatop_base + ANATOP_PLL3_PFD); + + /* switch IPU_SEL clock to PLL3_PFD1 */ + val = readl_relaxed(ccm_base + CCM_CSCDR3); + val |= (0x3 << 9); + writel_relaxed(val, ccm_base + CCM_CSCDR3); + + /* restore the IPU PODF*/ + val = readl_relaxed(ccm_base + CCM_CSCDR3); + val |= origin_podf; + writel_relaxed(val, ccm_base + CCM_CSCDR3); + + /* enable PLL3_PFD1 */ + val = readl_relaxed(anatop_base + ANATOP_PLL3_PFD); + val |= (0x1 << 15); + writel_relaxed(val, anatop_base + ANATOP_PLL3_PFD); + + /* enable IPU1_IPU clock */ + val = readl_relaxed(ccm_base + CCM_CCGR3); + val |= 0x3; + writel_relaxed(val, ccm_base + CCM_CCGR3); + + /* enable IPU DCIC1/2 clock */ + val = readl_relaxed(ccm_base + CCM_CCGR0); + val |= (0xf << 24); + writel_relaxed(val, ccm_base + CCM_CCGR0); +} + +static void disable_anatop_clocks(void __iomem *anatop_base) +{ + unsigned int reg; + + /* Make sure PLL2 PFDs 0-2 are gated */ + reg = readl_relaxed(anatop_base + CCM_ANALOG_PFD_528); + /* Cannot gate PFD2 if pll2_pfd2_396m is the parent of MMDC clock */ + if (clk_get_parent(clk[IMX6QDL_CLK_PERIPH_PRE]) == + clk[IMX6QDL_CLK_PLL2_PFD2_396M]) + reg |= PFD0_CLKGATE | PFD1_CLKGATE; + else + reg |= PFD0_CLKGATE | PFD1_CLKGATE | PFD2_CLKGATE; + writel_relaxed(reg, anatop_base + CCM_ANALOG_PFD_528); + + /* Make sure PLL3 PFDs 0-3 are gated */ + reg = readl_relaxed(anatop_base + CCM_ANALOG_PFD_480); + reg |= PFD0_CLKGATE | PFD1_CLKGATE | PFD2_CLKGATE | PFD3_CLKGATE; + writel_relaxed(reg, anatop_base + CCM_ANALOG_PFD_480); + + /* Make sure PLL5 is disabled */ + reg = readl_relaxed(anatop_base + CCM_ANALOG_PLL_VIDEO); + reg &= ~PLL_ENABLE; + writel_relaxed(reg, anatop_base + CCM_ANALOG_PLL_VIDEO); +} + static void __init imx6q_clocks_init(struct device_node *ccm_node) { struct device_node *np; - void __iomem *base; + void __iomem *anatop_base, *base; int i; - int ret; + u32 val; clk[IMX6QDL_CLK_DUMMY] = imx_clk_fixed("dummy", 0); clk[IMX6QDL_CLK_CKIL] = imx_obtain_fixed_clock("ckil", 0); @@ -172,7 +502,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_ANACLK2] = imx_obtain_fixed_clock("anaclk2", 0); np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop"); - base = of_iomap(np, 0); + anatop_base = base = of_iomap(np, 0); WARN_ON(!base); of_node_put(np); @@ -194,7 +524,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) /* type name parent_name base div_mask */ clk[IMX6QDL_CLK_PLL1] = imx_clk_pllv3(IMX_PLLV3_SYS, "pll1", "osc", base + 0x00, 0x7f); - clk[IMX6QDL_CLK_PLL2] = imx_clk_pllv3(IMX_PLLV3_GENERIC, "pll2", "osc", base + 0x30, 0x1); + clk[IMX6QDL_CLK_PLL2] = imx_clk_pllv3(IMX_PLLV3_PLL2, "pll2", "osc", base + 0x30, 0x1); clk[IMX6QDL_CLK_PLL3] = imx_clk_pllv3(IMX_PLLV3_USB, "pll3", "osc", base + 0x10, 0x3); clk[IMX6QDL_CLK_PLL4] = imx_clk_pllv3(IMX_PLLV3_AV, "pll4", "osc", base + 0x70, 0x7f); clk[IMX6QDL_CLK_PLL5] = imx_clk_pllv3(IMX_PLLV3_AV, "pll5", "osc", base + 0xa0, 0x7f); @@ -218,7 +548,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk_set_parent(clk[IMX6QDL_PLL6_BYPASS], clk[IMX6QDL_CLK_PLL6]); clk_set_parent(clk[IMX6QDL_PLL7_BYPASS], clk[IMX6QDL_CLK_PLL7]); - clk[IMX6QDL_CLK_PLL1_SYS] = imx_clk_gate("pll1_sys", "pll1_bypass", base + 0x00, 13); + clk[IMX6QDL_CLK_PLL1_SYS] = imx_clk_fixed_factor("pll1_sys", "pll1_bypass", 1, 1); clk[IMX6QDL_CLK_PLL2_BUS] = imx_clk_gate("pll2_bus", "pll2_bypass", base + 0x30, 13); clk[IMX6QDL_CLK_PLL3_USB_OTG] = imx_clk_gate("pll3_usb_otg", "pll3_bypass", base + 0x10, 13); clk[IMX6QDL_CLK_PLL4_AUDIO] = imx_clk_gate("pll4_audio", "pll4_bypass", base + 0x70, 13); @@ -293,23 +623,25 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_GPU3D_AXI] = imx_clk_fixed_factor("gpu3d_axi", "mmdc_ch0_axi_podf", 1, 1); } - clk[IMX6QDL_CLK_PLL4_POST_DIV] = clk_register_divider_table(NULL, "pll4_post_div", "pll4_audio", CLK_SET_RATE_PARENT, base + 0x70, 19, 2, 0, post_div_table, &imx_ccm_lock); - clk[IMX6QDL_CLK_PLL4_AUDIO_DIV] = clk_register_divider(NULL, "pll4_audio_div", "pll4_post_div", CLK_SET_RATE_PARENT, base + 0x170, 15, 1, 0, &imx_ccm_lock); - clk[IMX6QDL_CLK_PLL5_POST_DIV] = clk_register_divider_table(NULL, "pll5_post_div", "pll5_video", CLK_SET_RATE_PARENT, base + 0xa0, 19, 2, 0, post_div_table, &imx_ccm_lock); - clk[IMX6QDL_CLK_PLL5_VIDEO_DIV] = clk_register_divider_table(NULL, "pll5_video_div", "pll5_post_div", CLK_SET_RATE_PARENT, base + 0x170, 30, 2, 0, video_div_table, &imx_ccm_lock); + clk[IMX6QDL_CLK_PLL4_POST_DIV] = clk_register_divider_table(NULL, "pll4_post_div", "pll4_audio", CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, base + 0x70, 19, 2, 0, post_div_table, &imx_ccm_lock); + clk[IMX6QDL_CLK_PLL4_AUDIO_DIV] = clk_register_divider(NULL, "pll4_audio_div", "pll4_post_div", CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, base + 0x170, 15, 1, 0, &imx_ccm_lock); + clk[IMX6QDL_CLK_PLL5_POST_DIV] = clk_register_divider_table(NULL, "pll5_post_div", "pll5_video", CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, base + 0xa0, 19, 2, 0, post_div_table, &imx_ccm_lock); + clk[IMX6QDL_CLK_PLL5_VIDEO_DIV] = clk_register_divider_table(NULL, "pll5_video_div", "pll5_post_div", CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE, base + 0x170, 30, 2, 0, video_div_table, &imx_ccm_lock); np = ccm_node; base = of_iomap(np, 0); + ccm_base = base; WARN_ON(!base); /* name reg shift width parent_names num_parents */ clk[IMX6QDL_CLK_STEP] = imx_clk_mux("step", base + 0xc, 8, 1, step_sels, ARRAY_SIZE(step_sels)); - clk[IMX6QDL_CLK_PLL1_SW] = imx_clk_mux("pll1_sw", base + 0xc, 2, 1, pll1_sw_sels, ARRAY_SIZE(pll1_sw_sels)); - clk[IMX6QDL_CLK_PERIPH_PRE] = imx_clk_mux("periph_pre", base + 0x18, 18, 2, periph_pre_sels, ARRAY_SIZE(periph_pre_sels)); + clk[IMX6QDL_CLK_PLL1_SW] = imx_clk_mux_glitchless("pll1_sw", base + 0xc, 2, 1, pll1_sw_sels, ARRAY_SIZE(pll1_sw_sels)); + clk[IMX6QDL_CLK_PERIPH_PRE] = imx_clk_mux_bus("periph_pre", base + 0x18, 18, 2, periph_pre_sels, ARRAY_SIZE(periph_pre_sels)); clk[IMX6QDL_CLK_PERIPH2_PRE] = imx_clk_mux("periph2_pre", base + 0x18, 21, 2, periph_pre_sels, ARRAY_SIZE(periph_pre_sels)); - clk[IMX6QDL_CLK_PERIPH_CLK2_SEL] = imx_clk_mux("periph_clk2_sel", base + 0x18, 12, 2, periph_clk2_sels, ARRAY_SIZE(periph_clk2_sels)); + clk[IMX6QDL_CLK_PERIPH_CLK2_SEL] = imx_clk_mux_bus("periph_clk2_sel", base + 0x18, 12, 2, periph_clk2_sels, ARRAY_SIZE(periph_clk2_sels)); clk[IMX6QDL_CLK_PERIPH2_CLK2_SEL] = imx_clk_mux("periph2_clk2_sel", base + 0x18, 20, 1, periph2_clk2_sels, ARRAY_SIZE(periph2_clk2_sels)); - clk[IMX6QDL_CLK_AXI_SEL] = imx_clk_mux("axi_sel", base + 0x14, 6, 2, axi_sels, ARRAY_SIZE(axi_sels)); + clk[IMX6QDL_CLK_AXI_ALT_SEL] = imx_clk_mux("axi_alt_sel", base + 0x14, 7, 1, axi_alt_sels, ARRAY_SIZE(axi_alt_sels)); + clk[IMX6QDL_CLK_AXI_SEL] = imx_clk_mux_glitchless("axi_sel", base + 0x14, 6, 1, axi_sels, ARRAY_SIZE(axi_sels)); clk[IMX6QDL_CLK_ESAI_SEL] = imx_clk_mux("esai_sel", base + 0x20, 19, 2, audio_sels, ARRAY_SIZE(audio_sels)); clk[IMX6QDL_CLK_ASRC_SEL] = imx_clk_mux("asrc_sel", base + 0x30, 7, 2, audio_sels, ARRAY_SIZE(audio_sels)); clk[IMX6QDL_CLK_SPDIF_SEL] = imx_clk_mux("spdif_sel", base + 0x30, 20, 2, audio_sels, ARRAY_SIZE(audio_sels)); @@ -335,8 +667,27 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_GPU3D_SHADER_SEL] = imx_clk_mux("gpu3d_shader_sel", base + 0x18, 8, 2, gpu3d_shader_sels, ARRAY_SIZE(gpu3d_shader_sels)); clk[IMX6QDL_CLK_IPU1_SEL] = imx_clk_mux("ipu1_sel", base + 0x3c, 9, 2, ipu_sels, ARRAY_SIZE(ipu_sels)); clk[IMX6QDL_CLK_IPU2_SEL] = imx_clk_mux("ipu2_sel", base + 0x3c, 14, 2, ipu_sels, ARRAY_SIZE(ipu_sels)); - clk[IMX6QDL_CLK_LDB_DI0_SEL] = imx_clk_mux_flags("ldb_di0_sel", base + 0x2c, 9, 3, ldb_di_sels, ARRAY_SIZE(ldb_di_sels), CLK_SET_RATE_PARENT); - clk[IMX6QDL_CLK_LDB_DI1_SEL] = imx_clk_mux_flags("ldb_di1_sel", base + 0x2c, 12, 3, ldb_di_sels, ARRAY_SIZE(ldb_di_sels), CLK_SET_RATE_PARENT); + + if (clk_on_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) { + clk[IMX6QDL_CLK_LDB_DI0_SEL] = imx_clk_mux_flags("ldb_di0_sel", base + 0x2c, 9, 3, ldb_di_sels, ARRAY_SIZE(ldb_di_sels), CLK_SET_RATE_PARENT); + clk[IMX6QDL_CLK_LDB_DI1_SEL] = imx_clk_mux_flags("ldb_di1_sel", base + 0x2c, 12, 3, ldb_di_sels, ARRAY_SIZE(ldb_di_sels), CLK_SET_RATE_PARENT); + } else { + disable_anatop_clocks(anatop_base); + + imx6q_mmdc_ch1_mask_handshake(base); + + /* + * The LDB_DI0/1_SEL muxes are registered read-only due to a hardware + * bug. Set the muxes to the requested values before registering the + * ldb_di_sel clocks. + */ + init_ldb_clks(np, base); + + clk[IMX6QDL_CLK_LDB_DI0_SEL] = imx_clk_mux_ldb("ldb_di0_sel", base + 0x2c, 9, 3, ldb_di_sels, ARRAY_SIZE(ldb_di_sels)); + clk[IMX6QDL_CLK_LDB_DI1_SEL] = imx_clk_mux_ldb("ldb_di1_sel", base + 0x2c, 12, 3, ldb_di_sels, ARRAY_SIZE(ldb_di_sels)); + } + clk[IMX6QDL_CLK_LDB_DI0_DIV_SEL] = imx_clk_mux_flags("ldb_di0_div_sel", base + 0x20, 10, 1, ldb_di0_div_sels, ARRAY_SIZE(ldb_di0_div_sels), CLK_SET_RATE_PARENT); + clk[IMX6QDL_CLK_LDB_DI1_DIV_SEL] = imx_clk_mux_flags("ldb_di1_div_sel", base + 0x20, 11, 1, ldb_di1_div_sels, ARRAY_SIZE(ldb_di1_div_sels), CLK_SET_RATE_PARENT); clk[IMX6QDL_CLK_IPU1_DI0_PRE_SEL] = imx_clk_mux_flags("ipu1_di0_pre_sel", base + 0x34, 6, 3, ipu_di_pre_sels, ARRAY_SIZE(ipu_di_pre_sels), CLK_SET_RATE_PARENT); clk[IMX6QDL_CLK_IPU1_DI1_PRE_SEL] = imx_clk_mux_flags("ipu1_di1_pre_sel", base + 0x34, 15, 3, ipu_di_pre_sels, ARRAY_SIZE(ipu_di_pre_sels), CLK_SET_RATE_PARENT); clk[IMX6QDL_CLK_IPU2_DI0_PRE_SEL] = imx_clk_mux_flags("ipu2_di0_pre_sel", base + 0x38, 6, 3, ipu_di_pre_sels, ARRAY_SIZE(ipu_di_pre_sels), CLK_SET_RATE_PARENT); @@ -402,6 +753,8 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_UART_SERIAL_PODF] = imx_clk_divider("uart_serial_podf", "uart_sel", base + 0x24, 0, 6); clk[IMX6QDL_CLK_LDB_DI0_DIV_3_5] = imx_clk_fixed_factor("ldb_di0_div_3_5", "ldb_di0", 2, 7); clk[IMX6QDL_CLK_LDB_DI1_DIV_3_5] = imx_clk_fixed_factor("ldb_di1_div_3_5", "ldb_di1", 2, 7); + clk[IMX6QDL_CLK_LDB_DI0_DIV_7] = imx_clk_fixed_factor("ldb_di0_div_7", "ldb_di0", 1, 7); + clk[IMX6QDL_CLK_LDB_DI1_DIV_7] = imx_clk_fixed_factor("ldb_di1_div_7", "ldb_di1", 1, 7); } else { clk[IMX6QDL_CLK_ECSPI_ROOT] = imx_clk_divider("ecspi_root", "pll3_60m", base + 0x38, 19, 6); clk[IMX6QDL_CLK_CAN_ROOT] = imx_clk_divider("can_root", "pll3_60m", base + 0x20, 2, 6); @@ -409,6 +762,8 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_UART_SERIAL_PODF] = imx_clk_divider("uart_serial_podf", "pll3_80m", base + 0x24, 0, 6); clk[IMX6QDL_CLK_LDB_DI0_DIV_3_5] = imx_clk_fixed_factor("ldb_di0_div_3_5", "ldb_di0_sel", 2, 7); clk[IMX6QDL_CLK_LDB_DI1_DIV_3_5] = imx_clk_fixed_factor("ldb_di1_div_3_5", "ldb_di1_sel", 2, 7); + clk[IMX6QDL_CLK_LDB_DI0_DIV_7] = imx_clk_fixed_factor("ldb_di0_div_7", "ldb_di0_sel", 1, 7); + clk[IMX6QDL_CLK_LDB_DI1_DIV_7] = imx_clk_fixed_factor("ldb_di1_div_7", "ldb_di1_sel", 1, 7); } if (clk_on_imx6dl()) clk[IMX6QDL_CLK_MLB_PODF] = imx_clk_divider("mlb_podf", "mlb_sel", base + 0x18, 23, 3); @@ -475,6 +830,8 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_CAN1_SERIAL] = imx_clk_gate2("can1_serial", "can_root", base + 0x68, 16); clk[IMX6QDL_CLK_CAN2_IPG] = imx_clk_gate2("can2_ipg", "ipg", base + 0x68, 18); clk[IMX6QDL_CLK_CAN2_SERIAL] = imx_clk_gate2("can2_serial", "can_root", base + 0x68, 20); + clk[IMX6QDL_CLK_DCIC1] = imx_clk_gate2("dcic1", "ipu1_podf", base + 0x68, 24); + clk[IMX6QDL_CLK_DCIC2] = imx_clk_gate2("dcic2", "ipu2_podf", base + 0x68, 26); clk[IMX6QDL_CLK_ECSPI1] = imx_clk_gate2("ecspi1", "ecspi_root", base + 0x6c, 0); clk[IMX6QDL_CLK_ECSPI2] = imx_clk_gate2("ecspi2", "ecspi_root", base + 0x6c, 2); clk[IMX6QDL_CLK_ECSPI3] = imx_clk_gate2("ecspi3", "ecspi_root", base + 0x6c, 4); @@ -492,7 +849,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_GPU2D_CORE] = imx_clk_gate2("gpu2d_core", "gpu2d_core_podf", base + 0x6c, 24); clk[IMX6QDL_CLK_GPU3D_CORE] = imx_clk_gate2("gpu3d_core", "gpu3d_core_podf", base + 0x6c, 26); clk[IMX6QDL_CLK_HDMI_IAHB] = imx_clk_gate2("hdmi_iahb", "ahb", base + 0x70, 0); - clk[IMX6QDL_CLK_HDMI_ISFR] = imx_clk_gate2("hdmi_isfr", "mipi_core_cfg", base + 0x70, 4); + clk[IMX6QDL_CLK_HDMI_ISFR] = imx_clk_gate2("hdmi_isfr", "pll3_pfd1_540m", base + 0x70, 4); clk[IMX6QDL_CLK_I2C1] = imx_clk_gate2("i2c1", "ipg_per", base + 0x70, 6); clk[IMX6QDL_CLK_I2C2] = imx_clk_gate2("i2c2", "ipg_per", base + 0x70, 8); clk[IMX6QDL_CLK_I2C3] = imx_clk_gate2("i2c3", "ipg_per", base + 0x70, 10); @@ -585,17 +942,27 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk_data.clk_num = ARRAY_SIZE(clk); of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); + clk_register_clkdev(clk[IMX6QDL_CLK_GPT_IPG], "ipg", "imx-gpt.0"); + clk_register_clkdev(clk[IMX6QDL_CLK_GPT_IPG_PER], "per", "imx-gpt.0"); + clk_register_clkdev(clk[IMX6QDL_CLK_GPT_3M], "gpt_3m", "imx-gpt.0"); clk_register_clkdev(clk[IMX6QDL_CLK_ENET_REF], "enet_ref", NULL); - if ((imx_get_soc_revision() != IMX_CHIP_REVISION_1_0) || - clk_on_imx6dl()) { - clk_set_parent(clk[IMX6QDL_CLK_LDB_DI0_SEL], clk[IMX6QDL_CLK_PLL5_VIDEO_DIV]); - clk_set_parent(clk[IMX6QDL_CLK_LDB_DI1_SEL], clk[IMX6QDL_CLK_PLL5_VIDEO_DIV]); - } - clk_set_rate(clk[IMX6QDL_CLK_PLL3_PFD1_540M], 540000000); - if (clk_on_imx6dl()) + if (clk_on_imx6dl()) { + init_ipu_clk(anatop_base); clk_set_parent(clk[IMX6QDL_CLK_IPU1_SEL], clk[IMX6QDL_CLK_PLL3_PFD1_540M]); + clk_set_parent(clk[IMX6QDL_CLK_AXI_ALT_SEL], clk[IMX6QDL_CLK_PLL3_PFD1_540M]); + clk_set_parent(clk[IMX6QDL_CLK_AXI_SEL], clk[IMX6QDL_CLK_AXI_ALT_SEL]); + /* set epdc/pxp axi clock to 200Mhz */ + clk_set_parent(clk[IMX6QDL_CLK_IPU2_SEL], clk[IMX6QDL_CLK_PLL2_PFD2_396M]); + clk_set_rate(clk[IMX6QDL_CLK_IPU2], 200000000); + } else { + /* set eim_slow to 132Mhz for i.MX6Q */ + if (clk_on_imx6q()) + clk_set_rate(clk[IMX6QDL_CLK_EIM_SLOW], 132000000); + clk_set_parent(clk[IMX6QDL_CLK_IPU1_SEL], clk[IMX6QDL_CLK_MMDC_CH0_AXI]); + clk_set_parent(clk[IMX6QDL_CLK_IPU2_SEL], clk[IMX6QDL_CLK_MMDC_CH0_AXI]); + } clk_set_parent(clk[IMX6QDL_CLK_IPU1_DI0_PRE_SEL], clk[IMX6QDL_CLK_PLL5_VIDEO_DIV]); clk_set_parent(clk[IMX6QDL_CLK_IPU1_DI1_PRE_SEL], clk[IMX6QDL_CLK_PLL5_VIDEO_DIV]); @@ -606,6 +973,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk_set_parent(clk[IMX6QDL_CLK_IPU2_DI0_SEL], clk[IMX6QDL_CLK_IPU2_DI0_PRE]); clk_set_parent(clk[IMX6QDL_CLK_IPU2_DI1_SEL], clk[IMX6QDL_CLK_IPU2_DI1_PRE]); + /* * The gpmi needs 100MHz frequency in the EDO/Sync mode, * We can not get the 100MHz from the pll2_pfd0_352m. @@ -613,30 +981,93 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) */ clk_set_parent(clk[IMX6QDL_CLK_ENFC_SEL], clk[IMX6QDL_CLK_PLL2_PFD2_396M]); - for (i = 0; i < ARRAY_SIZE(clks_init_on); i++) - clk_prepare_enable(clk[clks_init_on[i]]); - - if (IS_ENABLED(CONFIG_USB_MXS_PHY)) { - clk_prepare_enable(clk[IMX6QDL_CLK_USBPHY1_GATE]); - clk_prepare_enable(clk[IMX6QDL_CLK_USBPHY2_GATE]); + /* gpu clock initilazation */ + /* + * On mx6dl, 2d core clock sources(sel, podf) is from 3d + * shader core clock, but 3d shader clock multiplexer of + * mx6dl is different. For instance the equivalent of + * pll2_pfd_594M on mx6q is pll2_pfd_528M on mx6dl. + * Make a note here. + */ + if (clk_on_imx6dl()) { + clk_set_parent(clk[IMX6QDL_CLK_GPU3D_SHADER_SEL], clk[IMX6QDL_CLK_PLL2_PFD1_594M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_SHADER], 528000000); + /* for mx6dl, change gpu3d_core parent to 594_PFD*/ + clk_set_parent(clk[IMX6QDL_CLK_GPU3D_CORE_SEL], clk[IMX6QDL_CLK_PLL2_PFD1_594M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_CORE], 528000000); + } else if (clk_on_imx6q()) { + if (imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) { + clk_set_parent(clk[IMX6QDL_CLK_GPU3D_SHADER_SEL], clk[IMX6QDL_CLK_PLL3_PFD0_720M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_SHADER], 720000000); + clk_set_parent(clk[IMX6QDL_CLK_GPU3D_CORE_SEL], clk[IMX6QDL_CLK_PLL2_PFD1_594M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_CORE], 594000000); + clk_set_parent(clk[IMX6QDL_CLK_GPU2D_CORE_SEL], clk[IMX6QDL_CLK_PLL3_PFD0_720M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU2D_CORE], 720000000); + } else { + clk_set_parent(clk[IMX6QDL_CLK_GPU3D_SHADER_SEL], clk[IMX6QDL_CLK_PLL2_PFD1_594M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_SHADER], 594000000); + clk_set_parent(clk[IMX6QDL_CLK_GPU3D_CORE_SEL], clk[IMX6QDL_CLK_MMDC_CH0_AXI]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_CORE], 528000000); + clk_set_parent(clk[IMX6QDL_CLK_GPU2D_CORE_SEL], clk[IMX6QDL_CLK_PLL3_USB_OTG]); + imx_clk_set_rate(clk[IMX6QDL_CLK_GPU2D_CORE], 480000000); + } } /* * Let's initially set up CLKO with OSC24M, since this configuration * is widely used by imx6q board designs to clock audio codec. */ - ret = clk_set_parent(clk[IMX6QDL_CLK_CKO2_SEL], clk[IMX6QDL_CLK_OSC]); - if (!ret) - ret = clk_set_parent(clk[IMX6QDL_CLK_CKO], clk[IMX6QDL_CLK_CKO2]); - if (ret) - pr_warn("failed to set up CLKO: %d\n", ret); + imx_clk_set_parent(clk[IMX6QDL_CLK_CKO2_SEL], clk[IMX6QDL_CLK_OSC]); + imx_clk_set_parent(clk[IMX6QDL_CLK_CKO], clk[IMX6QDL_CLK_CKO2]); /* Audio-related clocks configuration */ clk_set_parent(clk[IMX6QDL_CLK_SPDIF_SEL], clk[IMX6QDL_CLK_PLL3_PFD3_454M]); /* All existing boards with PCIe use LVDS1 */ - if (IS_ENABLED(CONFIG_PCI_IMX6)) + if (IS_ENABLED(CONFIG_PCI_IMX6)) { clk_set_parent(clk[IMX6QDL_CLK_LVDS1_SEL], clk[IMX6QDL_CLK_SATA_REF_100M]); + np = of_find_compatible_node(NULL, NULL, "snps,dw-pcie"); + /* external oscillator is used or not. */ + if (of_property_read_u32(np, "ext_osc", &val) < 0) + val = 0; + /* + * imx6qp sabresd revb board has the external osc used by pcie + * - pll6 should be set bypass mode later in driver. + * - lvds_clk1 should be selected as pll6 bypass src, set here. + */ + if (clk_on_imx6qp() && val == 1) + clk_set_parent(clk[IMX6QDL_PLL6_BYPASS_SRC], clk[IMX6QDL_CLK_LVDS1_IN]); + } + + /* + * Enable clocks only after both parent and rate are all initialized + * as needed + */ + for (i = 0; i < ARRAY_SIZE(clks_init_on); i++) + imx_clk_prepare_enable(clk[clks_init_on[i]]); + + if (IS_ENABLED(CONFIG_USB_MXS_PHY)) { + imx_clk_prepare_enable(clk[IMX6QDL_CLK_USBPHY1_GATE]); + imx_clk_prepare_enable(clk[IMX6QDL_CLK_USBPHY2_GATE]); + } + + /*Set enet_ref clock to 125M to supply for RGMII tx_clk */ + clk_set_rate(clk[IMX6QDL_CLK_ENET_REF], 125000000); + +#ifdef CONFIG_MX6_VPU_352M + /* + * If VPU 352M is enabled, then PLL2_PDF2 need to be + * set to 352M, cpufreq will be disabled as VDDSOC/PU + * need to be at highest voltage, scaling cpu freq is + * not saving any power, and busfreq will be also disabled + * as the PLL2_PFD2 is not at default freq, in a word, + * all modules that sourceing clk from PLL2_PFD2 will + * be impacted. + */ + imx_clk_set_rate(clk[IMX6QDL_CLK_PLL2_PFD2_396M], 352000000); + clk_set_parent(clk[IMX6QDL_CLK_VPU_AXI_SEL], clk[IMX6QDL_CLK_PLL2_PFD2_396M]); + pr_info("VPU 352M is enabled!\n"); +#endif /* * Initialize the GPU clock muxes, so that the maximum specified clock @@ -657,5 +1088,35 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) } imx_register_uart_clocks(uart_clks); + + /* + * for i.MX6QP with speeding grading set to 1.2GHz, + * VPU should run at 396MHz. + */ + if (clk_on_imx6q() && imx_get_soc_revision() >= IMX_CHIP_REVISION_2_0) { + np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp"); + WARN_ON(!np); + + base = of_iomap(np, 0); + WARN_ON(!base); + + /* + * SPEED_GRADING[1:0] defines the max speed of ARM: + * 2b'11: 1200000000Hz; + * 2b'10: 996000000Hz; + * 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz. + * 2b'00: 792000000Hz; + * We need to set the max speed of ARM according to fuse map. + */ + val = readl_relaxed(base + OCOTP_CFG3); + val >>= OCOTP_CFG3_SPEED_SHIFT; + val &= 0x3; + if (val == OCOTP_CFG3_SPEED_1P2GHZ) { + imx_clk_set_parent(clk[IMX6QDL_CLK_VPU_AXI_SEL], clk[IMX6QDL_CLK_PLL2_PFD2_396M]); + imx_clk_set_rate(clk[IMX6QDL_CLK_VPU_AXI_PODF], 396000000); + pr_info("VPU frequency set to 396MHz!\n"); + } + iounmap(base); + } } CLK_OF_DECLARE(imx6q, "fsl,imx6q-ccm", imx6q_clocks_init); |