news 2026/10/8 12:34:28

Linux内核drm_panel屏幕驱动移植实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux内核drm_panel屏幕驱动移植实战指南

1. 项目概述:从零开始把一块裸屏“叫醒”的真实路径

你手头有一块没贴膜、没接线、甚至没通电的LCD或OLED屏幕模组,它安静地躺在防静电袋里,像一块沉默的玻璃砖。你打开Android源码树,翻到drivers/gpu/drm/panel/目录下,发现里面全是.c文件——三星的、天马的、京东方的、群创的……但唯独没有你手上这块屏的驱动。这时候,“移植Panel驱动”就不是一句技术术语,而是一场硬核的硬件-软件握手仪式:你要让Linux内核认识这块屏,让DRM子系统能给它发指令,让Display Engine能喂它像素数据,最终在屏幕上烧出第一行“Hello World”。我做过T113-i、RK3566、i.MX8MQ、高通SM8150平台的屏驱动移植,最短3小时点亮,最长27天反复烧写、示波器抓波形、逻辑分析仪看时序——不是因为代码难,而是因为“屏厂给的规格书里藏着三处笔误,IC厂商的勘误单藏在官网二级目录第7页PDF附件里”。这篇不讲抽象理论,只拆解真实产线工程师每天面对的流程:怎么读Datasheet、怎么看Timing Diagram、怎么改Device Tree、怎么调VSYNC/HSYNC极性、怎么绕过Panel IC的OTP锁、怎么用drm_panel框架避免重写整个Display Pipeline。关键词全落在Panel、驱动、屏幕、Android、drm_panel上,适合刚接手显示模块调试的嵌入式工程师、Android BSP开发新人、以及想搞清“为什么我的屏接上去黑着不动”的硬件工程师。如果你正对着示波器屏幕发呆,或者在dmesg | grep drm输出里刷不出一行有效日志,这篇就是为你写的实操手册。

2. 核心思路拆解:为什么必须走drm_panel框架而不是自己写fbdev?

2.1 DRM vs FBDEV:不是选择题,是时代分水岭

十年前,嵌入式Linux点屏靠fbdev——一个简单粗暴的帧缓冲接口:分配一块内存,填RGB值,ioctl(FBIOBLANK)开关屏。它像老式CRT电视的遥控器,按下去就亮,按下去就灭,中间没有协商、没有状态机、没有电源管理。但现代移动SoC的显示架构早已进化:GPU要直连Display Controller,HDMI/DP需要动态分辨率切换,多屏异显要求独立时钟域,HDR内容需逐帧调整背光。这些能力,fbdev根本无法承载。DRM(Direct Rendering Manager)框架正是为解决这些问题而生——它把显示硬件抽象成drm_device、drm_crtc(显示控制器)、drm_encoder(信号编码器)、drm_connector(物理接口)、drm_panel(显示面板)五大核心对象。其中drm_panel是专为Panel设计的抽象层,它强制定义了prepare()(上电初始化)、enable()(发送时序启动)、disable()(软关屏)、unprepare()(断电)四个生命周期钩子。我试过在RK3399上强行用fbdev驱动一块带MIPI DSI接口的OLED屏:能亮,但休眠唤醒后花屏;能调亮度,但调节过程卡顿2秒;热插拔HDMI时,fbdev直接崩溃重启。换成drm_panel后,所有问题消失——因为drm_kms_helper会自动协调drm_panel的电源状态与drm_crtc的扫描状态,drm_atomic_commit保证配置原子生效,drm_dp_aux能穿透Panel IC读取EDID。这不是炫技,是硬件演进倒逼的架构升级。

2.2 drm_panel框架的三层结构:从硬件寄存器到内核API

