题图:河北太行山脉,山头密密麻麻都被太阳能板覆盖了。
欢迎关注,每周更新!☞
本合集分享的是,我当初学习Linux驱动的来时路——《《驱动之路》开篇:自序&前言》。
正文
在实际工作中,虽然并不需要我们从零编写 LCD 驱动程序,但必须掌握 LCD 驱动框架 —— 这是分析现有驱动、定位问题,以及在必要时修改驱动以适配硬件或需求的必修课。
分析之前,首先需要掌握字符设备驱动的基本框架,看前面文章《驱动之路#01:Hello World!》。LCD 驱动本质上也是字符设备驱动,理解了字符设备驱动的框架,有助于分析 LCD 驱动程序。
下面我们就运用前面掌握的字符设备驱动基本框架,以 RK3576 平台的 panel-simple.c 为例,浅浅分析 LCD 驱动。
驱动位置:kernel-6.1/drivers/gpu/drm/panel/panel-simple.c
如何阅读驱动程序,准确说是如何阅读字符设备驱动程序?阅读一个字符设备驱动程序,应该从它的入口函数开始看。
看到 panel-simple.c 的入口函数*register() 注册了 3 个驱动,说明该驱动程序同时兼容DPI、DSI和SPI接口连接的 panel。

拿 panel_simple_dsi_driver 举例分析,继续往下看。当 driver 与 device(即 dts) 的 compatible 属性匹配就会调用 panel_simple_dsi_probe 函数。

