F1C100s 主线内核适配 ST7701 RGB 屏幕
目录
硬件环境
驱动移植
设备树配置
内核配置
问题解决
最终屏参
结语
硬件环境
- SoC: 全志 F1C100s (ARM926EJ-S)
- 屏幕: ST7701S, 2.95 寸竖屏, SPI 9 bit 初始化 + RGB565 并行数据,不接复位
- 接口: SPI (GPIO 软件模拟) + RGB565 HV 模式 (无 DE 信号)
- 内核: Linux 7.2
- 上游驱动: panel-sitronix-st7701.c
驱动移植
面板描述符 t295h5c4001_desc
根据厂商屏参文件配置, 包含显示时序、电源参数、Gamma 值、总线格式等。
初始显示时序 (直接照搬厂商值)
static const struct drm_display_mode t295h5c4001_mode = {
.clock = 28000, /* 像素时钟 28MHz */
.hdisplay = 360,
.hsync_start = 360 + 50, /* HFP = 50 */
.hsync_end = 360 + 50 + 8, /* HSW = 8 */
.htotal = 360 + 50 + 8 + 20, /* HBP = 20, htotal=438 */
.vdisplay = 640,
.vsync_start = 640 + 28, /* VFP = 28 */
.vsync_end = 640 + 28 + 4, /* VSW = 4 */
.vtotal = 640 + 28 + 4 + 32, /* VBP = 32, vtotal=704 */
.width_mm = 41,
.height_mm = 73,
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
};
面板描述符
static const struct st7701_panel_desc t295h5c4001_desc = {
.mode = &t295h5c4001_mode,
.panel_sleep_delay = 80, /* 厂商总等待 200ms (120 base + 80 extra) */
.bus_format = MEDIA_BUS_FMT_RGB565_1X16,
/* Gamma 校正 (厂商屏参直接提供) */
.pv_gamma = {
0x00, 0x0A, 0x11, 0x0C, 0x10, 0x05, 0x00, 0x08,
0x08, 0x1F, 0x07, 0x13, 0x10, 0xA9, 0x30, 0x18
},
.nv_gamma = {
0x00, 0x0B, 0x11, 0x0D, 0x0F, 0x05, 0x02, 0x07,
0x06, 0x20, 0x05, 0x15, 0x13, 0xA9, 0x30, 0x18
},
/* 电源参数 (基于厂商参数反算) */
.nlinv = 1,
.vop_uv = 4575000,
.vcom_uv = 1300000,
.vgh_mv = 15000,
.vgl_mv = -10170,
.avdd_mv = 6600,
.avcl_mv = -4600,
.gamma_op_bias = OP_BIAS_MIDDLE,
.input_op_bias = OP_BIAS_MIN,
.output_op_bias = OP_BIAS_MIN,
.t2d_ns = 1600,
.t3d_ns = 10400,
.eot_en = true,
.gip_sequence = t295h5c4001_gip_sequence,
};
struct st7701_panel_desc 添加 bus_format 属性,并在 st7701_get_modes 中添加通过 bus_format 字段将 RGB565 格式传递给 TCON:
if (st7701->desc->bus_format)
drm_display_info_set_bus_formats(&connector->display_info,
&st7701->desc->bus_format, 1);
驱动注册与匹配 (SPI 接口)
of_device_id 匹配表和 SPI ID 表:
static const struct of_device_id st7701_spi_of_match[] = {
{ .compatible = "anbernic,rg28xx-panel", .data = &rg28xx_desc },
{ .compatible = "goldenmorning,t295h5c4001-panel", .data = &t295h5c4001_desc },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, st7701_spi_of_match);
static const struct spi_device_id st7701_spi_ids[] = {
{ "rg28xx-panel" },
{ "t295h5c4001-panel" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, st7701_spi_ids);
设备树配置
dtsi 补充
由于 F1C100s 在主线内核中没有 lcd-controller 等相关驱动,参考社区使用了 sun4i 的驱动,因此在 suniv-f1c100s.dtsi 中补充。
根节点下补充:
de: display-engine {
compatible = "allwinner,sun4i-a10-display-engine";
allwinner,pipelines = <&fe0>;
status = "disabled";
};
soc 节点下补充:
tcon0: lcd-controller@1c0c000 {
compatible = "allwinner,sun4i-a10-tcon";
reg = <0x01c0c000 0x1000>;
interrupts = <29>;
resets = <&ccu RST_BUS_LCD>;
reset-names = "lcd";
clocks = <&ccu CLK_BUS_LCD>,
<&ccu CLK_TCON>,
<&ccu CLK_TVE1_CLK>;
clock-names = "ahb",
"tcon-ch0",
"tcon-ch1";
clock-output-names = "tcon-pixel-clock";
#clock-cells = <0>;
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
tcon0_in: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
tcon0_in_be0: endpoint@0 {
reg = <0>;
remote-endpoint = <&be0_out_tcon0>;
};
};
tcon0_out: port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
};
};
};
fe0: display-frontend@1e00000 {
compatible = "allwinner,sun4i-a10-display-frontend";
reg = <0x01e00000 0x20000>;
interrupts = <30>;
clocks = <&ccu CLK_BUS_DE_FE>, <&ccu CLK_DE_FE>,
<&ccu CLK_DRAM_DE_FE>;
clock-names = "ahb", "mod",
"ram";
resets = <&ccu RST_BUS_DE_FE>;
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
fe0_out: port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
fe0_out_be0: endpoint@0 {
reg = <0>;
remote-endpoint = <&be0_in_fe0>;
};
};
};
};
be0: display-backend@1e60000 {
compatible = "allwinner,sun4i-a10-display-backend";
reg = <0x01e60000 0x10000>;
reg-names = "be";
interrupts = <31>;
clocks = <&ccu CLK_BUS_DE_BE>, <&ccu CLK_DE_BE>,
<&ccu CLK_DRAM_DE_BE>;
clock-names = "ahb", "mod",
"ram";
resets = <&ccu RST_BUS_DE_BE>;
reset-names = "be";
assigned-clocks = <&ccu CLK_DE_BE>;
assigned-clock-rates = <300000000>;
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
be0_in: port@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
be0_in_fe0: endpoint@0 {
reg = <0>;
remote-endpoint = <&fe0_out_be0>;
};
};
be0_out: port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
be0_out_tcon0: endpoint@0 {
reg = <0>;
remote-endpoint = <&tcon0_in_be0>;
};
};
};
};
引脚分配
| 引脚 | 功能 | 说明 |
|---|---|---|
| PD1-PD11 | LCD 数据 | RGB565 数据线 (F1C100s LCD D3-D15 映射) |
| PD13-PD17 | LCD 数据 | RGB565 数据线 (F1C100s LCD D19-D23 映射) |
| PD18 | LCD_PCLK | 像素时钟 |
| PD20 | LCD_HS | 行同步 |
| PD21 | LCD_VS | 列同步 |
| PD19 | SPI SCK | 软件 SPI 时钟 |
| PE4 | SPI MOSI | 软件 SPI 数据输出 |
| PE11 | SPI CS | 片选 (低有效) |
| PE12 | PWM0 | 背光 PWM 输出 |
PD0 和 PD12 用作 I2C, PD19 因 SPI SCK 占用被排除。
关键设备树配置
根节点下添加 pwm_backlight 和 spi_gpio(panel) :
pwm_backlight: backlight {
compatible = "pwm-backlight";
pwms = <&pwm 0 10000 0>;
brightness-levels = <0 4 8 16 32 64 128 196 220 255>;
default-brightness-level = <6>;
};
spi_gpio: spi-gpio {
compatible = "spi-gpio";
#address-cells = <1>;
#size-cells = <0>;
sck-gpios = <&pio 3 19 GPIO_ACTIVE_HIGH>;
mosi-gpios = <&pio 4 4 GPIO_ACTIVE_HIGH>;
cs-gpios = <&pio 4 11 GPIO_ACTIVE_LOW>;
num-chipselects = <1>;
status = "okay";
panel@0 {
compatible = "goldenmorning,t295h5c4001-panel";
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
spi-max-frequency = <100000>;
backlight = <&pwm_backlight>;
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
panel_input: endpoint@0 {
reg = <0>;
remote-endpoint = <&tcon0_out_lcd>;
};
};
};
};
其他重要节点:
&de {
status = "okay";
};
&tcon0 {
pinctrl-names = "default";
pinctrl-0 = <&lcd_rgb565_no_de_pins>;
status = "okay";
};
&tcon0_out {
tcon0_out_lcd: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_input>;
};
};
&fe0 {
status = "okay";
};
&be0 {
status = "okay";
};
&pio {
pwm0_pe_pins: pwm0-pe-pins {
pins = "PE12";
function = "pwm0";
};
lcd_rgb565_no_de_pins: lcd-rgb565-no-de-pins {
pins = "PD1", "PD2", "PD3", "PD4", "PD5",
"PD6", "PD7", "PD8", "PD9", "PD10", "PD11",
"PD13", "PD14", "PD15", "PD16", "PD17",
"PD18", "PD20", "PD21";
function = "lcd";
};
};
&pwm {
pinctrl-names = "default";
pinctrl-0 = <&pwm0_pe_pins>;
status = "okay";
};
内核配置
DRM 显示框架和面板驱动
Device Drivers --->
Graphics support --->
<*> Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)
Supported DRM clients --->
[*] Enable legacy fbdev support for your modesetting driver
<*> DRM Support for Allwinner A10 Display Engine
[*] Sun4i/Sun5i/Sun7i/Sun8i/Sun9i Display Backend
[*] Allwinner A10 Display Engine support
Display Panels --->
<*> Sitronix ST7701 panel driver
Frame buffer Devices --->
<*> Support for frame buffer device drivers ----
[*] Provide legacy /dev/fb* device
SPI 软件 GPIO 驱动
Device Drivers --->
SPI support --->
<*> GPIO-based bitbanging SPI Master
背光与 PWM
Device Drivers --->
Graphics support --->
[*] Backlight & LCD device support --->
<*> Lowlevel Backlight controls
<*> Generic PWM based Backlight support
PWM Support --->
<*> Allwinner PWM support
控制台
Device Drivers --->
Console display driver support --->
[*] Framebuffer Console support
-*- Map the console to the primary display device
[*] Bootup logo --->
问题解决
1. 竖条纹无显示
根因: 厂商 STM32 代码使用 DE 模式 (C3h=0x10), 移植时直接照搬。但实际硬件未连接 DE 引脚, TCON 也未配置 DE 输出。ST7701S 在 DE 模式下等待 DE 信号, 永远收不到。
修复: gip_sequence 中 C3h 改为 0x11, HV 模式:
ST7701_WRITE(st7701, 0xC3, 0x11, 0x08);
2. 色彩异常
根因: MADCTL (0x36) 寄存器值 0x80 的颜色通道顺序与实际 RGB565 接线不匹配。
修复: MADCTL 改为 0x84 (bit2 置位, 交换 RGB 通道):
ST7701_WRITE(st7701, MIPI_DCS_SET_ADDRESS_MODE, 0x84);
3. 偏移
根因:
- gip_sequence 覆盖了 PORCTRL (0x07,0x02) 和 INVSEL (0x31,0x05) 为厂商 DE 模式值, 与 TCON 实际时序不匹配;
- htotal=438 低于 ST7701S 硬件最小值 512 PCLK/行 (RTNI 字段公式: PCLK = 512 + RTNI[4:0] × 16, RTNI 为 5 位无符号, 最小值 0 对应 512);
- 屏厂参数不可靠,无法直接使用。
修复:
- 删除 gip_sequence 中 PORCTRL 和 INVSEL 的覆盖, 让 init_sequence 从 mode 自动计算;
- HBP 从 20 改为 60, HFP 从 32 调整为 84, htotal 从 438 改为 512;
- VBP 从 32 调整为 1, VFP 从 28 调整为 59, vtotal 保持 704 不变。
4. 冷启动帧截断
以下为 AI 推测。
根因: DRM 框架执行顺序为 prepare() → CRTC enable (TCON 启动) → enable()。prepare 阶段执行 sleep out (0x11) 时 TCON 尚未启动, ST7701S 没有外部 VSYNC 可锁定, 内部行计数器从随机位置开始运转。等到 TCON 启动输出 VSYNC 时, ST7701S 已经在错误位置运行, 不会自动重新同步, 导致画面从中间某行开始显示 (帧截断)。热启动正常是因为 ST7701S 未断电, 内部时序引擎保持运转, sleep out 后能快速恢复同步。
修复: 在 st7701_enable() 中 (此时 TCON 已运行) 做一次 sleep in → sleep out 循环, 让 ST7701S 重新锁定 VSYNC:
static int st7701_enable(struct drm_panel *panel)
{
struct st7701 *st7701 = panel_to_st7701(panel);
ST7701_WRITE(st7701, MIPI_DCS_ENTER_SLEEP_MODE, 0x00);
msleep(50);
ST7701_WRITE(st7701, MIPI_DCS_EXIT_SLEEP_MODE, 0x00);
msleep(120);
ST7701_WRITE(st7701, MIPI_DCS_SET_DISPLAY_ON, 0x00);
return 0;
}
最终屏参
显示模式参数
static const struct drm_display_mode t295h5c4001_mode = {
.clock = 24000,
.hdisplay = 360,
.hsync_start = 360 + 84, // HFP=84
.hsync_end = 360 + 84 + 8, // HSW=8
.htotal = 360 + 84 + 8 + 60, // HBP=60
.vdisplay = 640,
.vsync_start = 640 + 59, // VFP=59
.vsync_end = 640 + 59 + 4, // VSW=4
.vtotal = 640 + 59 + 4 + 1, // VBP=1
.width_mm = 41,
.height_mm = 73,
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
};
时序参数汇总:
| 参数 | 初始值 | 最终值 | 说明 |
|---|---|---|---|
| clock | 28000 | 24000 | 28MHz 文字不清晰, 改 24MHz |
| hdisplay | 360 | 360 | 不变 |
| HFP | 50 | 84 | 增大 (补偿 htotal 提高到 512) |
| HSW | 8 | 8 | 不变 |
| HBP | 20 | 60 | 等于 LCD_W_OFFSET |
| htotal | 438 | 512 | ST7701S 最小 PCLK/行 |
| vdisplay | 640 | 640 | 不变 |
| VFP | 28 | 59 | 增大 (补偿 VBP 减小) |
| VSW | 4 | 4 | 不变 |
| VBP | 32 | 1 | 减小 (修正纵向偏移) |
| vtotal | 704 | 704 | 不变 |
| 刷新率 | ~90.7 Hz | ~66.6 Hz | 24000000 / (512 × 704) |
gip_sequence 最终版本
static void t295h5c4001_gip_sequence(struct st7701 *st7701)
{
st7701_switch_cmd_bkx(st7701, true, 0);
// ST7701_WRITE(st7701, ST7701_CMD2_BK0_PORCTRL, 0x07, 0x02);
// ST7701_WRITE(st7701, ST7701_CMD2_BK0_INVSEL, 0x31, 0x05);
ST7701_WRITE(st7701, 0xC3, 0x11, 0x08);
ST7701_WRITE(st7701, 0xCC, 0x10);
st7701_switch_cmd_bkx(st7701, true, 1);
ST7701_WRITE(st7701, 0xBB, 0x03);
ST7701_WRITE(st7701, 0xBC, 0x75);
ST7701_WRITE(st7701, 0xCD, 0x08);
msleep(100);
ST7701_WRITE(st7701, 0xE0, 0x00, 0x00, 0x02);
ST7701_WRITE(st7701, 0xE1, 0x03, 0xA0, 0x00, 0x00, 0x02, 0xA0, 0x00,
0x00, 0x00, 0x33, 0x33);
ST7701_WRITE(st7701, 0xE2, 0x22, 0x22, 0x33, 0x33, 0x88, 0xA0, 0x00,
0x00, 0x87, 0xA0, 0x00, 0x00);
ST7701_WRITE(st7701, 0xE3, 0x00, 0x00, 0x22, 0x22);
ST7701_WRITE(st7701, 0xE4, 0x44, 0x44);
ST7701_WRITE(st7701, 0xE5, 0x04, 0x84, 0xA0, 0xA0, 0x06, 0x86, 0xA0,
0xA0, 0x08, 0x88, 0xA0, 0xA0, 0x0A, 0x8A, 0xA0, 0xA0);
ST7701_WRITE(st7701, 0xE6, 0x00, 0x00, 0x22, 0x22);
ST7701_WRITE(st7701, 0xE7, 0x44, 0x44);
ST7701_WRITE(st7701, 0xE8, 0x03, 0x83, 0xA0, 0xA0, 0x05, 0x85, 0xA0,
0xA0, 0x07, 0x87, 0xA0, 0xA0, 0x09, 0x89, 0xA0, 0xA0);
ST7701_WRITE(st7701, 0xEB, 0x00, 0x01, 0xE4, 0xE4, 0x88, 0x00, 0x40);
ST7701_WRITE(st7701, 0xEC, 0x3C, 0x01);
ST7701_WRITE(st7701, 0xED, 0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA);
ST7701_WRITE(st7701, 0xEF, 0x10, 0x0D, 0x04, 0x08, 0x3F, 0x1F);
st7701_switch_cmd_bkx(st7701, true, 3);
ST7701_WRITE(st7701, 0xE6, 0x16, 0x7C);
ST7701_WRITE(st7701, 0xE8, 0x00, 0x0E);
st7701_switch_cmd_bkx(st7701, true, 3);
ST7701_WRITE(st7701, 0xE8, 0x00, 0x0C);
ST7701_WRITE(st7701, 0xE8, 0x00, 0x00);
st7701_switch_cmd_bkx(st7701, false, 0);
ST7701_WRITE(st7701, MIPI_DCS_SET_ADDRESS_MODE, 0x84);
ST7701_WRITE(st7701, MIPI_DCS_SET_PIXEL_FORMAT, 0x55);
ST7701_WRITE(st7701, MIPI_DCS_EXIT_SLEEP_MODE);
msleep(120);
}
结语
本次移植完成了 F1C100s SoC 平台上一款 ST7701S SPI+RGB565 屏幕在 Linux 7.2 的适配,历时约两天。得益于社区版 dtsi ,在内核和设备树配置上还算比较顺利,驱动是在上游 st7701 的驱动上修改而来。

浙公网安备 33010602011771号