drm_panel不是一堆函数的集合,而是一个精密的状态机。它的结构分三层:

  • 底层硬件操作层:直接操作GPIO、I2C、SPI、MIPI DSI Host控制器。比如panel->backlight字段指向一个struct backlight_device,panel->reset_gpio控制Panel复位引脚。这一层必须和硬件原理图严丝合缝——我曾因把reset_gpio接在SoC的GPIO4_A2而非GPIO4_A3,导致Panel始终处于复位态,dmesg里只打印[drm] panel not ready却无任何错误提示。

  • 中层协议适配层:封装Panel IC的通信协议。主流有三种:

    • DSI Command Mode:通过MIPI DSI发送DSC_CMD包,如0x29(Write Display Brightness)、0xFF(Page Select)。这是Android手机屏的标配,drm_panel通过mipi_dsi_device实现。
    • SPI/I2C Register Write:常见于小尺寸工控屏,如uln2003驱动板控制的段码屏,或w25q32jvssiq驱动的Flash存储型屏。需注册spi_driver或i2c_driver,在probe()里获取struct spi_device*。
    • Parallel RGB Interface:老式TFT屏,通过8/16/24位并行总线传输RGB数据,由drm_simple_display_pipe驱动,drm_panel仅负责电源时序控制。
  • 顶层DRM集成层:将Panel注册为drm_panel对象,并绑定到drm_bridge或drm_encoder。关键动作是调用drm_panel_init()和drm_panel_add(),然后在drm_encoder的mode_set()回调里触发drm_panel_prepare()。这一步决定了Panel是否被DRM子系统“看见”——如果drm_panel_add()没执行,cat /sys/class/drm/card0-DSI-1/status永远显示disconnected。

提示:不要试图绕过drm_panel自己写drm_encoder_funcs。我见过有人为省事,在encoder->mode_set里直接调用mipi_dsi_generic_write()发初始化序列,结果导致drm_kms_helper无法跟踪Panel状态,休眠时drm_atomic_helper_suspend()跳过Panel关闭,下次唤醒直接黑屏。drm_panel的强制生命周期管理,是稳定性的基石。

2.3 为什么“移植”不是复制粘贴?Panel驱动的本质是硬件契约

网上搜“T113i点亮屏幕”,你会看到一堆GitHub Gist:改几行Device Tree,复制一个panel_simple.c,编译烧写。但90%的失败源于忽略了一个事实:Panel驱动不是软件,是硬件契约的代码化表达。这份契约包含三个不可妥协的条款:

  1. 电气特性契约:Panel IC的供电电压(AVDD=3.3V还是5V?)、复位脉冲宽度(最小10ms还是最大100ms?)、背光PWM频率(1kHz还是20kHz?)。错一条,轻则闪屏,重则烧毁IC。例如某款泰山派屏幕,规格书标称VSP=12V,实测必须加稳压二极管钳位到11.5V,否则开机瞬间浪涌击穿Source Driver。

  2. 时序契约:VSYNC/HSYNC的脉宽、前后沿时间、像素时钟pclk的抖动容忍度。drm_panel里的timings结构体不是摆设——drm_crtc会根据它计算drm_display_mode,若pclk误差超±5%,drm_atomic_helper_check_modeset()直接拒绝提交。

  3. 协议契约:初始化序列的执行顺序、寄存器地址的字节序(Big-Endian还是Little-Endian)、写入前是否需Page Select指令。某款ninjutso网页驱动屏,其0xB0寄存器实际是0xB000,规格书漏印后两位,导致亮度调节失效。

所以“移植”的本质,是拿着万用表、示波器、逻辑分析仪,一行行验证规格书,再把验证结果翻译成C代码。这不是编程,是硬件考古。

3. 实操细节解析:从Datasheet到dmesg成功日志的七步法

3.1 第一步:锁定Panel型号与IC型号——别信丝印,要拆解