只有我们弄清楚panel_simple_dsi_probe函数的具体操作就基本了解了panel_simple_dsi_driver驱动,这就是分析字符设备驱动程序的大致流程。
接下来,分析 panel_simple_dsi_probe 函数,直接看代码注释。
/** MIPI-DSI 面板探测函数* 负责初始化DSI接口的面板设备*/staticintpanel_simple_dsi_probe(structmipi_dsi_device *dsi){ /* 局部变量定义 */ structpanel_simple *panel; // 通用面板数据结构 structdevice *dev = &dsi->dev; // 获取设备指针,简化后续访问 conststructpanel_desc_dsi *desc;// DSI面板描述符(预定义或动态) structpanel_desc_dsi *d; // 动态分配的描述符指针 conststructof_device_id *id; // 设备树匹配结果 interr; // 错误码 /* 1. 设备树匹配 - 检查设备是否在驱动支持列表中 */ id = of_match_node(dsi_of_match, dsi->dev.of_node); if(!id) return-ENODEV; // 设备不在支持列表中,返回设备不存在错误 /* 2. 面板描述符处理 */ if(!id->data) { /* 情况A: 设备树中定义了自定义面板,没有预定义描述符 */ // 动态分配DSI面板描述符内存 d = devm_kzalloc(dev,sizeof(*d), GFP_KERNEL); if(!d) return-ENOMEM; // 内存分配失败 /* 从设备树节点解析面板参数 */ err = panel_simple_dsi_of_get_desc_data(dev, d); if(err) { dev_err(dev,"failed to get desc data: %dn", err); returnerr; // 参数解析失败 } } /* 注意: 如果 id->data 不为空,表示使用预定义的描述符,跳过动态分配 */ /* 3. 选择最终使用的描述符 */ desc = id->data ? id->data : d; // 优先使用预定义,否则使用动态解析的描述符 /* 4. 调用通用面板探测函数进行基础初始化 */ err = panel_simple_probe(&dsi->dev, &desc->desc); if(err < 0) return err; // 通用面板初始化失败 /* 5. 获取面板数据结构并关联DSI设备 */ panel = dev_get_drvdata(dev); // 从设备私有数据中获取panel_simple指针 panel->dsi = dsi; // 将DSI设备指针保存到面板结构中,建立双向关联 /* 6. 背光设备处理 */ if(!panel->base.backlight) { /* 如果面板没有背光设备,注册一个DCS背光设备 */ structbacklight_properties props; // 背光属性结构 /* 初始化背光属性 */ memset(&props,0,sizeof(props)); props.type = BACKLIGHT_RAW; // 背光类型:原始模式 props.brightness =255; // 初始亮度:最大值 props.max_brightness =255; // 最大亮度:255级 /* 注册背光设备 */ panel->base.backlight = devm_backlight_device_register(dev, dev_name(dev), dev, panel, &dcs_bl_ops, &props); /* 错误处理 */ if(IS_ERR(panel->base.backlight)) { err = PTR_ERR(panel->base.backlight); dev_err(dev,"failed to register dcs backlight: %dn", err); returnerr; // 背光设备注册失败 } } /* 注意: 如果面板已有背光设备(如通过设备树引用),则跳过此步骤 */ /* 7. 配置DSI主机控制器参数 */ dsi->mode_flags = desc->flags; // 设置DSI工作模式标志 dsi->format = desc->format; // 设置像素数据格式 dsi->lanes = desc->lanes; // 设置数据通道数量 /* 8. 将面板附加到DSI主机 */ err = mipi_dsi_attach(dsi); if(err) { /* 附件失败时的清理工作 */ structpanel_simple *panel = mipi_dsi_get_drvdata(dsi); drm_panel_remove(&panel->base); // 从DRM子系统移除面板 } /* 9. 返回执行结果 */ returnerr; // 成功返回0,失败返回错误码}
我们在设备树中定义的屏参以及mipi 屏幕相关初始化代码。

最终是通过函数 panel_simple_dsi_of_get_desc_data()和 panel_simple_of_get_desc_data()获取到的。

至此,mipi-dsi 屏幕驱动分析完毕!能分析阅读整体驱动框架和函数调用流程就行,不需要花大量时间和精力去解读每行代码解读,AI时代没多大意义。
最后附上两个比较重要的函数,供阅读参考。
附录:static int panel_simple_dsi_of_get_desc_data(struct device *dev, struct panel_desc_dsi *desc){ struct device_node *np = dev->of_node; u32val; int err; // 1. 首先解析通用面板参数 err = panel_simple_of_get_desc_data(dev, &desc->desc); if(err) returnerr; // 2. 设置连接器类型为DSI desc->desc.connector_type = DRM_MODE_CONNECTOR_DSI; // 3. 从dts解析DSI特有参数 if(!of_property_read_u32(np,"dsi,flags", &val)) desc->flags =val; if(!of_property_read_u32(np,"dsi,format", &val)) desc->format =val; if(!of_property_read_u32(np,"dsi,lanes", &val)) desc->lanes =val; return0;}函数 panel_simple_dsi_of_get_desc_data( )中调用 panel_simple_dsi_of_get_desc_data( )获取面板 desc_data。static int panel_simple_of_get_desc_data(struct device *dev, struct panel_desc *desc){ struct device_node *np = dev->of_node; u32 bus_flags; constvoid *data; int len; int err; // 1. 解析显示时序 if(of_child_node_is_present(np,"display-timings")) { struct drm_display_mode *mode; mode = devm_kzalloc(dev, sizeof(*mode), GFP_KERNEL); if(!mode) return-ENOMEM; // 从设备树解析DRM显示模式 if(!of_get_drm_display_mode(np, mode, &bus_flags, OF_USE_NATIVE_MODE)) { desc->modes = mode; desc->num_modes =1; desc->bus_flags = bus_flags; } }elseif(of_child_node_is_present(np,"panel-timing")) { struct display_timing *timing; struct videomode vm; timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL); if(!timing) return-ENOMEM; // 从设备树解析显示时序 if(!of_get_display_timing(np,"panel-timing", timing)) { desc->timings = timing; desc->num_timings =1; // 转换时序标志为总线标志 bus_flags =0; vm.flags = timing->flags; drm_bus_flags_from_videomode(&vm, &bus_flags); desc->bus_flags = bus_flags; } } // 2. 解析基本面板属性 if(desc->num_modes || desc->num_timings) { of_property_read_u32(np,"bpc", &desc->bpc); // 每颜色位数 of_property_read_u32(np,"connector-type", &desc->connector_type); of_property_read_u32(np,"bus-format", &desc->bus_format);// 总线格式 of_property_read_u32(np,"width-mm", &desc->size.width); // 物理宽度 of_property_read_u32(np,"height-mm", &desc->size.height);// 物理高度 } // 3. 解析时序延迟参数 of_property_read_u32(np,"prepare-delay-ms", &desc->delay.prepare); of_property_read_u32(np,"enable-delay-ms", &desc->delay.enable); of_property_read_u32(np,"disable-delay-ms", &desc->delay.disable); of_property_read_u32(np,"unprepare-delay-ms", &desc->delay.unprepare); of_property_read_u32(np,"reset-delay-ms", &desc->delay.reset); of_property_read_u32(np,"init-delay-ms", &desc->delay.init); // 4. 解析初始化序列 data= of_get_property(np,"panel-init-sequence", &len); if(data) { desc->init_seq = devm_kzalloc(dev, sizeof(*desc->init_seq), GFP_KERNEL); if(!desc->init_seq) return-ENOMEM; // 解析初始化命令序列 err = panel_simple_parse_cmd_seq(dev,data, len, desc->init_seq); if(err) { dev_err(dev,"failed to parse init sequencen"); returnerr; } } // 5. 解析退出序列 data= of_get_property(np,"panel-exit-sequence", &len); if(data) { desc->exit_seq = devm_kzalloc(dev, sizeof(*desc->exit_seq), GFP_KERNEL); if(!desc->exit_seq) return-ENOMEM; // 解析退出命令序列 err = panel_simple_parse_cmd_seq(dev,data, len, desc->exit_seq); if(err) { dev_err(dev,"failed to parse exit sequencen"); returnerr; } } return0;}
(完)
审核编辑 黄宇
-
lcd
+关注
关注
36文章
4630浏览量
177867 -
驱动程序
+关注
关注
19文章
872浏览量
50699 -
rk3576
+关注
关注
1文章
291浏览量
1663
发布评论请先 登录
硬核进阶:RK3576 Android15 驱动与系统开发实战指南
RK3576驱动高端显控系统升级:多屏拼控与AI视觉融合解决方案
【作品合集】米尔RK3576开发板测评
【作品合集】灵眸科技EASY EAI Orin Nano(RK3576)开发板测评
瑞芯微RK3576与RK3576S有什么区别,性能参数配置与型号差异解析
驱动之路#04:LCD 驱动程序分析(基于RK3576)
评论