lk2nd 移植实战:为广信 EF33 老年机添加 ST7789V SPI 屏支持的 2 个核心补丁
为广信EF33老年机移植lk2ndST7789V SPI屏幕驱动开发全记录引言在嵌入式系统开发领域Bootloader移植往往是项目成功的关键第一步。当面对一款采用高通msm8909平台的广信EF33老年机时原厂锁定的fastboot模式让lk2nd移植成为唯一选择。本文将详细记录如何通过逆向工程手段为这款设备特有的ST7789V SPI屏幕开发驱动补丁的全过程。不同于常见的MIPI接口SPI屏幕在移动设备中相对少见这为驱动开发带来了独特挑战。我们将从硬件分析开始逐步深入到寄存器配置、GPIO映射和时钟树调整最终提供两个可直接应用于lk2nd的补丁文件。这些实战经验对于任何需要在高通平台添加非标准显示设备的开发者都具有参考价值。1. 硬件分析与逆向工程准备1.1 设备基础信息确认通过adb shell获取的硬件信息显示这款广信EF33老年机采用以下配置高通msm8909平台骁龙210512MB RAM 4GB ROM单摄像头无触摸屏2.4GHz WCN3610v1 WiFi模块关键发现来自/proc/cmdline中的显示参数mdss_mdp3.panel0:spi:0:qcom,mdss_spi_st7789v_ctc_qvga_nrx_cmd这明确指出了屏幕型号为ST7789V采用SPI接口。1.2 调试接口利用由于fastboot被厂商锁定我们通过以下途径获取硬件信息挂载debugfs获取GPIO状态mount -t debugfs debugfs /sdcard/debug cat /sdcard/debug/gpio输出显示关键GPIOgpio-928 (dc-gpios ) out hi gpio-1022 (red ) out lo gpio-1023 (green ) out hi通过EDL模式提取固件获得lk二进制文件。高通平台的Bootloader带有ELF头这大大简化了逆向工程工作。1.3 Ghidra逆向分析将提取的lk二进制导入Ghidra后我们重点关注以下数据结构struct mdss_spi_cmd { int size; // 指令长度 char *payload; // 指令内容 int wait; // 延迟时间 uint8_t cmds_post_tg; };通过交叉引用分析我们定位到屏幕初始化序列存储在st7789v_ctc_qvga_nrx_cmd_on_command数组中。与标准ST7789V数据手册对比发现厂商对以下参数做了定制寄存器标准值定制值作用0x360x000xC0屏幕扫描方向0xB20x0C0x0C空周期控制0xB70x350x75门控控制2. lk2nd移植核心步骤2.1 基础环境搭建使用lk2nd的experimental-rebase分支而非官方LK原因在于更好的GCC新版本兼容性已包含msm8909基础支持社区维护更活跃编译命令make TOOLCHAIN_PREFIXarm-none-eabi- msm8909 -j82.2 屏幕驱动集成在target/msm8909/oem_panel.c中添加以下关键修改#include include/panel_st7789v_ctc_qvga_nrx_cmd.h enum { AUO_390P_CMD_PANEL, ST7789v2_QVGA_SPI_CMD_PANEL, ST7789V_CTC_QVGA_NRX_CMD_PANEL, UNKNOWN_PANEL }; static struct panel_list supp_panels[] { {auo_390p_cmd, AUO_390P_CMD_PANEL}, {ST7789V2_qvga_cmd, ST7789v2_QVGA_SPI_CMD_PANEL}, {st7789v_ctc_qvga_nrx_cmd, ST7789V_CTC_QVGA_NRX_CMD_PANEL}, };初始化函数中绑定逆向得到的数据结构case ST7789V_CTC_QVGA_NRX_CMD_PANEL: panelstruct-paneldata st7789v_ctc_qvga_nrx_cmd_panel_data; panelstruct-panelres st7789v_ctc_qvga_nrx_cmd_panel_res; panelstruct-color st7789v_ctc_qvga_nrx_cmd_color; pinfo-spi.panel_cmds st7789v_ctc_qvga_nrx_cmd_on_command; pan_type PANEL_TYPE_SPI; break;2.3 SPI总线配置调整通过逆向设备树发现屏幕连接在BLSP1_QUP5而非lk2nd默认的QUP4。需要以下修改时钟寄存器定义platform/msm8909/include/platform/iomap.h#define GCC_BLSP1_QUP6_SPI_APPS_CBCR (CLK_CTL_BASE 0x701C) #define GCC_BLSP1_QUP6_SPI_APPS_CMD_RCGR (CLK_CTL_BASE 0x7024)GPIO配置platform/msm8909/gpio.cvoid gpio_config_blsp_spi(uint8_t blsp_id, uint8_t qup_id) { if(blsp_id BLSP_ID_1 qup_id QUP_ID_5) { gpio_tlmm_config(8, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); // MOSI gpio_tlmm_config(9, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); // MISO gpio_tlmm_config(10, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); // CS gpio_tlmm_config(11, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); // CLK } }修改默认QUP IDtarget/msm8909/include/target/display.h-#define SPI_QUP_ID 4 #define SPI_QUP_ID 53. 关键问题解决与调试3.1 白屏问题排查初期烧录后出现白屏但背光亮起的现象通过以下步骤定位问题确认初始化日志显示驱动已加载[LK] panel st7789v_ctc_qvga_nrx_cmd init使用逻辑分析仪抓取SPI信号发现CS线无活动对比设备树与代码发现QUP配置不匹配qcom,mdss_spi_client { reg 0x00; label MDSS SPI QUP5 CLIENT; spi-max-frequency 0x2faf080; // 50MHz };3.2 时钟配置验证BLSP时钟树配置需要确保核心时钟BLSP1_AHB_CLK已使能SPI应用时钟BLSP1_SPI_APPS_CLK正确分频时钟源选择GPLL0800MHz调试时添加的日志输出dprintf(INFO, BLSP1_CLK_ENA: 0x%x\n, readl(GCC_BLSP1_AHB_CBCR)); dprintf(INFO, SPI_APPS_CLK_SRC: 0x%x\n, readl(GCC_BLSP1_QUP5_SPI_APPS_CMD_RCGR));3.3 电源时序调整ST7789V要求严格的电源上电顺序VCCIO (1.8V) 先上电延迟10ms后上电VCC (2.8V)再延迟5ms后释放复位通过修改panel_st7789v_ctc_qvga_nrx_cmd_reset_seq实现static struct reset_seq st7789v_ctc_qvga_nrx_cmd_reset_seq { .sequence { {1, 0, 1}, // GPIO928 (RESET) 拉低 {10, 0, 0}, // 保持10ms {0, 1, 1}, // GPIO928 拉高 {120, 0, 0} // 保持120ms }, .size 4 };4. 完整补丁文件4.1 oem_panel.c补丁--- a/target/msm8909/oem_panel.c b/target/msm8909/oem_panel.c -54,6 56,7 #include include/panel_auo_390p_cmd.h #include include/panel_st7789v2_qvga_spi_cmd.h #include include/panel_gc9305_qvga_spi_cmd.h #include include/panel_st7789v_ctc_qvga_nrx_cmd.h enum { AUO_390P_CMD_PANEL, -86,6 89,7 static struct panel_list supp_panels[] { {auo_390p_cmd, AUO_390P_CMD_PANEL}, {ST7789V2_qvga_cmd, ST7789v2_QVGA_SPI_CMD_PANEL}, {st7789v_ctc_qvga_nrx_cmd, ST7789V_CTC_QVGA_NRX_CMD_PANEL}, {gc9305_qvga_cmd, GC9305_QVGA_SPI_CMD_PANEL}, UNKNOWN_PANEL }; -502,20 506,22 uint32_t platform_type board_platform_id(); uint32_t platform_subtype board_hardware_subtype(); int32_t panel_override_id; int oem_panel_id oem_read_panel_id(); dprintf(INFO, [HACK] oem_panel_id :%d\n,oem_panel_id); - if (panel_name) { if (1) { panel_override_id panel_name_to_id(supp_panels, - ARRAY_SIZE(supp_panels), panel_name); ARRAY_SIZE(supp_panels), st7789v_ctc_qvga_nrx_cmd); if (panel_override_id 0) { dprintf(CRITICAL, Not able to search the panel:%s\n, - panel_name); st7789v_ctc_qvga_nrx_cmd); } else if (panel_override_id UNKNOWN_PANEL) { /* panel override using fastboot oem command */ panel_id panel_override_id; dprintf(INFO, OEM panel override:%s\n, - panel_name); st7789v_ctc_qvga_nrx_cmd); goto panel_init; } }4.2 gpio.c补丁--- a/platform/msm8909/gpio.c b/platform/msm8909/gpio.c -124,13 124,6 GPIO_8MA, GPIO_DISABLE); break; case QUP_ID_5: - /* configure I2C SDA gpio */ - gpio_tlmm_config(18, 3, GPIO_OUTPUT, GPIO_NO_PULL, - GPIO_8MA, GPIO_DISABLE); - - /* configure I2C SCL gpio */ - gpio_tlmm_config(19, 3, GPIO_OUTPUT, GPIO_NO_PULL, - GPIO_8MA, GPIO_DISABLE); break; default: dprintf(CRITICAL, Incorrect QUP id %d\n,qup_id); -147,51 140,33 { if(blsp_id BLSP_ID_1) { switch (qup_id) { - case QUP_ID_3: - /* configure SPI MOSI gpio */ - gpio_tlmm_config(12, 1, GPIO_OUTPUT, GPIO_NO_PULL, - GPIO_16MA, GPIO_DISABLE); - - /* configure SPI MISO gpio */ - gpio_tlmm_config(13, 1, GPIO_OUTPUT, GPIO_NO_PULL, - GPIO_16MA, GPIO_DISABLE); - - /* configure SPI CS_N gpio */ - gpio_tlmm_config(14, 1, GPIO_OUTPUT, GPIO_NO_PULL, - GPIO_16MA, GPIO_DISABLE); - - /* configure SPI CLK gpio */ - gpio_tlmm_config(15, 1, GPIO_OUTPUT, GPIO_NO_PULL, - GPIO_16MA, GPIO_DISABLE); - break; - case QUP_ID_4: case QUP_ID_5: /* configure SPI MOSI gpio */ - gpio_tlmm_config(16, 1, GPIO_OUTPUT, GPIO_NO_PULL, gpio_tlmm_config(8, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); /* configure SPI MISO gpio */ - gpio_tlmm_config(17, 1, GPIO_OUTPUT, GPIO_NO_PULL, gpio_tlmm_config(9, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); /* configure SPI CS_N gpio */ - gpio_tlmm_config(18, 1, GPIO_OUTPUT, GPIO_NO_PULL, gpio_tlmm_config(10, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); /* configure SPI CLK gpio */ - gpio_tlmm_config(19, 1, GPIO_OUTPUT, GPIO_NO_PULL, gpio_tlmm_config(11, 1, GPIO_OUTPUT, GPIO_NO_PULL, GPIO_16MA, GPIO_DISABLE); - break; - - case QUP_ID_0: - case QUP_ID_1: - case QUP_ID_2: - case QUP_ID_5: default: dprintf(CRITICAL, Incorrect QUP id %d\n,qup_id); ASSERT(0);5. 验证与后续工作成功烧录修改后的lk2nd后设备能够正常显示Bootloader界面。通过以下命令验证显示子系统状态fastboot oem panel-info输出应包含Panel: st7789v_ctc_qvga_nrx_cmd Interface: SPI Resolution: 240x320后续可继续完善添加动态屏幕旋转支持实现帧缓冲控制台优化SPI传输效率启用DMA模式为主线Linux内核开发对应的tinydrm驱动这个案例展示了即使面对非标准硬件配置通过系统化的逆向工程和模块化修改也能成功实现Bootloader的深度定制。最重要的是保持对硬件文档的细致分析和对日志信息的充分利用这两者往往是解决复杂移植问题的关键。