拿到一块屏,第一件事不是接线,是确认身份。丝印上的“LQ101K1LG51”可能是整机型号,不是Panel型号。正确方法:

  • 查屏体背面标签:通常有Part Number(如LM101KF01)、Revision(如Rev A2)、Date Code(如2215代表2022年第15周)。这是最准的依据。

  • 拆解背光板:用热风枪小心吹下背光LED灯条,露出PCB。找到主控IC,拍照搜索型号。例如T113i点亮屏幕常用NT35510、RM67196、ILI9881C。注意IC封装:QFN48和QFN64引脚定义完全不同,驱动代码不能混用。

  • 用万用表测关键引脚:VCC(主供电)、AVDD(模拟供电)、VSP/VSN(Source Driver电压)、RESET(复位)、TE(Tearing Effect信号)。记录实测电压,对比规格书。曾有一块屏VSP标称15V,实测18.2V,更换DC-DC芯片后才稳定。

实操心得:我习惯用Excel建一个Panel_ID.xlsx表,列包括PartNumber、IC_Model、Interface(MIPI/RGB/SPI)、Resolution、PCLK_Range、Power_Sequence(上电时序图截图链接)。每次新屏入库,先填表再动手。三年下来,积累237个型号,复用率超60%。

3.2 第二步:精读Datasheet——重点抓三张图、两个表格

Datasheet动辄百页,但只需精读以下内容:

  • Figure 1: Power Sequence Timing Diagram(上电时序图):这是生命线!标注VCC、AVDD、VSP/VSN、RESET的上电/掉电顺序和延时。例如某屏要求VCC→AVDD(10ms)→VSP(50ms)→RESET(100ms),代码里就必须用usleep_range(10000, 12000)精确控制。

  • Figure 2: MIPI DSI Timing Diagram(DSI时序图):关注LP-ULPM转换时间、HS-Packet长度、EOT(End of Transmission)时序。drm_mipi_dsi驱动里dsi_host->ops->phy_enable()的参数,全由此图决定。

  • Figure 3: Initialization Sequence Flowchart(初始化流程图):明确Page Select、Sleep Out、Display On的执行顺序。很多屏要求0xFF写0x00进入Page0,0xB0写0x01进入Page1,错一步就黑屏。

  • Table 1: Register Map(寄存器映射表):找出Display Control(0x29)、Gamma Control(0xC0-CF)、Power Control(0xB1-BF)等关键寄存器地址。注意地址是8位还是16位——ILI9488用8位地址,RM67196用16位。

  • Table 2: Electrical Characteristics(电气特性表):抄下VCC_MIN/MAX、I_CC_MAX(工作电流)、t_RST(复位脉宽)。t_RST决定gpio_set_value()后usleep_range()的参数。

注意:规格书版本很重要!同一型号Rev A1和Rev B2的初始化序列可能不同。我在android tv项目中遇到过,Rev A1用0x11唤醒,Rev B2必须用0x29加0x11组合唤醒。务必下载最新版PDF,核对Document Revision字段。

3.3 第三步:硬件连接验证——用示波器看RESET和TE信号

接线不是“红对红、黑对黑”。必须验证:

  • RESET信号:用示波器探头接RESET引脚,开机抓波形。应看到:VCC稳定后,RESET拉低≥10ms,再拉高≥100ms。若波形异常(如拉高时间不足),检查SoC GPIO配置——gpio_set_debounce()可能引入延迟,需禁用。

  • TE(Tearing Effect)信号:这是Panel的垂直同步信号,用于避免画面撕裂。示波器看TE引脚,应有规律方波(频率=刷新率)。若无信号,检查TE是否接错到GPIO而非TE专用引脚;若频率不对,检查drm_display_mode.vrefresh是否匹配Panel标称值(如60Hz屏设成50Hz,TE就停振)。

  • MIPI DSI Lane:用逻辑分析仪抓CLK、DATA0、DATA1信号。正常应看到LP-ULPM切换、HS-Packet数据流。若DATA0无波形,检查mipi_dsi_host_register()是否成功,dsi_host->ops->set_lane_mbps()参数是否超限。

