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)

2)在其他soc上,

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



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

至此, 我們就有一種可能的通路了:

ldb_di0-> IPU_DI0_SEL -> IPU_DI0


4. IPU_DI0_CLK_ROOT 的去向:

再看 datasheet 的描述:

如此,便可以通過該寄存器選擇 DI 的Clock。

該寄存器說明 最終的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 寄存器如下:

    通過代碼和寄存器的描述,這一bit 就是是ipu%u_pclk%u , 最後一道關卡了。系統會在設定pixelclock 後, 在特定的代碼中enable該clock。此時, lvds 就輸出pixelclock 了。設定pixelclock 就可以設定其parent 的div 值。

    ipu%u_pclk%u  就是我們最終設定的clock。

9. 總結block圖   

根據以上對代碼的分析與datasheet 的解讀,可以得出以下圖解:



                                                                                                                                                                            -Qing   

                                                                                                                                                                           2018/02/06


















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章