天天看点

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]

继续阅读