RK fb源码分析之SCREEN

本文详细解析了RK平台LCD驱动的实现过程,重点介绍了probe()函数的作用及内部流程,包括如何解析dts中的display_timing获取屏幕信息,以及如何解析powercontrol获取LCD控制脚。

前言
学习,学而时习之。在工作中,利用闲暇时光简单分析下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).

  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);
    这里写图片描述

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:hellobirthdayzhao@gmail.com

### RK3566 芯片 LCD 驱动源码分析 RK3566 是一款高性能处理器,广泛应用于平板电脑和其他嵌入式设备中。其 LCD 驱动程序的设计通常基于 Linux 内核框架下的 DRM(Direct Rendering Manager)子系统以及特定的显示控制器接口。 #### 1. 文件结构与路径解析 在 RK3566 的开发环境中,LCD 驱动的相关文件主要分布在以下几个目录下[^1]: - **`drivers/framework/model/display/driver/panelvendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info`**: 这部分包含了面板厂商提供的配置信息,定义了显示屏的具体参数,例如分辨率、刷新率等。 - **`input`**: 输入设备相关的初始化脚本和配置文件,虽然不直接参与 LCD 显示逻辑,但在某些场景下会影响整体交互体验。 这些文件共同构成了驱动的核心功能模块,通过 HAL 层级调用实现底层硬件控制。 #### 2. 测试 LCD 驱动的方法 为了验证 LCD 驱动的功能是否正常工作,可以采用以下几种常见方法[^2]: - 向帧缓冲区 `/dev/fb0` 中写入随机数据并观察屏幕是否会呈现花屏现象: ```bash cat anyfile > /dev/fb0 ``` - 使用 `echo` 命令将字符串输出到虚拟终端 `/dev/tty1` 并确认屏幕上能否正确显示文字内容: ```bash echo "hello darkbird!" > /dev/tty1 ``` - 编辑系统的初始进程表 `/etc/inittab` 添加对 tty1 的支持项,随后重启内核加载新的设置完成进一步测试。 以上操作均需确保当前环境已挂载对应字符设备节点且权限允许访问。 #### 3. NAND Flash 启动机制概述 由于 Amoi 设备采用了 NAND Flash 启动模式,在此过程中涉及到多个阶段的操作流程[^3]。相比 NOR Flash 更加复杂但也更贴近现代计算机体系架构特点——即先由 BootROM 加载初步引导代码至 RAM 执行;接着依次读取 U-boot 及操作系统镜像直至最终进入用户空间运行状态为止。 以下是简化版启动序列图解说明: ```plaintext BootROM -> SPL (Secondary Program Loader) -> U-Boot -> Kernel Image Loading -> System Initialization. ``` 这一系列动作保障了整个平台能够平稳过渡到高级别应用层面上来处理图形界面渲染等工作任务。 #### 4. 关键技术点探讨 针对具体实现细节方面可以从如下几个角度切入研究学习: - **寄存器映射**:了解如何利用 C/C++ 完成物理地址范围内的内存分配从而获得对外设资源的有效操控权能; - **中断服务例程(ISR)**:掌握何时何地触发事件通知机制以便及时响应外部输入信号变化情况反馈给应用程序开发者做后续处置安排考虑进去的话可能会更好一点哦! - **同步异步通信协议选用原则及其优劣对比分析报告撰写提纲建议稿如下所示可供参考借鉴使用价值较高值得收藏备用以防万一时候拿出来看看也不错呢是不是?** --- ### 示例代码片段展示 下面是关于如何打开并操作帧缓冲设备的一个简单例子供参考: ```c #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> int main() { int fbfd = open("/dev/fb0", O_RDWR); if (fbfd == -1) { perror("Error opening framebuffer"); return -1; } struct fb_var_screeninfo vinfo; ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); void* fbp = mmap(NULL, vinfo.xres * vinfo.yres * (vinfo.bits_per_pixel / 8), PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0); // Write some data to the framebuffer... unsigned char red = 255, green = 0, blue = 0; size_t pixel_offset = 0; for(int y=0; y<vinfo.yres && pixel_offset<(size_t)(vinfo.xres*vinfo.yres*(vinfo.bits_per_pixel/8)); ++y){ for(int x=0; x<vinfo.xres && pixel_offset<(size_t)(vinfo.xres*vinfo.yres*(vinfo.bits_per_pixel/8)); ++x,pixel_offset+=3){ *(fbp + pixel_offset ) = blue ; *(fbp + pixel_offset + 1) = green; *(fbp + pixel_offset + 2) = red ; } } munmap(fbp, vinfo.xres * vinfo.yres * (vinfo.bits_per_pixel / 8)); close(fbfd); } ``` 该示例展示了基本的帧缓冲管理过程,包括打开设备文件、获取变量屏幕信息、映射内存区域以及最后释放资源关闭连接等功能单元组成部分介绍完毕之后我们再来看一下其他相关内容吧?
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值