IMX6DL Lvds pixelclock 深入详解

写在前面的话:   

        嵌入式系统中有两个比较难搞的问题, 一个是电源,一个是Clock。随着现在电源管理芯片的成熟,我们将会越来越少地关注到电源的配置和设定(但管理应用还是比较广泛的)。 Clock系统是一个嵌入式产品的命脉,Soc 复杂的Clock 常常会让大家望而却步, 另外还有Soc厂商大都已经完善了Soc Clock 的配置, 这让我们在移植过程中 更是不长接触soc clock 子系统。   

        本章专注于imx6dl soc 的LVDS pixel clock 的深入探讨,结合imx6 TRM & source code, 还原了一个真实的LVDS pixel clock 的渊源。明确了文章表达的主旨后,你可以通过操作LVDS/ IPU 相关register 来debug LVDS pixelclock 的相关Issue。如果你对LVDS & 内核clk 子系统 有一定的了解,你可以从头开始看起,每一步都是笔者分析的心路历程。 当然你也可以从最后一张图看起,依次往前推,了解了每张图的含义,你也就基本了解了 imx6 lvds clock 的渊源。

        谨以此文献给 被imx6 LVDS pixelclock 缠绕的人们,希望对你们有用, 祝好运!

 

1. ldb_di0  clock 的起源;

1) 在imx6q 中ldb_di0_sel (mux)  ->  ldb_di0 (gate) -> ldb_di0_div_3_5"/"ldb_di0_div_7" -> ldb_di0_div_sel -> (ipu_di0_sel)

IMX6DL Lvds pixelclock 深入详解_第1张图片

2)在其他soc上,

IMX6DL Lvds pixelclock 深入详解_第2张图片

ldb_di0_div_sel 是一个mux clk, 负责 3.5/7 分频, 其parent 为ldb_di0_div_3_5", "ldb_di0_div_7" 的固定分频因子。(事实上二者为1)

ldb_di0 <- ldb_di0_div_sel  <-  ldb_di0_div_3_5"/"ldb_di0_div_7"  <- ldb_di0_sel

IMX6DL Lvds pixelclock 深入详解_第3张图片

 

2. Ldb_di0_sel 的parent 那里指定:

在dts 中:

&clks {
	fsl,ldb-di0-parent = <&clks IMX6QDL_CLK_PLL2_PFD0_352M>;
	fsl,ldb-di1-parent = <&clks IMX6QDL_CLK_PLL2_PFD0_352M>;
};

在源码中clk-imx6q.c 中

static void init_ldb_clks(struct device_node *np)
{
	..........

	of_assigned_ldb_sels(np, &ldb_di0_sel[3], &ldb_di1_sel[3]);

	。。。。。。
   /* 这段代码, 是LDB_DI0_SEL 的parent 指定为PLL2_PFD0_352M */
	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);
	}
.......
}

至此我们明白了时钟的起源:

1) imx6q version2 

PLL2_PFD0_352M-> ldb_di0_sel (mux)  ->  ldb_di0 (gate)

2)  other

PLL2_PFD0_352M -> ldb_di0_sel -> ldb_di0_div_3_5"/"ldb_di0_div_7"-> ldb_di0_div_sel -> ldb_di0

 

3. IPU 中涉及的Clock:

IPU_DI0 , IPU_DI0_SEL 及LDB_DI0 之间的关系:

IPU_DI0 是一个gate,CG1

IMX6DL Lvds pixelclock 深入详解_第4张图片

至此, 我们就有一种可能的通路了:

ldb_di0-> IPU_DI0_SEL -> IPU_DI0

 

4. IPU_DI0_CLK_ROOT 的去向:

IMX6DL Lvds pixelclock 深入详解_第5张图片

再看 datasheet 的描述:

IMX6DL Lvds pixelclock 深入详解_第6张图片

如此,便可以通过该寄存器选择 DI 的Clock。

IMX6DL Lvds pixelclock 深入详解_第7张图片

该寄存器说明 最终的clock 是图4-3 出来的Clock  经过div 分频得到的。

