写在前面的话:
嵌入式系统中有两个比较难搞的问题, 一个是电源,一个是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) 在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
在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
IPU_DI0 , IPU_DI0_SEL 及LDB_DI0 之间的关系:
IPU_DI0 是一个gate,CG1
至此, 我们就有一种可能的通路了:
ldb_di0-> IPU_DI0_SEL -> IPU_DI0
再看 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了。
我们看一个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;
}
............................
}
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;
}
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 就是这个寄存器。
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。
根据以上对代码的分析与datasheet 的解读,可以得出以下图解:
-Qing
2018/02/06