diff options
author | Ranjani Vaidyanathan <ra5478@freescale.com> | 2015-04-29 10:28:06 -0500 |
---|---|---|
committer | Nitin Garg <nitin.garg@freescale.com> | 2015-09-17 09:23:00 -0500 |
commit | 3f575c4967001c239634565c1327f4cc1a30b2c4 (patch) | |
tree | 329d10a60943533ce3b7ad0958613a1f8119de9d /arch/arm/mach-imx/clk-imx6q.c | |
parent | bbb48bbc88284f29c6980ee70373f91c7c86ff42 (diff) |
MLK-10782-3 ARM:imx6qdl: Initialize LDB_DI_CLK parent at boot
Due to incorrect placement of the clock gate cell in the ldb_di[x]_clk tree, the glitchy parent mux
of ldb_di[x]_clk can cause a glitch to enter the ldb_di_ipu_div divider. If the divider gets locked
up, no ldb_di[x]_clk is generated, and the LVDS display will hang when the ipu_di_clk is sourced
from ldb_di_clk.
To fix the problem, both the new and current parent of the ldb_di_clk should be disabled before the
switch. This patch ensures that correct steps are followed when ldb_di_clk parent is switched in
the beginning of boot. The glitchy muxes are then registered as read-only. The clock parent can be
selected by adding an entry shown below in the board device tree file:
&clks {
fsl,ldb-di0-parent = <&clks IMX6QDL_CLK_PLL2_PFD0_352M>;
fsl,ldb-di1-parent = <&clks IMX6QDL_CLK_PLL2_PFD0_352M>;
};
Signed-off-by: Ranjani Vaidyanathan <Ranjani.Vaidyanathan@freescale.com>
Signed-off-by: Fabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Diffstat (limited to 'arch/arm/mach-imx/clk-imx6q.c')
-rw-r--r-- | arch/arm/mach-imx/clk-imx6q.c | 259 |
1 files changed, 231 insertions, 28 deletions
diff --git a/arch/arm/mach-imx/clk-imx6q.c b/arch/arm/mach-imx/clk-imx6q.c index afd6fcb953a8..94db33ae52c1 100644 --- a/arch/arm/mach-imx/clk-imx6q.c +++ b/arch/arm/mach-imx/clk-imx6q.c @@ -98,6 +98,8 @@ 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 *anatop_base; +static void __iomem *ccm_base; static unsigned int const clks_init_on[] __initconst = { IMX6QDL_CLK_MMDC_CH0_AXI, @@ -139,16 +141,204 @@ static unsigned int share_count_prg0; static unsigned int share_count_prg1; #define CCM_CCDR 0x04 +#define CCM_CCSR 0x0c +#define CCM_CBCMR 0x18 +#define CCM_CS2CDR 0x2c #define CCDR_MMDC_CH1_MASK BIT(16) +#define CCSR_PLL3_SW_CLK_SEL BIT(0) +#define CBCMR_PRE_PERIPH_CLK_SEL_MASK 0xC0000 -static void __init imx6q_mmdc_ch1_mask_handshake(void __iomem *ccm_base) +static int ldb_di_sel_by_clock_id(int clock_id) +{ + switch (clock_id) { + case IMX6QDL_CLK_PLL5_VIDEO_DIV: + if (cpu_is_imx6q() && (imx_get_soc_revision() == IMX_CHIP_REVISION_1_0)) + return -ENOENT; + else + 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; + } +} + +/* + * Read the LDBID[0/1] parents from the device tree. + * If no entry is found in the device tree, the default + * parent will be set to IMX6QDL_CLK_PLL2_PFD0_352M + */ +static void of_assigned_ldb_sels(struct device_node *node, + int *ldb_di0_sel, int *ldb_di1_sel) +{ + struct of_phandle_args clkspec; + int index = 0, rc; + + rc = of_parse_phandle_with_args(node, "fsl,ldb-di0-parent", + "#clock-cells", index, &clkspec); + if (rc < 0) { + /* skip empty (null) phandles */ + if (rc != -ENOENT) + *ldb_di0_sel = IMX6QDL_CLK_PLL2_PFD0_352M; + } else { + *ldb_di0_sel = ldb_di_sel_by_clock_id(clkspec.args[0]); + if (*ldb_di0_sel == -ENOENT) + *ldb_di0_sel = IMX6QDL_CLK_PLL2_PFD0_352M; + } + rc = of_parse_phandle_with_args(node, "fsl,ldb-di1-parent", + "#clock-cells", index, &clkspec); + if (rc < 0) { + /* skip empty (null) phandles */ + if (rc != -ENOENT) + *ldb_di1_sel = IMX6QDL_CLK_PLL2_PFD0_352M; + } else { + *ldb_di1_sel = ldb_di_sel_by_clock_id(clkspec.args[0]); + if (*ldb_di1_sel == -ENOENT) + *ldb_di1_sel = IMX6QDL_CLK_PLL2_PFD0_352M; + } +} + +static void __init mmdc_ch1_handshake(bool enable) +{ + unsigned int reg; + + if (enable) { + reg = readl_relaxed(ccm_base + CCM_CCDR); + reg &= ~CCDR_MMDC_CH1_MASK; + writel_relaxed(reg, ccm_base + CCM_CCDR); + } else { + 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) +{ + 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) +{ + 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]); +} + +/* + * Need to follow a strict procedure when changing the LDB + * clock, else we can introduce a glitch. Things to keep in + * mind: + * 1. The current and new parent clocks must be disabled. + * 2. The default clock for ldb_dio_clk is mmdc_ch1 which has + * no CG bit. + * 3. In the RTL implementation of the LDB_DI_CLK_SEL mux + * the top four options are in one mux and the PLL3 option along + * with another option is in the second mux. There is third mux + * used to decide between the first and second mux. + * The code below switches the parent to the bottom mux first + * and then manipulates the top mux. This ensures that no glitch + * will enter the divider. + */ +static void init_ldb_clks(struct device_node *np) +{ + unsigned int reg; + int ldb_di0_sel[4] = { 0 }; + int ldb_di1_sel[4] = { 0 }; + int i; + + reg = readl_relaxed(ccm_base + CCM_CS2CDR); + ldb_di0_sel[0] = (reg >> 9) & 7; + ldb_di1_sel[0] = (reg >> 12) & 7; + + of_assigned_ldb_sels(np, &ldb_di0_sel[3], &ldb_di1_sel[3]); + + if (ldb_di0_sel[0] == ldb_di0_sel[3] && + ldb_di1_sel[0] == ldb_di1_sel[3]) + return; + + if (ldb_di0_sel[0] != 3 || ldb_di1_sel[0] != 3) + pr_warn("ccm: ldb_di_sel already changed from reset value\n"); + + if (ldb_di0_sel[0] > 3 || ldb_di1_sel[0] > 3 || + ldb_di0_sel[3] > 3 || ldb_di1_sel[3] > 3) { + pr_err("ccm: ldb_di_sel workaround only for top mux\n"); + return; + } + + ldb_di0_sel[1] = ldb_di0_sel[0] | 4; /* Ensure that lower mux is selected. */ + ldb_di0_sel[2] = ldb_di0_sel[3] | 4; /* Change the upper mux while lower mux is selected. */ + ldb_di1_sel[1] = ldb_di1_sel[0] | 4; + ldb_di1_sel[2] = ldb_di1_sel[3] | 4; + + mmdc_ch1_handshake(false); + mmdc_ch1_disable(); + + for (i = 1; i < 4; i++) { + reg = readl_relaxed(ccm_base + CCM_CS2CDR); + reg &= ~((7 << 9) | (7 << 12)); + reg |= ((ldb_di0_sel[i] << 9) | (ldb_di1_sel[i] << 12)); + writel_relaxed(reg, ccm_base + CCM_CS2CDR); + } + + mmdc_ch1_reenable(); + mmdc_ch1_handshake(true); +} + +static void disable_anatop_clocks(void) { u32 reg; - reg = readl_relaxed(ccm_base + CCM_CCDR); - reg |= CCDR_MMDC_CH1_MASK; - writel_relaxed(reg, ccm_base + CCM_CCDR); + /* Make sure PFDs are disabled at boot. */ + reg = readl_relaxed(anatop_base + 0x100); + /* Cannot gate pll2_pfd2_396m if its the parent of MMDC clock */ + if (clk_get_parent(clk[IMX6QDL_CLK_PERIPH_PRE]) == + clk[IMX6QDL_CLK_PLL2_PFD2_396M]) + reg |= 0x00008080; + else + reg |= 0x00808080; + writel_relaxed(reg, anatop_base + 0x100); + + /* Disable PLL3 PFDs. */ + reg = readl_relaxed(anatop_base + 0xf0); + reg |= 0x80808080; + writel_relaxed(reg, anatop_base + 0xf0); + + /* Make sure PLL5 (video PLL) is disabled */ + reg = readl_relaxed(anatop_base + 0xa0); + reg &= ~(1 << 13); + writel_relaxed(reg, anatop_base + 0xa0); } static void __init imx6q_clocks_init(struct device_node *ccm_node) @@ -167,6 +357,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-anatop"); base = of_iomap(np, 0); + anatop_base = base; WARN_ON(!base); /* Audio/video PLL post dividers do not work on i.MX6q revision 1.0 */ @@ -285,10 +476,10 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) np = ccm_node; base = of_iomap(np, 0); + ccm_base = base; WARN_ON(!base); imx6q_pm_set_ccm_base(base); - imx6q_mmdc_ch1_mask_handshake(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)); @@ -303,6 +494,27 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) 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)); + /* name parent_name reg shift width busy: reg, shift */ + clk[IMX6QDL_CLK_AXI] = imx_clk_busy_divider("axi", "axi_sel", base + 0x14, 16, 3, base + 0x48, 0); + clk[IMX6QDL_CLK_MMDC_CH0_AXI] = imx_clk_busy_divider("mmdc_ch0_axi", "periph", base + 0x14, 19, 3, base + 0x48, 4); + if (cpu_is_imx6q() && imx_get_soc_revision() == IMX_CHIP_REVISION_2_0) { + clk[IMX6QDL_CLK_MMDC_CH1_AXI_CG] = imx_clk_gate("mmdc_ch1_axi_cg", "periph2", base + 0x4, 18); + clk[IMX6QDL_CLK_MMDC_CH1_AXI] = imx_clk_busy_divider("mmdc_ch1_axi", "mmdc_ch1_axi_cg", base + 0x14, 3, 3, base + 0x48, 2); + } else { + clk[IMX6QDL_CLK_MMDC_CH1_AXI] = imx_clk_busy_divider("mmdc_ch1_axi", "periph2", base + 0x14, 3, 3, base + 0x48, 2); + } + clk[IMX6QDL_CLK_ARM] = imx_clk_busy_divider("arm", "pll1_sw", base + 0x10, 0, 3, base + 0x48, 16); + clk[IMX6QDL_CLK_AHB] = imx_clk_busy_divider("ahb", "periph", base + 0x14, 10, 3, base + 0x48, 1); + + /* name parent_name reg shift width */ + clk[IMX6QDL_CLK_PERIPH_CLK2] = imx_clk_divider("periph_clk2", "periph_clk2_sel", base + 0x14, 27, 3); + clk[IMX6QDL_CLK_PERIPH2_CLK2] = imx_clk_divider("periph2_clk2", "periph2_clk2_sel", base + 0x14, 0, 3); + clk[IMX6QDL_CLK_IPG] = imx_clk_divider("ipg", "ahb", base + 0x14, 8, 2); + + /* name reg shift width busy: reg, shift parent_names num_parents */ + clk[IMX6QDL_CLK_PERIPH] = imx_clk_busy_mux("periph", base + 0x14, 25, 1, base + 0x48, 5, periph_sels, ARRAY_SIZE(periph_sels)); + clk[IMX6QDL_CLK_PERIPH2] = imx_clk_busy_mux("periph2", base + 0x14, 26, 1, base + 0x48, 3, periph2_sels, ARRAY_SIZE(periph2_sels)); + if ((cpu_is_imx6q() && imx_get_soc_revision() == IMX_CHIP_REVISION_2_0) || cpu_is_imx6dl()) { clk[IMX6QDL_CLK_GPU2D_AXI] = imx_clk_fixed_factor("gpu2d_axi", "mmdc_ch0_axi", 1, 1); clk[IMX6QDL_CLK_GPU3D_AXI] = imx_clk_fixed_factor("gpu3d_axi", "mmdc_ch0_axi", 1, 1); @@ -324,8 +536,20 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) } 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_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)); + if (cpu_is_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 { + /* + * 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. + */ + disable_anatop_clocks(); + init_ldb_clks(np); + 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); @@ -373,14 +597,7 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_CKO2_SEL] = imx_clk_mux("cko2_sel", base + 0x60, 16, 5, cko2_sels, ARRAY_SIZE(cko2_sels)); clk[IMX6QDL_CLK_CKO] = imx_clk_mux("cko", base + 0x60, 8, 1, cko_sels, ARRAY_SIZE(cko_sels)); - /* name reg shift width busy: reg, shift parent_names num_parents */ - clk[IMX6QDL_CLK_PERIPH] = imx_clk_busy_mux("periph", base + 0x14, 25, 1, base + 0x48, 5, periph_sels, ARRAY_SIZE(periph_sels)); - clk[IMX6QDL_CLK_PERIPH2] = imx_clk_busy_mux("periph2", base + 0x14, 26, 1, base + 0x48, 3, periph2_sels, ARRAY_SIZE(periph2_sels)); - /* name parent_name reg shift width */ - clk[IMX6QDL_CLK_PERIPH_CLK2] = imx_clk_divider("periph_clk2", "periph_clk2_sel", base + 0x14, 27, 3); - clk[IMX6QDL_CLK_PERIPH2_CLK2] = imx_clk_divider("periph2_clk2", "periph2_clk2_sel", base + 0x14, 0, 3); - clk[IMX6QDL_CLK_IPG] = imx_clk_divider("ipg", "ahb", base + 0x14, 8, 2); clk[IMX6QDL_CLK_ESAI_PRED] = imx_clk_divider("esai_pred", "esai_sel", base + 0x28, 9, 3); clk[IMX6QDL_CLK_ESAI_PODF] = imx_clk_divider("esai_podf", "esai_pred", base + 0x28, 25, 3); clk[IMX6QDL_CLK_ASRC_PRED] = imx_clk_divider("asrc_pred", "asrc_sel", base + 0x30, 12, 3); @@ -439,18 +656,6 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) clk[IMX6QDL_CLK_CKO1_PODF] = imx_clk_divider("cko1_podf", "cko1_sel", base + 0x60, 4, 3); clk[IMX6QDL_CLK_CKO2_PODF] = imx_clk_divider("cko2_podf", "cko2_sel", base + 0x60, 21, 3); - /* name parent_name reg shift width busy: reg, shift */ - clk[IMX6QDL_CLK_AXI] = imx_clk_busy_divider("axi", "axi_sel", base + 0x14, 16, 3, base + 0x48, 0); - clk[IMX6QDL_CLK_MMDC_CH0_AXI] = imx_clk_busy_divider("mmdc_ch0_axi", "periph", base + 0x14, 19, 3, base + 0x48, 4); - if (cpu_is_imx6q() && imx_get_soc_revision() == IMX_CHIP_REVISION_2_0) { - clk[IMX6QDL_CLK_MMDC_CH1_AXI_CG] = imx_clk_gate("mmdc_ch1_axi_cg", "periph2", base + 0x4, 18); - clk[IMX6QDL_CLK_MMDC_CH1_AXI] = imx_clk_busy_divider("mmdc_ch1_axi", "mmdc_ch1_axi_cg", base + 0x14, 3, 3, base + 0x48, 2); - } else { - clk[IMX6QDL_CLK_MMDC_CH1_AXI] = imx_clk_busy_divider("mmdc_ch1_axi", "periph2", base + 0x14, 3, 3, base + 0x48, 2); - } - clk[IMX6QDL_CLK_ARM] = imx_clk_busy_divider("arm", "pll1_sw", base + 0x10, 0, 3, base + 0x48, 16); - clk[IMX6QDL_CLK_AHB] = imx_clk_busy_divider("ahb", "periph", base + 0x14, 10, 3, base + 0x48, 1); - /* name parent_name reg shift */ clk[IMX6QDL_CLK_APBH_DMA] = imx_clk_gate2("apbh_dma", "usdhc3", base + 0x68, 4); clk[IMX6QDL_CLK_ASRC] = imx_clk_gate2_shared("asrc", "asrc_podf", base + 0x68, 6, &share_count_asrc); @@ -584,7 +789,6 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) imx_clk_set_parent(clk[IMX6QDL_CLK_LDB_DI0_SEL], clk[IMX6QDL_CLK_PLL2_PFD0_352M]); imx_clk_set_parent(clk[IMX6QDL_CLK_LDB_DI1_SEL], clk[IMX6QDL_CLK_PLL2_PFD0_352M]); } - /* ipu clock initialization */ imx_clk_set_parent(clk[IMX6QDL_CLK_IPU1_DI0_PRE_SEL], clk[IMX6QDL_CLK_PLL5_VIDEO_DIV]); imx_clk_set_parent(clk[IMX6QDL_CLK_IPU1_DI1_PRE_SEL], clk[IMX6QDL_CLK_PLL5_VIDEO_DIV]); @@ -639,7 +843,6 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node) imx_clk_set_rate(clk[IMX6QDL_CLK_GPU3D_CORE], 594000000); imx_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 { imx_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); |