天天看點

RK fb源碼分析之SCREEN

前言

學習,學而時習之。在工作中,利用閑暇時光簡單分析下RK平台下fb源碼部分,本人才疏學淺,很多地方了解的也不到位,隻是簡單的分析下代碼流程,搞明白驅動調試過程中需要注意的地方。現将自己的一些了解與建議總結下來,如有錯誤之處,還望指正。

RK的LCD這塊,亮點也就在于雙屏異顯,才開始搞驅動時,感覺這就是個高大上的東西,一臉懵逼,不知所措。随着後來慢慢深入才發現,原理和單屏也是差不多的。

分析之前,我将RK LCD這塊首先分為四大塊:fb、lcdc、screen、screen_type.這四部分互相依賴,首先從我們最容易入手的地方開始:rk_screen.c
           

一、函數調用關系

rk_screen.c函數調用關系如下:

RK fb源碼分析之SCREEN

二、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的”歸屬”:

RK fb源碼分析之SCREEN

代碼中,dts的”screen_prop”的值決定了屏參檔案傳遞進來後指派給了誰:prmry_screen或者extend_screen.(NOTE:隻有在DUAL_LCD時,screen節點下才會有”screen_prop”屬性,單屏,由LCDC部分判斷”screen_prop”)

rk_fb_prase_timing_dt()

RK fb源碼分析之SCREEN

通過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).

  1. 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);

    RK fb源碼分析之SCREEN

for_each_child_of_node(root, child)循環解析每個子節點,例如:

RK fb源碼分析之SCREEN

首先為每個子節點(如lcd_en,lcd_cs,lcd_rst等)kmalloc一段空間, 解析dts中”rockchip,power_type”的值,rockchip,power_type = GPIO,分别擷取其GPIO存至各自的pwr_ctr->pwr_ctr.gpio中,然後申請GPIO。這裡,還有一個值:rockchip,delay,可以控制上電時序的延時操作,這個值在後面用到時再講。

RK fb源碼分析之SCREEN

2. rk_fb_video_mode_from_timing()

RK fb源碼分析之SCREEN

該函數擷取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源碼分析之SCREEN

調用rk_fb_get_screen()這個函數來取得screen的資訊。該函數在哪被調用,後續會碰到,暫且不讨論。

以上部分,是雙屏時screen部分的流程,事實上,單屏的代碼更為簡單。就是在probe()直接調用rk_fb_prase_timing_dt(np, rk_screen)來擷取LCD屏資訊,擷取screen:

RK fb源碼分析之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]

繼續閱讀