static int ldb_setup(struct mxc_dispdrv_handle *mddh,
		     struct fb_info *fbi)
{
............................	
#ifdef CONFIG_ARCH_ADVANTECH
	ldb_di_parent = ldb->spl_mode ? ldb->div_3_5_clk[chno] : ldb->div_7_clk[chno];
#endif

	if (ldb->clk_fixup) {
		/*
		 * ldb_di_sel_parent(plls) -> ldb_di_sel -> ldb_di[chno] ->
		 *
		 *     -> div_3_5[chno] ->
		 * -> |                   |-> div_sel[chno] -> di[id]
		 *     ->  div_7[chno] ->
		 */
   /* For IMX6Q*/
#ifdef CONFIG_ARCH_ADVANTECH
		clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent);
#endif
   /*ldb->di_clk[id] == IPU_DI0_SEL, 也即是将ldb_di 的clk 输出给ipu 的DI0_SEL */
		clk_set_parent(ldb->di_clk[id], ldb->div_sel_clk[chno]);
	} else {
		/*
		 * ldb_di_sel_parent(plls) -> ldb_di_sel ->
		 *
		 *     -> div_3_5[chno] ->
		 * -> |                   |-> div_sel[chno] ->
		 *     ->  div_7[chno] ->
		 *
		 * -> ldb_di[chno] -> di[id]
		 */
   /*ldb->di_clk[id] == IPU_DI0_SEL, 也即是将ldb_di 的clk 输出给ipu 的DI0_SEL */
		clk_set_parent(ldb->di_clk[id], ldb->ldb_di_clk[chno]);

#ifdef CONFIG_ARCH_ADVANTECH
		clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent); 
#endif
	}

#ifndef CONFIG_ARCH_ADVANTECH
	ldb_di_parent = ldb->spl_mode ? ldb->div_3_5_clk[chno] :
			ldb->div_7_clk[chno];
	clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent);
#endif

	..........
}

如此,图3-1 中的ldb_di0_ipu clock 就输出给IPU_DI0_SEL了。

 

5. LVDS timing 的生成

我们看一个ipu 的函数

