寫在前面的話:
嵌入式系統中有兩個比較難搞的問題, 一個是電源,一個是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