0%

OrangePi-3B 折腾笔记(六)—— 点亮LCD

编译环境

  • 主板: OrangePi-3b
  • 芯片: RK3566
  • 环境: Debian(12-x86_64) + Docker(Debian:12)

内容说明

买了这个 OrangePi 3b 之后,顺手给它配一个5寸的屏幕。

淘宝里面找到一款设计给树莓派使用的屏幕,刚好 OrangePi 3b 的打孔位置和这个屏幕完全匹配,所有果断入手。

屏幕链接:https://item.taobao.com/item.htm?id=627655701617

但是,这块屏实测默认只有香橙派的 Android 固件能够点亮它,其他的都不能直接点亮(LCD 屏通常需要特殊适配),不过至少也证明硬件是可以兼容的。

这就来适配一下。

移植

咨询网店老板屏幕相关参数信息,得知使用 raspberrypi,7inch-touchscreen-panel 就能驱动。香橙派的内核中包含这个驱动。

本来是想直接去香橙派的内核仓库抄作业的,但是发现香橙派只有 5.10 的内核适配了这块屏幕,直接拷过来到 6.6 的设备树中很多节点是无法识别的。

只能硬着头皮琢磨了。

不过香橙派的设备树倒是给了大致的思路。

使用 Device Tree Overlays 定义增量 DT

由于驱动是现成的,那么主要工作就是编辑设备树。按照主线内核的设备树架构,我这个设备并不是属于开发板,而是一个外设,那么我不应该直接修改通用的 rk3566-orangepo-3b.dts 文件,而应该使用 Device Tree Overlays 的方式定义。

Device Tree Overlays 是一种 DT 的增量扩展方案。一个母体 DT 可以通过融合很多 dtbo 文件进行动态扩展,达到一种解耦的效果。

首先来建立 Device Tree Overlays 的基本编译结构:

diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index 88ed4077425b..1405fe6ecdf0 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -106,3 +106,6 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588-rock-5b.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-indiedroid-nova.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-khadas-edge2.dtb
 dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3588s-rock-5a.dtb
+
+rk3566-orangepi-3b-custom-dtbs := rk3566-orangepi-3b.dtb rk356x-raspi-7inch-touchscreen.dtbo
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3566-orangepi-3b-custom.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk356x-raspi-7inch-touchscreen.dtso b/arch/arm64/boot/dts/rockchip/rk356x-raspi-7inch-touchscreen.dtso
new file mode 100644
index 000000000000..0e5000a4d9d8
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk356x-raspi-7inch-touchscreen.dtso
@@ -0,0 +1,75 @@
+/dts-v1/;
+/plugin/;
+
+&{/} {
+
+};
  1. kernel6.x5.x 关联 dtbo 规则是不一样的,这里演示的是 6.x 的版本;
  2. dtso 文件的写法有两种,其中一种新式写法和普通的 dts 很类似,下面就都采用的这种写法;
  3. Makefile 中的两句的意思是通过 rk3566-orangepi-3b.dtsrk356x-raspi-7inch-touchscreen.dtso 构建出 rk3566-orangepi-3b-custom.dtb
    • rk3566-orangepi-3b.dts 是构建 kernel 时增对 OrangePi-3B 加入的 DT,是一个比较通用的 DT
    • rk356x-raspi-7inch-touchscreen.dtso 是这次针对屏幕新增加的 DT;他有 /plugin/ 标志,表示是一个插件 DT
    • rk3566-orangepi-3b-custom.dtbrk3566-orangepi-3b.dtsrk356x-raspi-7inch-touchscreen.dtso 合并构建出的产物,它还可以融合更多的 dtso 文件;
  4. 产物会构建出 rk3566-orangepi-3b-custom.dtbrk356x-raspi-7inch-touchscreen.dtbo;前者可以用在 u-boot 不支持 fdt 命令的场景,后者可以使用 u-boot 通过 fdt 命令进行融合;
  5. 上面 dts/dtbdtso/dtbo 变来变去没有问题,**s* 是源文件后缀,编译后的产物就是 **b* 后缀;

使能 DSI 协议接口

首先,这是一块 LCD 屏幕,物理尺寸为 5inch,物理分辨率为 800x480,带电容触摸屏,接口为 MIPI,使用 15p 排线和 OrangePi 3B 连接。查看 OrangePi 3B 的原理图得知,连接屏幕的 MIPI 口在 RK3566 中为 MIPI1(图纸中标签为 LCD1)。