int32_t ipu_init_sync_panel(struct ipu_soc *ipu, int disp, uint32_t pixel_clk,
			    uint16_t width, uint16_t height,
			    uint32_t pixel_fmt,
			    uint16_t h_start_width, uint16_t h_sync_width,
			    uint16_t h_end_width, uint16_t v_start_width,
			    uint16_t v_sync_width, uint16_t v_end_width,
			    uint32_t v_to_h_sync, ipu_di_signal_cfg_t sig)
{
	..........................

	/* Init clocking */
	dev_dbg(ipu->dev, "pixel clk = %d\n", pixel_clk);
   /* ipu->di_clk_sel[disp] 为图3-1 中的IPU_DI0_SEL */
	di_parent = clk_get_parent(ipu->di_clk_sel[disp]);
	if (!di_parent) {
		dev_err(ipu->dev, "get di clk parent fail\n");
		return -EINVAL;
	}
	ldb_di0_clk = clk_get(ipu->dev, "ldb_di0");
	if (IS_ERR(ldb_di0_clk)) {
		dev_err(ipu->dev, "clk_get di0 failed");
		return PTR_ERR(ldb_di0_clk);
	}
	ldb_di1_clk = clk_get(ipu->dev, "ldb_di1");
	if (IS_ERR(ldb_di1_clk)) {
		dev_err(ipu->dev, "clk_get di1 failed");
		return PTR_ERR(ldb_di1_clk);
	}
	if (!strcmp(__clk_get_name(di_parent), __clk_get_name(ldb_di0_clk)) ||
		!strcmp(__clk_get_name(di_parent), __clk_get_name(ldb_di1_clk))) {
		/* if di clk parent is tve/ldb, then keep it;*/
		dev_dbg(ipu->dev, "use special clk parent\n");
   /* IPU_DI0_SEL’s clock source is from LDB_DI0, then, 
   System will use IPU_DI as the pixelclock’s source  */
		ret = clk_set_parent(ipu->pixel_clk_sel[disp], ipu->di_clk[disp]);
		if (ret) {
			dev_err(ipu->dev, "set pixel clk error:%d\n", ret);
			return ret;
		}
		clk_put(ldb_di0_clk);
		clk_put(ldb_di1_clk);
	} else {
   /* IPU_DI0_SEL’s clock source is  not  from LDB_DI */
		/* try ipu clk first*/
		dev_dbg(ipu->dev, "try ipu internal clk\n");
   /* pixel_clk 系列起源请参考chapter 6. 
     IPU_DI0_SEL 不使用LDB_DI 作为时钟源时,首先尝试使用IPU_CLK 作为pixeclock
          的时钟源。
   */
		ret = clk_set_parent(ipu->pixel_clk_sel[disp], ipu->ipu_clk);
		if (ret) {
			dev_err(ipu->dev, "set pixel clk error:%d\n", ret);
			return ret;
		}
		rounded_pixel_clk = clk_round_rate(ipu->pixel_clk[disp], pixel_clk);
		dev_dbg(ipu->dev, "rounded pix clk:%d\n", rounded_pixel_clk);
		/*
		 * we will only use 1/2 fraction for ipu clk,
		 * so if the clk rate is not fit, try ext clk.
		 */
		if (!sig.int_clk &&
			((rounded_pixel_clk >= pixel_clk + pixel_clk/200) ||
			(rounded_pixel_clk <= pixel_clk - pixel_clk/200))) {
			dev_dbg(ipu->dev, "try ipu ext di clk\n");
   /* 如果不使用内部clk 做pixelclock 时钟源时, 再尝试使用外部时钟源即
   IPU_DI0  作为pixelclock 的时钟源. */
			rounded_pixel_clk =
				clk_round_rate(ipu->di_clk[disp], pixel_clk);
			ret = clk_set_rate(ipu->di_clk[disp],
						rounded_pixel_clk);
			if (ret) {
				dev_err(ipu->dev,
					"set di clk rate error:%d\n", ret);
				return ret;
			}
			dev_dbg(ipu->dev, "di clk:%d\n", rounded_pixel_clk);
			ret = clk_set_parent(ipu->pixel_clk_sel[disp],
						ipu->di_clk[disp]);
			if (ret) {
				dev_err(ipu->dev,
					"set pixel clk parent error:%d\n", ret);
				return ret;
			}
		}
	}
   /* pixel_clk 系列起源请参考chapter 6. */
	rounded_pixel_clk = clk_round_rate(ipu->pixel_clk[disp], pixel_clk);
	dev_dbg(ipu->dev, "round pixel clk:%d\n", rounded_pixel_clk);
	ret = clk_set_rate(ipu->pixel_clk[disp], rounded_pixel_clk);
	if (ret) {
		dev_err(ipu->dev, "set pixel clk rate error:%d\n", ret);
		return ret;
	}
	............................
}

6. IPU pixelcloclk 的来源及家族

