为广信EF33老年机移植lk2nd:ST7789V SPI屏幕驱动开发全记录
引言
在嵌入式系统开发领域,Bootloader移植往往是项目成功的关键第一步。当面对一款采用高通msm8909平台的广信EF33老年机时,原厂锁定的fastboot模式让lk2nd移植成为唯一选择。本文将详细记录如何通过逆向工程手段,为这款设备特有的ST7789V SPI屏幕开发驱动补丁的全过程。
不同于常见的MIPI接口,SPI屏幕在移动设备中相对少见,这为驱动开发带来了独特挑战。我们将从硬件分析开始,逐步深入到寄存器配置、GPIO映射和时钟树调整,最终提供两个可直接应用于lk2nd的补丁文件。这些实战经验对于任何需要在高通平台添加非标准显示设备的开发者都具有参考价值。
1. 硬件分析与逆向工程准备
1.1 设备基础信息确认
通过adb shell获取的硬件信息显示,这款广信EF33老年机采用以下配置:
- 高通msm8909平台(骁龙210)
- 512MB RAM + 4GB ROM
- 单摄像头,无触摸屏
- 2.4GHz WCN3610v1 WiFi模块
关键发现来自/proc/cmdline中的显示参数:
mdss_mdp3.panel=0: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输出显示关键GPIO:
gpio-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数据手册对比,发现厂商对以下参数做了定制:
| 寄存器 | 标准值 | 定制值 | 作用 |
|---|---|---|---|
| 0x36 | 0x00 | 0xC0 | 屏幕扫描方向 |
| 0xB2 | 0x0C | 0x0C | 空周期控制 |
| 0xB7 | 0x35 | 0x75 | 门控控制 |
2. lk2nd移植核心步骤
2.1 基础环境搭建
使用lk2nd的experimental-rebase分支而非官方LK,原因在于:
- 更好的GCC新版本兼容性
- 已包含msm8909基础支持
- 社区维护更活跃
编译命令:
make TOOLCHAIN_PREFIX=arm-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.c):
void 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 ID(
target/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)正确分频
- 时钟源选择GPLL0(800MHz)
调试时添加的日志输出:
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的深度定制。最重要的是保持对硬件文档的细致分析和对日志信息的充分利用,这两者往往是解决复杂移植问题的关键。