进一步查阅资料:在 MIPI 规范中,MIPI-DSI 协议接口用于图像输出,MIPI-CSI 协议接口用于图像输入(比如摄像头)。

所以参照原理图:先将对应的 dsi1 节点使能;在 rk356x.dtsi 文件中发现 dts1 节点对应的 phys 关联到了 dsi_dphy1 节点, 那也一起使能:

&dsi1 {
	status = "okay";
};

&dsi_dphy1 {
	status = "okay";
};
  1. rk356x.dtsirk3566rk3568 等设备树的爸爸,里面定义的设备默认都是 disable 的,要是需要使用那就得手动使能。

配置 MIPI-DSI 协议接口输入

然后,既然 MIPI-DSI 只是一种协议接口,那么它就需要有需要输入和输出信号。

rk356x.dtsi 文件中,已经给 dsi1 定义了两个端口 dsi1_indsi1_out,接下来就需要给这两个接口通过设备树联结对端。

端口 dsi1_indsi1 的输入,它的数据来源应该是系统的显卡什么的。在瑞芯微的设备树中,这个设备叫做 display_subsystem,它自己的端口又指向了 vop_out

瑞芯微给 vop_out 定义了 vp0vp1vp2 三路子端口,估计是支持三路视频输出。

其中 vp0rk3566-orangepi-3b.dts 中配置为连接 HDMI 的端口。

那么我这就使用 vp1 把:

#include <dt-bindings/soc/rockchip,vop2.h>

&vp1 {
	vp1_out_dsi1: endpoint@ROCKCHIP_VOP2_EP_MIPI1 {
		reg = <ROCKCHIP_VOP2_EP_MIPI1>;
		remote-endpoint = <&dsi1_in_vp2>;
	};
};

&dsi1_in {
	dsi1_in_vp2: endpoint {
		remote-endpoint = <&vp1_out_dsi1>;
	};
};

但是这样写编译 dtbs 的时候会抛警告,大致意思是 vp1dsi1_in 这些节点应该是一个 port,但没有指定。

强迫症果断受不了。

那就换种写法,其实就是声明包含 port 的节点路径,这些路径可以在 rk356x.dtsi 里面找到:

#include <dt-bindings/soc/rockchip,vop2.h>

&vop_out {
	#address-cells = <1>;
	#size-cells = <0>;

	vp1: port@1 {
		reg = <1>;
		#address-cells = <1>;
		#size-cells = <0>;

		vp1_out_dsi1: endpoint@ROCKCHIP_VOP2_EP_MIPI1 {
			reg = <ROCKCHIP_VOP2_EP_MIPI1>;
			remote-endpoint = <&dsi1_in_vp2>;
		};
	};
};

&dsi1 {
	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		dsi1_in: port@0 {
			reg = <0>;
			dsi1_in_vp2: endpoint {
				remote-endpoint = <&vp1_out_dsi1>;
			};
		};
	};
};
  1. 实测 RK3566 支持至少两路视频,并且可以双屏异显;不过可能和分辨率有关,我的显示屏分辨率都不高。
  2. 设备树真的很神奇,就这样简单的配一下,就实现了两个不同设备的联结。

配置屏幕设备树

现在 MIPI-DSI 的输入部分已经好了,现在来看输出部分。在正式配置 MIPI-DSI 输出链路之前先把屏幕给挂到设备树上面。

上面说道,我用的屏幕是 MIPI 接口的,但 OrangePi-3B 和这块屏设计接口里面有一路 i2c,它用来和屏幕控制芯片进行信令交互。

参考香橙派官方的设备树修改,在 kernel 6.6 版本里面最终配置长这样:

&i2c1 {
	#address-cells = <1>;
	#size-cells = <0>;
	status = "okay";

	raspits_panel: raspits-panel@45 {
		compatible = "raspberrypi,7inch-touchscreen-panel";
		reg = <0x45>;

		port {
			panel_in_dsi1: endpoint {
				remote-endpoint = <&dsi1_out_panel>;
			};
		};
	};

	raspits_touch_ft5426: raspits-touch-ft5426@38 {
		compatible = "raspits_ft5426";
		reg = <0x38>;
	};
};
  1. i2c1 是根据香橙派的原理图得到的。
  2. 屏幕在设备树里面都叫面板,raspits_panel 定义了一个 portpanel_in_dsi1, 这是它的信号输入端口(看着很牛逼,但这些规则都是驱动程序里面的逻辑)。
  3. 因为这块屏还有一个触摸屏的能力,有现成的就一起抄过来了。但是这个驱动主线 kernel 中并没有,移植驱动的部分放后面。