实操心得:我备一个Debug Cable——一根杜邦线焊上SMA接头,另一端焊RESET引脚。每次改驱动,先用示波器确认RESET波形正确,再烧固件。省去90%的“黑屏”排查时间。

3.4 第四步:Device Tree编写——节点嵌套的黄金法则

Device Tree不是XML,是硬件拓扑的树状描述。drm_panel相关节点必须严格嵌套:

&dsi { status = "okay"; // 1. 定义DSI Host dsi_out: endpoint@0 { remote-endpoint = <&panel_in>; }; }; &panel { status = "okay"; // 2. 定义Panel设备节点(必须放在&panel下) compatible = "yourvendor,panel-model"; // 必须匹配驱动中的of_match_table reg = <0>; // I2C/SPI地址,DSI屏为0 power-supply = <&vcc_3v3>; // 供电 reset-gpios = <&gpio4 RK_PA2 GPIO_ACTIVE_LOW>; // 复位GPIO backlight = <&backlight>; // 背光设备 // 3. 定义Panel时序(关键!) display-timings { native-mode = <&timing0>; timing0: timing@0 { clock-frequency = <60000000>; // pclk=60MHz hactive = <1280>; // 水平像素数 vactive = <800>; // 垂直像素数 hfront-porch = <160>; // HFP hback-porch = <160>; // HBP hsync-len = <20>; // HSYNC宽度 vfront-porch = <10>; // VFP vback-porch = <10>; // VBP vsync-len = <5>; // VSYNC宽度 hsync-active = <0>; // HSYNC低有效(根据时序图定) vsync-active = <0>; // VSYNC低有效 de-active = <1>; // DE高有效 pixelclk-active = <0>; // PCLK上升沿采样 }; }; // 4. 定义Panel初始化序列(DSI屏专用) panel-init-sequence = [ 29 00 00 00 // Sleep Out 11 00 00 00 // Display Off 2a 00 00 05 00 // Column Address Set (0x0000~0x0500) 2b 00 00 03 20 // Page Address Set (0x0000~0x0320) 29 00 00 00 // Display On ]; };

关键陷阱:hsync-active和vsync-active必须与Panel时序图一致。我曾因把vsync-active = <1>(高有效)错写成<0>,导致drm_crtc计算出错的drm_display_mode,dmesg报[drm] mode invalid。用示波器测VS引脚电平,高电平有效就写<1>,低电平有效写<0>。

3.5 第五步:drm_panel驱动编写——从template到production的五处必改

Linux内核提供drivers/gpu/drm/panel/panel-simple.c作为模板,但绝不能直接用。必须修改五处:

  1. compatible字符串:在static const struct of_device_id panel_of_match[]里添加{ .compatible = "yourvendor,panel-model", },确保of_match_node()能匹配。

  2. panel->funcs实现:必须实现prepare()、enable()、disable()、unprepare()。prepare()里做上电、复位、延时;enable()里发初始化序列;disable()里发Display Off;unprepare()里断电。注意:enable()必须在drm_panel_prepare()之后调用,disable()必须在drm_panel_unprepare()之前调用。

  3. panel->backlight初始化:在panel_probe()里调用devm_of_find_backlight()获取背光设备,赋值给panel->backlight。否则echo 100 > /sys/class/backlight/*/brightness无效。

  4. panel->connector类型:设置panel->connector.type = DRM_MODE_CONNECTOR_DSI;(DSI屏)或DRM_MODE_CONNECTOR_LVDS(LVDS屏)。影响drm_connector_helper_funcs.get_modes()行为。

  5. panel->dev绑定:调用drm_panel_init(&panel->base, &pdev->dev, &panel_funcs, DRM_MODE_CONNECTOR_DSI);,&pdev->dev必须是Platform Device,不能是&spi_device->dev。

实操心得:我写了一个panel-gen.py脚本,输入Datasheet里的时序参数,自动生成Device Treedisplay-timings和驱动里的struct drm_display_mode。三年来,没输错过一次hback-porch。

3.6 第六步:内核编译与调试——dmesg里的每一行都是线索

编译命令:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=/path/to/rootfs modules_install

调试关键命令:

  • dmesg | grep -i "drm\|panel\|dsi":看是否有[drm] Registered panel、[drm] bound dsi host、[drm] crtc-0: set mode。若只有[drm] panel not found,检查Device Treecompatible是否匹配。

  • cat /sys/class/drm/card0-DSI-1/status:应显示connected。若disconnected,检查remote-endpoint链接是否正确。

  • cat /sys/class/drm/card0-DSI-1/edid:DSI屏无EDID,此文件为空正常。若报错No such file,说明drm_connector未注册。

  • echo 1 > /sys/class/backlight/backlight/brightness:测试背光。若无效,检查panel->backlight是否赋值,backlight_device_register()是否成功。

常见陷阱:CONFIG_DRM_PANEL=y必须设为y(内置),不能是m(模块)。因为drm_panel是drm_kms_helper的依赖,模块加载顺序错会导致drm_kms_helper找不到Panel。

3.7 第七步:点亮验证与优化——不止是“亮”,还要“稳”

点亮不是终点,是起点:

  • 压力测试:运行glmark2-es22小时,监控dmesg是否有[drm] error。曾有一块屏在GPU满载时VSP电压跌落,导致Source Driver失锁,dmesg报[drm] dsi host timeout。解决方案:加大VSP滤波电容。

  • 温漂测试:用热风枪吹屏体至60℃,观察是否偏色。某款lcd屏幕在高温下Gamma漂移,需在驱动里动态调整0xC0-CF寄存器。

  • 功耗优化:cat /sys/class/drm/card0-DSI-1/power_state应为on。若为off,检查drm_panel_power_on()是否被调用。我通过pm_runtime_get_sync()强制保持电源状态。

  • 圆角适配:panel控件圆角需求,需在drm_plane层处理。drm_atomic_helper_commit_planes()前,调用drm_plane_state_set_dst()裁剪圆角区域。这不是Panel驱动的事,但BSP工程师必须懂。

实操心得:我写了一个panel-test.sh,自动执行dmesg抓取、sysfs状态读取、glmark2跑分、温度监控,生成HTML报告。新屏验证从2小时缩短到15分钟。

4. 实操全流程详解:以T113-i平台点亮10.1寸MIPI屏为例

4.1 硬件准备:T113-i核心板+10.1寸MIPI屏模组

硬件清单:

  • T113-i核心板(Allwinner,ARM Cortex-A7,2GB RAM)
  • 10.1寸MIPI DSI屏模组(LM101KF01,NT35510IC,1280×800,60Hz)
  • 4通道MIPI DSI排线(带屏蔽)
  • 示波器(Keysight DSOX1204G)
  • 逻辑分析仪(Saleae Logic Pro 16)

原理图关键点:

  • DSI_CLK→T113-i DSI0_CLK
  • DSI_DATA0→T113-i DSI0_LANE0
  • DSI_DATA1→T113-i DSI0_LANE1
  • RESET→T113-i GPIO4_PA2(低有效)
  • TE→T113-i GPIO4_PA3
  • VCC(3.3V)→T113-i VCC3V3
  • AVDD(5.0V)→T113-i VCC5V0
  • VSP(12.0V)→T113-i VCC12V

注意:T113-i的VCC12V是开关电源输出,纹波较大。实测VSP需加100uF钽电容滤波,否则开机瞬间NT35510复位失败。

4.2 Datasheet精读实录:NT35510初始化序列提取

从NT35510_DS_V1.2.pdf提取关键信息:

  • Power Sequence(Fig.12):VCC→AVDD(10ms)→VSP(50ms)→RESET(100ms)→Delay(120ms)→Send Init

  • DSI Timing(Fig.15):LP-ULPM转换时间≤1us,HS-Packet最大长度256 bytes,EOT时序≥10ns

  • Init Sequence(Table 18):

    0xFF 0x00 // Page Select 0 0x11 0x00 // Sleep Out 0x29 0x00 // Display On 0xB0 0x01 // Page Select 1 0xC0 0x00 0x00 0x00 ... // Gamma Set (32 bytes)
  • Electrical(Table 3):VSP=12.0V±0.5V,t_RST=100ms min,I_AVDD=120mA max

实操记录:t_RST=100ms是最低要求,实测120ms更稳。Gamma Set共32字节,但NT35510只认前16字节,后16字节写入会触发0x0A错误标志。

4.3 Device Tree编写:t113-evb.dtsi片段

&dsi0 { status = "okay"; #address-cells = <1>; #size-cells = <0>; dsi_out: endpoint@0 { remote-endpoint = <&panel_in>; }; }; &panel { status = "okay"; compatible = "longcheer,lm101kf01"; reg = <0>; power-supply = <&vcc_3v3>; avdd-supply = <&vcc_5v0>; vsp-supply = <&vcc_12v>; reset-gpios = <&pio4 RK_PA2 GPIO_ACTIVE_LOW>; backlight = <&backlight>; display-timings { native-mode = <&timing0>; timing0: timing@0 { clock-frequency = <60000000>; hactive = <1280>; vactive = <800>; hfront-porch = <160>; hback-porch = <160>; hsync-len = <20>; vfront-porch = <10>; vback-porch = <10>; vsync-len = <5>; hsync-active = <0>; vsync-active = <0>; de-active = <1>; pixelclk-active = <0>; }; }; panel-init-sequence = [ ff 00 00 00 // Page Select 0 11 00 00 00 // Sleep Out 29 00 00 00 // Display On b0 01 00 00 // Page Select 1 c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 // Gamma Low c1 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 // Gamma High ]; };

关键点:vsp-supply单独定义,reset-gpios用GPIO_ACTIVE_LOW,panel-init-sequence严格按Datasheet顺序,Gamma分高低两段写入。

4.4 drm_panel驱动编写:drivers/gpu/drm/panel/panel-longcheer-lm101kf01.c

#include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/regulator/consumer.h> #include <linux/backlight.h> #include <drm/drm_panel.h> #include <drm/drm_mipi_dsi.h> struct longcheer_panel { struct drm_panel base; struct device *dev; struct regulator *vcc; struct regulator *avdd; struct regulator *vsp; struct gpio_desc *reset_gpio; struct backlight_device *backlight; }; static int longcheer_panel_prepare(struct drm_panel *panel) { struct longcheer_panel *lpanel = container_of(panel, struct longcheer_panel, base); // 上电顺序 regulator_enable(lpanel->vcc); usleep_range(10000, 12000); // VCC→AVDD 10ms regulator_enable(lpanel->avdd); usleep_range(50000, 52000); // AVDD→VSP 50ms regulator_enable(lpanel->vsp); usleep_range(100000, 102000); // VSP→RESET 100ms // 复位 gpiod_set_value_cansleep(lpanel->reset_gpio, 0); usleep_range(120000, 122000); // RESET低电平120ms gpiod_set_value_cansleep(lpanel->reset_gpio, 1); usleep_range(120000, 122000); // RESET高电平120ms return 0; } static int longcheer_panel_enable(struct drm_panel *panel) { struct longcheer_panel *lpanel = container_of(panel, struct longcheer_panel, base); struct mipi_dsi_device *dsi = to_mipi_dsi_device(lpanel->dev); // 发送初始化序列 mipi_dsi_dcs_write(dsi, MIPI_DCS_EXIT_SLEEP_MODE, NULL, 0); mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_ON, NULL, 0); // Gamma校准(略,调用mipi_dsi_generic_write) return 0; } static int longcheer_panel_disable(struct drm_panel *panel) { struct longcheer_panel *lpanel = container_of(panel, struct longcheer_panel, base); struct mipi_dsi_device *dsi = to_mipi_dsi_device(lpanel->dev); mipi_dsi_dcs_write(dsi, MIPI_DCS_SET_DISPLAY_OFF, NULL, 0); return 0; } static int longcheer_panel_unprepare(struct drm_panel *panel) { struct longcheer_panel *lpanel = container_of(panel, struct longcheer_panel, base); regulator_disable(lpanel->vsp); regulator_disable(lpanel->avdd); regulator_disable(lpanel->vcc); return 0; } static const struct drm_panel_funcs longcheer_panel_funcs = { .prepare = longcheer_panel_prepare, .enable = longcheer_panel_enable, .disable = longcheer_panel_disable, .unprepare = longcheer_panel_unprepare, }; static int longcheer_panel_probe(struct platform_device *pdev) { struct longcheer_panel *lpanel; struct device *dev = &pdev->dev; int ret; lpanel = devm_kzalloc(dev, sizeof(*lpanel), GFP_KERNEL); if (!lpanel) return -ENOMEM; lpanel->dev = dev; lpanel->vcc = devm_regulator_get(dev, "power"); lpanel->avdd = devm_regulator_get(dev, "avdd"); lpanel->vsp = devm_regulator_get(dev, "vsp"); lpanel->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); lpanel->backlight = devm_of_find_backlight(dev); ret = drm_panel_init(&lpanel->base, dev, &longcheer_panel_funcs, DRM_MODE_CONNECTOR_DSI); if (ret < 0) return ret; drm_panel_add(&lpanel->base); platform_set_drvdata(pdev, lpanel); return 0; } static int longcheer_panel_remove(struct platform_device *pdev) { struct longcheer_panel *lpanel = platform_get_drvdata(pdev); drm_panel_remove(&lpanel->base); return 0; } static const struct of_device_id longcheer_panel_of_match[] = { { .compatible = "longcheer,lm101kf01" }, { } }; MODULE_DEVICE_TABLE(of, longcheer_panel_of_match); static struct platform_driver longcheer_panel_driver = { .probe = longcheer_panel_probe, .remove = longcheer_panel_remove, .driver = { .name = "panel-longcheer-lm101kf01", .of_match_table = longcheer_panel_of_match, }, }; module_platform_driver(longcheer_panel_driver);

关键点:prepare()里严格按Datasheet时序延时;enable()调用标准mipi_dsi_dcs_write();unprepare()按反序断电;of_match_table匹配Device Tree。

4.5 编译烧写与dmesg验证

编译步骤:

# 修改Kconfig,添加CONFIG_DRM_PANEL_LONGCHEER_LM101KF01=y # 修改Makefile,添加obj-$(CONFIG_DRM_PANEL_LONGCHEER_LM101KF01) += panel-longcheer-lm101kf01.o make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules sudo make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- INSTALL_MOD_PATH=/mnt/nfsroot modules_install

烧写后dmesg关键日志:

[ 2.102345] [drm] Initialized drm_kms_helper 4.20.0 20230101 for drm [ 2.103456] [drm] bound 1c00000.dsi@1c00000 (ops dsi_ops) [ 2.104567] [drm] Registered panel longcheer,lm1
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 12:34:28

大模型应用开发 100 天:Python + LLM 从入门到精通 day30~【阶段实战】构建 Prompt 管理与版本控制系统

Day 30:🏆 【阶段实战】构建一个 Prompt 管理与版本控制系统 🔖 阶段三:Prompt Engineering 深度实战(Day 21 - Day 30) 恭喜你走到了第三阶段的最后一天!今天我们将把前 9 天学到的所有 Prompt Engineering 技术整合起来,构建一个生产级的 Prompt 管理与版本控制系统…

作者头像 李华
网站建设 2026/10/8 12:33:24

开源 Skills 全生命周期创造平台:从 SkillNexus 到 TaoToken 的落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华