2025年IMX6DL Lvds pixelclock 深入详解

IMX6DL Lvds pixelclock 深入详解写在前面的话 嵌入式系统中有两个比较难搞的问题 一个是电源 一个是 Clock 随着现在电源管理芯片的成熟 我们将会越来越少地关注到电源的配置和设定 但管理应用还是比较广泛的 Clock 系统是一个嵌入式产品的命脉 Soc 复杂的 Clock 常常会让大家望而却步

大家好,我是讯享网,很高兴认识大家。

写在前面的话:   

        嵌入式系统中有两个比较难搞的问题, 一个是电源,一个是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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

小讯
上一篇 2025-03-27 17:54
下一篇 2025-01-04 17:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/11446.html