配置 DSI 协议接口输出

屏幕好了之后来看 DSI 的输出端口,有了上面两个例子,其实已经很简单了,只需要和屏幕暴露的端口绑定起来就可以了:

&dsi1 {
	ports {
		#address-cells = <1>;
		#size-cells = <0>;

		dsi1_out: port@1 {
			reg = <1>;
			dsi1_out_panel: endpoint {
				remote-endpoint = <&panel_in_dsi1>;
			};
		};
	};
};

至此,设备树部分就写(其实是抄)完了,接下来为了让内核开机就能加载驱动,需要把上面设备树里面设备的驱动和内核一起编译。

修改编译配置

涉及到的驱动有:

#
# Touchscreen Support
#
CONFIG_INPUT=y
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_RASPITS_FT5426=y

#
# Display Panels
#
CONFIG_DRM=y
CONFIG_DRM_PANEL=y
CONFIG_DRM_ROCKCHIP=y
CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=y

#
# PHY Subsystem
#
CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY=y

配置有不少,但这不是开启显示功能的所有配置。因为有不少配置在 defconfig 里面默认是 y,我这只有那些不是 y 的部分。因为需要屏幕在没加载文件系统之前就能点亮。

这些配置还是挺复杂的,我自己其实是摸索了两三天才把屏幕点亮,至于为什么要有某个配置我说不好就不说了,我是根据设备树反向推导出来的。

举个例子,CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY 这个配置,是因为上面的 dts1compatiblerockchip,rk3568-mipi-dsi,这个 compatible 字段是驱动的识别,如果驱动要生效那么对应的字段值一定存在于驱动文件中。

于是我全局搜索 rockchip,rk3568-mipi-dsi, 发现在一个叫 dw-mipi-dsi-rockchip.c 的文件里面包含。

那想让驱动生效就等于得编译进入内核,所以就看这个文件的编译选项,有什么条件才能加入编译队列。一般就先看同级目录下的 Makefile 文件。

果然,在同级目录下的 Makefile 文件中发现他需要 CONFIG_ROCKCHIP_DW_MIPI_DSI 选项才能加入队列。

所有我得确保这个文件是被编译的,把它放到 .config 搜索发现已经是 y 了说明是会编译到内核不用等待文件系统加载的。

然后,除了它自己,还需要关注他的父级。一般是上级目录,看看是否会参与编译。打开上一级文件夹下面的 Makefile,查到当前文件夹 rockchip,发现果然有一个 CONFIG_DRM_ROCKCHIP 控制这个目录是否参与编译。

所有需要确认 CONFIG_DRM_ROCKCHIP 是否是 y。经检查它是 m,那就强制 y 一下。

其他的驱动配置过程类似,反正各种排查踩坑就对了。

也可以使用 menuconfig 来搜索配置,比如已经知道需要 CONFIG_ROCKCHIP_DW_MIPI_DSI,在 menuconfig 中搜索这个配置可以快速查看到他的依赖和对于的配置情况。

屏幕参数修改

经过长时间的摸爬滚打,屏幕终于是亮了。但是却出现了偏移的情况?回想起之前做项目的时候 LVDS 有个屏参的概念,一查果然 DSI 也有。

通过网上的文章学习了一下屏幕参数的概念以及计算方法,再从淘宝店小二那得到的信息,发现是驱动内的默认数据和屏幕没有对其。

老板给的信息虽然少,但是很有用,记录一下:

#define PIXEL_CLOCK ((2000000000 / 3) / 24)
#define VREFRESH    60
#define VTOTAL      (480 + 7 + 2 + 21)
#define HACT        800
#define HSW         2
#define HBP         46
#define HFP         ((PIXEL_CLOCK / (VTOTAL * VREFRESH))) - (HACT + HSW + HBP)
  1. 第一行频率和驱动里面的不一样,驱动里面得再除以 1000
  2. 让我恍然大悟的是最后一条 HFP 的计算,实际画面异常的原因也就是自带驱动的 HFP 是错误的;

网上的文章见最后,总之试了几次之后就正常显示了:

diff --git a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
index 4618c892cdd6..56e52206c9a8 100644
--- a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
+++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
@@ -203,7 +203,7 @@ static const struct drm_display_mode rpi_touchscreen_modes[] = {
                .hdisplay = 800,
                .hsync_start = 800 + 1,
                .hsync_end = 800 + 1 + 2,
-               .htotal = 800 + 1 + 2 + 46,
+               .htotal = 800 + 1 + 2 + 1,
                .vdisplay = 480,
                .vsync_start = 480 + 7,
                .vsync_end = 480 + 7 + 2,

双屏异显

刚开始弄的使用我以为 RK3566 不支持双路视频输出,调试的时候一直是吧 HDMI 的设备树禁用的。

后面发现 RK3566 是支持 MIPIHDMI 同时输出的,并且是双屏异显。

有个前提,就是两路信号得启用不同的 vp 端口。

触摸屏驱动

这块屏幕用的触摸芯片是 ft5426,内核中默认倒是有这个芯片的驱动,但是需要配置中断引脚,板子和屏幕都没有预留这个 IO 引脚,所有用不了。

那就只能将香橙派内核里面的驱动程序移植了过来。

移植倒是没多大难度,拷贝 drivers/input/touchscreen/raspits_ft5426.hdrivers/input/touchscreen/raspits_ft5426.c 两个源文件,同时把 drivers/input/touchscreen/Kconfig 文件中相关的部分也拷过来便于能够使用 menuconfig 进行配置,最后改一下 drivers/input/touchscreen/Makefile 文件,在里面增加 raspits_ft5426.c 作为编译输入。

需要注意的是 5.10 的驱动到 6.6 里面时,驱动的 proberemove 两个函数需要调整一下签名:

diff --git a/drivers/input/touchscreen/raspits_ft5426.c b/drivers/input/touchscreen/raspits_ft5426.c
index fa2edcb2995b..0899a7052454 100644
--- a/drivers/input/touchscreen/raspits_ft5426.c
+++ b/drivers/input/touchscreen/raspits_ft5426.c
@@ -217,8 +217,7 @@ static void raspits_ft5426_work(struct work_struct *work)
        }
 }

-static int raspits_ft5426_probe(struct i2c_client *client,
-                       const struct i2c_device_id *id)
+static int raspits_ft5426_probe(struct i2c_client *client)
 {
        struct raspits_ft5426_data *ts_data;
        struct input_dev *input_dev;
@@ -276,7 +275,7 @@ static int raspits_ft5426_probe(struct i2c_client *client,
        return ret;
 }

-static int raspits_ft5426_remove(struct i2c_client *client)
+static void raspits_ft5426_remove(struct i2c_client *client)
 {
        struct raspits_ft5426_data *ts_data = i2c_get_clientdata(client);

@@ -286,7 +285,6 @@ static int raspits_ft5426_remove(struct i2c_client *client)
                input_free_device(ts_data->input_dev);
        }
        kfree(ts_data);
-       return 0;
 }

 static const struct i2c_device_id raspits_ft5426_id[] = {

打完收工

走到这一步,这块屏就是完全驱动起来了,重新编译内核体验一下吧。

刚开始触摸屏不能用我以为是 Debian 系统不支持。还好没有放弃继续研究驱动搞定了。

下一个目标就是 OrangePi 3B 板载的蓝牙和无线网卡了~~~下篇博客再见!

针对 OrangePi 3B 的改动记录

https://github.com/lx0758/kernel/commits/orangepi-3b/

调试

搞了这么久,发现验证内核最快的方式还是 U-Boot 通过 NFS 直接加载来的快。

快速应用修改

为了快速验证修改,我设置了 U-Boot 从远程通过 NFS 加载 Kerneldtb,每次重启时自动应用。

setenv ipaddr "192.168.8.21"
setenv load_kernel "nfs 0x4000000 192.168.8.13:/home/liux/projects/kernel/out/arch/arm64/boot/Image.gz"
setenv load_initramfs "load mmc 0:1 0x2000000 /boot/initramfs.img"
setenv load_dtb "nfs 0x1000000 192.168.8.13:/home/liux/projects/kernel/out/arch/arm64/boot/dts/rockchip/rk3566-orangepi-3b-custom.dtb"
setenv boot "booti 0x4000000 - 0x1000000"
setenv bootcmd "${load_kernel};${load_initramfs};${load_dtb};${boot};"
setenv bootargs "earlycon console=tty1 console=ttyS2,1500000n8 consoleblank=0 root=/dev/mmcblk0p1 rw rootfstype=ext4 rootwait"
saveenv
  1. 设置 u-bootNFS 加载 kerneldts
  2. 设置 kernelMMC 加载 ROOTFS
  3. 设置 kernel 不使用 initramfs,避免驱动重复加载;
  4. 增加 kernel 启动参数 console=tty1 使能屏幕输出;

参考