static int ipu_clk_setup_enable(struct ipu_soc *ipu)
{
	....................................

	pixel_clk_parents[0] = pixel_clk_parent0;
	pixel_clk_parents[1] = pixel_clk_parent1;

	for (di = 0; di < 2; di++) {
		snprintf(pixel_clk_sel, sizeof(pixel_clk_sel),
				"ipu%u_pclk%u_sel", ipu_id, di);
		snprintf(pixel_clk_parent0, sizeof(pixel_clk_parent0),
				"ipu%u", ipu_id);
		snprintf(pixel_clk_parent1, sizeof(pixel_clk_parent1),
				"ipu%u_di%u", ipu_id, di);
/* ipux_pclkn_sel 的clock 源有 ipu clock 和 ipux-di, 如图4-3, 那个寄存器就是ipux_pclkn_sel.  */
		clk = clk_register_mux_pix_clk(ipu->dev, pixel_clk_sel,
				(const char **)pixel_clk_parents,
				ARRAY_SIZE(pixel_clk_parents),
				0, ipu->id, di, 0);
		if (IS_ERR(clk)) {
			dev_err(ipu->dev, "di%u mux clk register failed\n", di);
			return PTR_ERR(clk);
		}
		ipu->pixel_clk_sel[di] = clk;

   /* ipu%u_pclk%u_div 请参照chapter.7 的描述 */
		snprintf(pixel_clk_div, sizeof(pixel_clk_div),
				"ipu%u_pclk%u_div", ipu_id, di);
		clk = clk_register_div_pix_clk(ipu->dev, pixel_clk_div,
					pixel_clk_sel, 0, ipu->id, di, 0);
		if (IS_ERR(clk)) {
			dev_err(ipu->dev, "di%u div clk register failed\n", di);
			return PTR_ERR(clk);
		}
   /* ipu%u_pclk%u 请参照chapter.8 的描述 */
   
		snprintf(pixel_clk, sizeof(pixel_clk),
				"ipu%u_pclk%u", ipu_id, di);
		ipu->pixel_clk[di] = clk_register_gate_pix_clk(ipu->dev,
					pixel_clk, pixel_clk_div,
					CLK_SET_RATE_PARENT, ipu->id, di, 0);
		if (IS_ERR(ipu->pixel_clk[di])) {
			dev_err(ipu->dev,
					"di%u gate clk register failed\n", di);
			return PTR_ERR(ipu->pixel_clk[di]);
		}
   /* 默认将ipuclk 作为pixelclock 的输入源,后面会更换*/
		ret = clk_set_parent(ipu->pixel_clk_sel[di], ipu->ipu_clk);
		if (ret) {
			dev_err(ipu->dev, "pixel clk set parent failed\n");
			return ret;
		}

		snprintf(di_clk, sizeof(di_clk), "di%u", di);
		ipu->di_clk[di] = devm_clk_get(ipu->dev, di_clk);
		if (IS_ERR(ipu->di_clk[di])) {
			dev_err(ipu->dev, "di%u clk get failed\n", di);
			return PTR_ERR(ipu->di_clk[di]);
		}

		snprintf(di_clk_sel, sizeof(di_clk_sel), "di%u_sel", di);
		ipu->di_clk_sel[di] = devm_clk_get(ipu->dev, di_clk_sel);
		if (IS_ERR(ipu->di_clk_sel[di])) {
			dev_err(ipu->dev, "di%u sel clk get failed\n", di);
			return PTR_ERR(ipu->di_clk_sel[di]);
		}
	}

	return 0;
}

7.  ipu%u_pclk%u_div 为何物?

static int _ipu_pixel_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
			    unsigned long parent_clk_rate)
{
	.............................
	ipu_di_write(ipu, di_div->di_id, (u32)div, DI_BS_CLKGEN0);

	return 0;
}

DI_BS_CLKGEN0为何物? 其实就是图4-4中描述的寄存器。真相大白,ipu%u_pclk%u_div 就是这个寄存器。

8.  ipu%u_pclk%u 为何物?

Chapter6 代码中表明其是一个gate。根据该gate 的注册函数, 可以找到该gate 的寄存器:

   static int _ipu_pixel_clk_enable(struct clk_hw *hw)
   {
   	...............
   	disp_gen = ipu_cm_read(ipu, IPU_DISP_GEN);
   	disp_gen |= gate->di_id ? DI1_COUNTER_RELEASE : DI0_COUNTER_RELEASE;
   	ipu_cm_write(ipu, disp_gen, IPU_DISP_GEN);
   
   	return 0;
   }

IPU_DISP_GEN 寄存器如下:

IMX6DL Lvds pixelclock 深入详解_第8张图片

    通过代码和寄存器的描述,这一bit 就是是ipu%u_pclk%u , 最后一道关卡了。系统会在设定pixelclock 后, 在特定的代码中enable该clock。此时, lvds 就输出pixelclock 了。设定pixelclock 就可以设定其parent 的div 值。

    ipu%u_pclk%u  就是我们最终设定的clock。

9. 总结block图   

根据以上对代码的分析与datasheet 的解读,可以得出以下图解:

IMX6DL Lvds pixelclock 深入详解_第9张图片

 

                                                                                                                                                                            -Qing   

                                                                                                                                                                           2018/02/06

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(驱动开发,armlinux,ARM,嵌入式移植专栏,imx,lvds,pixelclock,LCD,timing)