Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5d283b08 authored by Fabio Estevam's avatar Fabio Estevam Committed by Shawn Guo
Browse files

clk: imx6: Fix procedure to switch the parent of LDB_DI_CLK

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 using the assigned-clocks and
assigned-clock-parents properties of the ccm device tree node:

        &clks {
                assigned-clocks = <&clks IMX6QDL_CLK_LDB_DI0_SEL>,
                                  <&clks IMX6QDL_CLK_LDB_DI1_SEL>;
                assigned-clock-parents = <&clks IMX6QDL_CLK_MMDC_CH1_AXI>,
                                         <&clks IMX6QDL_CLK_PLL5_VIDEO_DIV>;
        };

The issue is explained in detail in EB821 ("LDB Clock Switch Procedure &
i.MX6 Asynchronous Clock Switching Guidelines") [1].

[1] http://www.nxp.com/files/32bit/doc/eng_bulletin/EB821.pdf



Signed-off-by: default avatarRanjani Vaidyanathan <Ranjani.Vaidyanathan@nxp.com>
Signed-off-by: default avatarFabio Estevam <fabio.estevam@nxp.com>
Signed-off-by: default avatarPhilipp Zabel <p.zabel@pengutronix.de>
Reviewed-by: default avatarAkshay Bhat <akshay.bhat@timesys.com>
Tested-by Joshua Clayton <stillcompiling@gmail.com>
Tested-by: default avatarCharles Kang <Charles.Kang@advantech.com.tw>
Signed-off-by: default avatarShawn Guo <shawnguo@kernel.org>
parent 03d576f2
Loading
Loading
Loading
Loading
+259 −5
Original line number Diff line number Diff line
@@ -156,9 +156,90 @@ 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 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

static void __init imx6q_mmdc_ch1_mask_handshake(void __iomem *ccm_base)
{
@@ -169,10 +250,173 @@ static void __init imx6q_mmdc_ch1_mask_handshake(void __iomem *ccm_base)
	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)

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;

@@ -185,7 +429,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);

	/* Audio/video PLL post dividers do not work on i.MX6q revision 1.0 */
@@ -310,8 +554,6 @@ static void __init imx6q_clocks_init(struct device_node *ccm_node)
	base = of_iomap(np, 0);
	WARN_ON(!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));
	clk[IMX6QDL_CLK_PLL1_SW]          = imx_clk_mux("pll1_sw",	    base + 0xc,  2,  1, pll1_sw_sels,      ARRAY_SIZE(pll1_sw_sels));
@@ -345,6 +587,18 @@ 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));

	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_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);