前言
學習,學而時習之。在工作中,利用閑暇時光簡單分析下RK平台下fb源碼部分,本人才疏學淺,很多地方了解的也不到位,隻是簡單的分析下代碼流程,搞明白驅動調試過程中需要注意的地方。現将自己的一些了解與建議總結下來,如有錯誤之處,還望指正。
RK的LCD這塊,亮點也就在于雙屏異顯,才開始搞驅動時,感覺這就是個高大上的東西,一臉懵逼,不知所措。随着後來慢慢深入才發現,原理和單屏也是差不多的。
分析之前,我将RK LCD這塊首先分為四大塊:fb、lcdc、screen、screen_type.這四部分互相依賴,首先從我們最容易入手的地方開始:rk_screen.c
一、函數調用關系
rk_screen.c函數調用關系如下:
二、probe()分析
毫無疑問,驅動的重點就是probe()函數,驅動在比對到compatible = “rockchip,screen”後進入probe()函數,rk_screen_probe():
在probe函數中擷取”screen_prop”、”native-mode”等屬性,在這裡有個重要的結構體:prmry_screen會被初始化,prmry_screen 定義如下:
static struct rk_screen *prmry_screen
還有一個結構體:struct rk_screen *rk_screen,rk_screen貫串上下文,并将prmry_screen指向rk_screen,prmry_screen之是以重要,是因為之後screen_type(例如LVDS,EDP,MIPI等)需要擷取screen參數,就是擷取prmry_screen的值,而這裡prmry_screen就是rk_screen。
進入probe函數有兩個函數比較重要:rk_fb_prase_timing_dt()和rk_disp_pwr_ctr_parse_dt。
首先會根據device_node中”screen_prop”的值,來決定rk_screen的”歸屬”:
代碼中,dts的”screen_prop”的值決定了屏參檔案傳遞進來後指派給了誰:prmry_screen或者extend_screen.(NOTE:隻有在DUAL_LCD時,screen節點下才會有”screen_prop”屬性,單屏,由LCDC部分判斷”screen_prop”)
rk_fb_prase_timing_dt()
通過of_get_display_timings(np)解析device_node中的所有display_timing條目,然後調用
display_timings_get()從結構體display_timing中得到入口位址,最後調用了rk_fb_video_mode_from_timing(dt, screen)從display_timing中擷取screen的詳細資訊,并将其指派給rk_screen結構體(即prmry_screen).
-
rk_disp_pwr_ctr_parse_dt()
該函數主要是從dts中解析power control節點。其中,又引出來一個比較重要的結構體:struct rk_disp_pwr_ctr_list *pwr_ctr;該結構體也是一個雙向連結清單。然後,初始化了一個雙向連結清單rk_screen->pwrlist_head,最終會将pwr_ctr挂到rk_screen->pwrlist_head連結清單下:
list_add_tail(&pwr_ctr->list, rk_screen->pwrlist_head);
for_each_child_of_node(root, child)循環解析每個子節點,例如:
首先為每個子節點(如lcd_en,lcd_cs,lcd_rst等)kmalloc一段空間, 解析dts中”rockchip,power_type”的值,rockchip,power_type = GPIO,分别擷取其GPIO存至各自的pwr_ctr->pwr_ctr.gpio中,然後申請GPIO。這裡,還有一個值:rockchip,delay,可以控制上電時序的延時操作,這個值在後面用到時再講。
2. rk_fb_video_mode_from_timing()
該函數擷取dts中display_timing各個子節點的值,其中有我們熟悉的VBP,VFP,HBP,HFP等可變參數,最終将擷取到的值寫入變量screen中,這樣screen就被初始化完。
至此,screen部分probe()函數完結,現在總結下:
Screen的probe()函數主要幹了兩件事:
解析dts中的display_timing,擷取螢幕資訊
解析dts中的power control,擷取LCD的使能腳、片選腳、複位腳
最終,這些資訊都存在了struct rk_screen *rk_screen這個結構體中,也就是prmry_screen這個結構體。那麼,prmry_screen這個結構體在什麼地方會用到呢?答案也是在rk_screen.c中:
調用rk_fb_get_screen()這個函數來取得screen的資訊。該函數在哪被調用,後續會碰到,暫且不讨論。
以上部分,是雙屏時screen部分的流程,事實上,單屏的代碼更為簡單。就是在probe()直接調用rk_fb_prase_timing_dt(np, rk_screen)來擷取LCD屏資訊,擷取screen:
差別在于display_timings_get()的第二形參不同
但是,有些人又有疑問了?那雙屏時會去擷取power control節點的資訊,單屏時為什麼不用擷取呢?其實也不是沒有擷取,隻不過處理的地方不一樣。雙屏時,在screen部分擷取power control,因為有兩個LCD屏,有各自的使能腳、背光腳等等,是以引傳入連結表儲存至rk_screen結構體中,待将來使用。而單屏隻有一組控制腳,隻要在需要的地方擷取使用就可以了。後續,隻分析單屏的,理清思路即可。
最後,我們再來總結下probe()的功能:
1.如果是雙屏,解析dts中screen節點下的power_ctl節點,單屏的power_ctl節點在别的地方(後續講述)處理
2.從dts中擷取LCD各個參數(VFP,VBP,HBP,HFP,W,H,CLOCK等等)
3.單屏儲存至全局靜态變量rk_screen,雙屏時分别儲存至prmry_screen和extend_screen以差別主副屏
SCREEN部分是整個fb調試過程中,需要更改參數最多的,通常LCD調試隻需要調整screen的dts各個參數即可。LCD調試部分請參考另外一篇博文【Rk平台LCD調試說明】
三、struct rk_screen
struct rk_screen {
#ifdef CONFIG_DUAL_LCD
struct device *dev;
int prop;
struct list_head *pwrlist_head; //power ctl連結清單,儲存power ctl gpio
int native_mode;
#endif
u16 type;
u16 lvds_format; //LVDS資料格式
u16 face; //display out face,18bit,24bit
u16 color_mode;
u8 lcdc_id; //dual lcd時用于區分LCD
u8 screen_id;
struct fb_videomode mode; //important
u32 post_dsp_stx;
u32 post_dsp_sty;
u32 post_xsize;
u32 post_ysize;
u16 x_mirror;
u16 y_mirror;
int interlace;
int pixelrepeat; //For 480i/576i format, pixel is repeated twice.
u16 width;
u16 height;
u8 ft;
int *dsp_lut;
int *cabc_lut;
int *cabc_gamma_base;
#if defined(CONFIG_MFD_RK616) || defined(CONFIG_LCDC_RK312X)
u32 pll_cfg_val; //bellow are for jettaB
u32 frac;
u16 scl_vst;
u16 scl_hst;
u16 vif_vst;
u16 vif_hst;
#endif
u8 hdmi_resolution;
u8 mcu_wrperiod;
u8 mcu_usefmk;
u8 mcu_frmrate;
u8 pin_hsync;
u8 pin_vsync;
u8 pin_den;
u8 pin_dclk;
/* Swap rule */
u8 swap_gb;
u8 swap_rg;
u8 swap_rb;
u8 swap_delta;
u8 swap_dumy;
#if defined(CONFIG_MIPI_DSI)
/* MIPI DSI */
u8 dsi_lane;
u8 dsi_video_mode;
u32 hs_tx_clk;
#endif
int xpos; //horizontal display start position on the sceen ,then can be changed by application
int ypos;
int xsize; //horizontal and vertical display size on he screen,they can be changed by application
int ysize;
struct overscan overscan;
struct rk_screen *ext_screen;
/* Operation function*/
int (*init)(void);
int (*standby)(u8 enable);
int (*refresh)(u8 arg);
int (*scandir)(u16 dir);
int (*disparea)(u8 area);
int (*sscreen_get)(struct rk_screen *screen, u8 resolution);
int (*sscreen_set)(struct rk_screen *screen, bool type);// 1: use scaler 0:bypass
};
struct fb_videomode {
const char *name; /* optional */
u32 refresh; /* optional */
u32 xres;
u32 yres;
u32 pixclock;
u32 left_margin;
u32 right_margin;
u32 upper_margin;
u32 lower_margin;
u32 hsync_len;
u32 vsync_len;
u32 sync;
u32 vmode;
u32 flag;
};
SCREEN主要填充rk_screen結構體的各個字段,這個結構體将由SCREEN_TYPE和fb來擷取使用。
後記
<還記得初學linux驅動時,老版本的核心大量充斥于arch/arm/mach-xxxx下的board檔案,相對于現在的dts機制,此時我們是幸福的,也是悲哀的。”我們不是配置工程師!”>
Email:[email protected]