LCD 屏幕
在 V853 的手册中,可以看到 V853 芯片支持 1920 x 1080 @ 60fps 的 RGB 屏幕显示,同时也支持 1920 x 1200 @ 60fps 的 MIPI 屏幕显示。
本文将通过介绍 Tina Linux 的显示驱动、配置方法、移植适配 LCD 屏幕与测试和使用,介绍 Tina Linux 的显示系统。
LCD 的驱动
显示驱动可划分为三个层面:驱动层,框架层及底层。底层与图形硬件相接,主要负责将上层配置的功能参数转换成硬件所需要的参数,并配置到相应寄存器中。
显示框架层对底层进行抽象封装成一个个的功能模块。驱动层对外封装功能接口,通过内核向用户空间提供相应的设备结点及统一的接口。
在驱动层,分为三个驱动,分别是framebuffer
驱动,display
驱动,LCD&HDMI
驱动。framebuffer
驱动与 framebuffer core
对接,实现 linux
标准的framebuffer
接口。display
驱动是是整个显示驱动中的核心驱动模块,所有的接口都由 display
驱动来提供,包括 lcd
的接口。
在这里,我们只需要关心 LCD&HDMI
驱动,其他部分的驱动的开发可以参考《Linux_Display_开发指南.pdf》
LCD 配置
内核配置
运行 make kernel_menuconfig
进入内核配置,找到 Video support for sunxi
Device Drivers > Graphics support > Frame buffer Devices > Video support for sunxi
在显示驱动中最主要的是 <*> DISP Driver Support(sunxi-disp2)
,勾选后可以看到其他的选项。包括驱动支持,调试接口和 LCD 面板的选择。(LCD panels select
)
进入 LCD 面板选择可以看到许多已经适配了的显示屏可供选择使用。
设备树配置
与其他设备相同,屏幕驱动也使用了两份设备树。第一份配置了显示驱动的地址,时钟等等参数,位于kernel/linux-4.9/arch/arm/boot/dts/sun8iw21p1.dtsi
一般来说这里的参数都不需要修改,默认即可,另外一份配置位于 device/config/chips/v853/configs/vision/board.dts
包括了两个配置节点。第一个是 display
所使用的节点,配置了屏幕的特性与功能,另外一个是 lcd
所使用的节点,配置了 LCD 面板的驱动与参数。
这里的驱动配置非常复杂,具体代表的含义请参考《Linux_LCD_开发指南.pdf》,这里不做过多说明。
LCD 驱动
LCD 显示屏与其他驱动不一样,LCD 屏幕种类繁多,接口丰富,各式各样屏幕参数层出不穷。所以 LCD 屏幕驱动都是以单独的模块存在的,驱动文件位于:
kernel/linux-4.9/drivers/video/fbdev/sunxi/disp2/disp/lcd
从接口上来分,LCD 屏幕可以分为 RGB 屏幕,LVDS 屏幕,MIPI DSI 屏幕,eDP 屏幕、SPI 屏幕、IIC 屏幕。V853 芯片所支持的是 MIPI 与 RGB,所以在这里其他类型的屏幕不过多说明。这里简单介绍下两种屏幕。
(1)RGB 接口
RGB接口在全志平台又称HV接口(Horizontal同步和Vertical同步)。有些LCD屏支持高级的功能比如 gamma,像素格式的设置等,但是 RGB 协议本身不支持图像数据之外的传输,所以无法通过 RGB 管脚进行对 LCD 屏进行配置,所以拿到一款 RGB 接口屏,要么不需要初始化命令,要么这个屏会提供额外的管脚给 SoC 来进行配置,比如 SPI 和 I2C 等。RGB 屏幕有许多格式,不同的位宽,不同的时钟周期。下表是位宽与时钟周期的区别。
位宽 | 时钟周期数 | 颜色数量和格式 | 并行\串行 RGB |
---|---|---|---|
24 bits | 1 cycle | 16.7M colors, RGB888 | 并行 |
18 bits | 1 cycle | 262K colors, RGB666 | 并行 |
16 bits | 1 cycle | 65K colors, RGB565 | 并行 |
6 bits | 3 cycles | 262K colors, RGB666 | 串行 |
6 bits | 3 cycles | 65K colors, RGB565 | 串行 |
(2)MIPI DSI 接口
MIPI-DSI,即Mobile Industry Processor Interface Display Serial Interface,移动通信行业处理器接口显示串行接口。MIPI 有2种模式:
-
Command mode,类似MPU接口,需要IC内部有GRAM来缓冲。
-
Video mode。类似 RGB 接口,没有 GRAM,需要不停往 panel 刷数据。其中 video mode 又分为三个子 mode:
-
Non-burst mode with sync pulses
-
Non Burst mode with sync Events
-
Burst mode。简单理解就是有效数据比率更高,传输效率更高。
MIPI-DSI 的管脚是差分的,分为两种管脚:一种是时钟管脚,另外一种是数据管脚。数据管脚的数量是可变的,数量的单位是 lane
,lane
也指一对差分管脚,每一条 lane
实际包含两条线。一般来说 LCD 屏说明书里面的说的 lane
的数量是指数据管脚的数量不包括时钟管脚。比如说某 4 lane MIPI-DSI
屏就总共有 (4+1)*2
根脚。
适配驱动 LCD 屏幕
适配屏幕之前,我们可以先关闭 Uboot 的屏幕驱动,保留 Kernel 的驱动方便调试
先前往 Uboot 配置文件夹修改配置文件关闭屏幕驱动。
brandy/brandy-2.0/u-boot-2018/configs/sun8iw21p1_defconfig
把 CONFIG_DISP2_SUNXI=y
注释了
RGB 屏幕
下面简述如何移植一款 RGB 屏幕,型号【D500T7009VC】,此 RGB 屏幕无需初始化。使用40Pin 排线,RGB666 连接开发板(可以看见,开发板的 RGB LCD 有一根飞线,飞线后文有说明)
由于此 RGB 屏幕不需要初始化,所以直接使用 default_lcd
驱动即可,在 kernel 内 default_lcd
是默认启用的,所以只需要修改设备树即可。
(1)硬件连接
硬件连接如电路图与 LCD 屏幕手册所示:
可以看到,屏厂提供的手册中的 31 脚是 DISP 脚,默认 LOW 模式也就是关闭显示输出。而开发板侧的 LCD 屏幕连接器的 DISP 脚是悬空的。如果不飞线会导致屏幕关闭显示输出而不显示。不过也有的屏幕不需要 DISP 信号控制。根据屏幕而定。
(2)设备树配置
首先,我们在 pio
节点内增加 rgb18_pin
作为 LCD 屏幕的 Pin 绑定。
&pio {
rgb18_pins_a: rgb18@0 {
allwinner,pins = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,pname = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,function = "lcd";
allwinner,muxsel = <2>;
allwinner,drive = <3>;
allwinner,pull = <0>;
};
rgb18_pins_b: rgb18@1 {
allwinner,pins = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,pname = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,function = "rgb18_suspend";
allwinner,muxsel = <0xf>;
allwinner,drive = <1>;
allwinner,pull = <0>;
};
};
然后修改 lcd0
节点。这里将其分为几个部分,下文会对这几个部分着重说明。另外比较重要的配置项添加了注释。
&lcd0 {
base_config_start = <1>; # 开始设置
lcd_used = <1>; # 启用lcd
lcd_driver_name = "default_lcd"; # 使用 default_lcd 驱动
lcd_if = <0>; # 0:rgb 4:dsi
# Part 1
lcd_x = <800>; # 宽度
lcd_y = <480>; # 高度
lcd_width = <108>; # 屏幕物理宽度,单位 mm
lcd_height = <65>; # 屏幕物理高度,单位 mm
# Part 2
lcd_pwm_used = <1>; # 启用背光 PWM
lcd_pwm_ch = <9>; # 使用 PWM 通道 9
lcd_pwm_freq = <50000>; # PWM 频率,单位 Hz
lcd_pwm_pol = <0>; # 背光 PWM 的极性
lcd_pwm_max_limit = <255>; # 背光 PWM 的最大值(<=255)
# Part 3
lcd_dclk_freq = <24>; # 屏幕时钟,单位 MHz
lcd_ht = <816>; # hsync total cycle(pixel)
lcd_hbp = <12>; # hsync back porch(pixel) + hsync plus width(pixel);
lcd_hspw = <4>; # hsync plus width(pixel)
lcd_vt = <496>; # vsync total cycle(line)
lcd_vbp = <12>; # vsync back porch(line) + vysnc plus width(line)
lcd_vspw = <4>; # vsync plus width(pixel)
# Part 4
lcd_lvds_if = <0>;
lcd_lvds_colordepth = <1>;
lcd_lvds_mode = <0>;
lcd_frm = <0>; # 0:关闭; 1:启用rgb666抖动; 2:启用rgb656抖动
lcd_io_phase = <0x0000>;
lcd_gamma_en = <0>;
lcd_bright_curve_en = <0>;
lcd_cmap_en = <0>;
deu_mode = <0>;
lcdgamma4iep = <22>;
smart_color = <90>;
# Part 5
pinctrl-0 = <&rgb18_pins_a>;
pinctrl-1 = <&rgb18_pins_b>;
base_config_end = <1>; # 结束设置
};
Part 1
在这一部分中,我们设置了屏幕的像素宽度、高度,物理宽度、高度,这些数据都可以在屏厂提供的数据手册中查询到。有小数点的可以遵循四舍五入原则。
Part 2
在这一部分,我们配置了 LCD 屏幕的背光相关属性,使用 PWM 背光实现动态调整背光,可以在电路图中看到 LCD-PWM 使用的是 PWM9,所以配置 lcd_pwm_ch = <9>;
Part 3
这一部分是非常重要的一部分,能不能点亮 LCD 屏幕都靠这一部分。
我们打开屏幕手册,找到时序介绍这一部分。
(1)DCLK
首先是 DCLK,这里显示的 DCLK 值是 25,需要注意的是,如果直接设置 lcd_dclk_freq = <25>;
会导致实际的频率变为 48MHz。这是因为当 DCLK 的频率小于 48MHz 时,其频率是从 288MHz 的主时钟分频到实际频率的。而 25MHz 无法被完整分频导致其使用下一个频点 48M,从而导致屏幕被超频的情况。
这里简单说明下分频系数与得到的频率的计算方法:
在开机时,如果使用的是 RGB 屏幕,我们可以看到这样的输出:
disp 0, clk: pll(144000000),clk(144000000),dclk(24000000) dsi_rate(144000000)
clk real:pll(288000000),clk(288000000),dclk(48000000) dsi_rate(0)
实际芯片的 LCD 是通过 PLL 时钟分频得到的。所以在这里会计算分频的分频值。使用 dclk(24000000)
的 24MHz 乘上倍频系数 6
,得到 pll(144000000)
也就是144MHz,使用 pll(144000000)
去申请最近的时钟,这里申请到的是 real:pll(288000000)
也就是 288MHz。此时使用 (int)(real:pll / pll)
即可获得分频系数。由于不执行浮点运算,输出的结果会向下取整。例如这里的分频系数是 288MHz / 144MHz = 2
。使用分频系数乘上倍频系数即可得到 288MHz 的实际分频结果,这里则是 288MHz / (2 * 6) = 24MHz
刚刚好。
如果我们设置为25MHz,按照上面的计算可知分频系数为 1,分频结果则是 288MHz / (1 * 6) = 48MHz
由于 Display 框架不单单需要支持 RGB 显示屏,也需要支持 MIPI-DSI,LVDS 等等接口的显示屏,并且不同的频率区段所使用的倍频系数也是不同的,所以倍频系数是不可以随意修改的。
在这里,最好的方法就是选取一个最接近的 DCLK 值即可。
48MHz | 24MHz | 16MHz | 12MHz | 8MHz | 6MHz | 4MHz |
---|---|---|---|---|---|---|
而对于 48MHz 以上的屏幕,由于使用了不同的时钟源,其分频更加精准,无需按照此处方法调优。
(2)HT(Hsync Total)
查询手册可知,HT 的值是 816,这个值不需要修改。填入即可。
(3)HBP(Hsync Back Porch)
查询手册可知,HBP 值为 8,不过由于全志平台的 HBP 的含义是 HBP + HSPW,所以需要加上 HSPW 的值填入。这里便是 8 + 4 = 12
(4)HSPW(Hsync Plus Width)
查询手册可知,HSPW 的值是 4,这个值不需要修改。填入即可。
(5)VT(Vsync Total)
查询手册可知,HT 的值是 496,这个值不需要修改。填入即可。
(6)VBP(Vsync Back Porch)
查询手册可知,VBP 值为 8,不过由于全志平台的 VBP 的含义是 VBP + VSPW,所以需要加上 VSPW 的值填入。这里便是 8 + 4 = 12
(7)VSPW(Vsync Plus Width)
查询手册可知,VSPW 的值是 4,这个值不需要修改。填入即可。
可以看到手册还提供了 HFP(Hsync Front Porch)和 VFP(Vsync Front Porch)的值。这个值不需要填写,因为驱动可以通过计算得出实际的值。
Part 4
这一部分是 LCD 屏幕扩展功能的区域,包括 LCD 屏幕的各类功能,可以参照手册内容设置。
Part 5
这一部分是 Pin 的绑定,绑定上面创建的两个 RGB 节点。GPIO 与 Pin 绑定相关请查阅:【GPIO - V853】
(3)驱动勾选
不需要初始化的 RGB LCD 的驱动比较简单,勾选Video support for sunxi
即可
Device Drivers > Graphics support > Frame buffer Devices > Video support for sunxi
(4)测试屏幕
首先编译,打包。烧录系统。
先 ls
命令打印 /dev/
目录看看有没有出现 fb0
这个节点
可以看到这里有 `fb0`` 节点。那就进行下最简单的花屏测试。
cat /dev/urandom > /dev/fb0
这个测试一定会显示 cat: write error: No space left on device
,这样才是正常情况,因为 FB0 可以类比为屏幕的显存,显存是固定大小的可以被消耗完,当显存填满的时候就会报错 No space left on device
。如果执行这一行命令一直没出现这个报错则有可能底层显示驱动配置有问题。
另外也可以使用 colorbar
测试
echo 8 > /sys/class/disp/disp/attr/colorbar
也可以使用,获取屏幕的图层信息,帧率等等。
cat /sys/class/disp/disp/attr/sys
开机 LOGO
开机 LOGO 是由 UBOOT 所提供的支持,所以需要配置 UBOOT 的显示屏驱动。
在这之前,先前往 Uboot 检查是否开启了屏幕驱动。
brandy/brandy-2.0/u-boot-2018/configs/sun8iw21p1_defconfig
把 CONFIG_DISP2_SUNXI=y
取消注释
然后如同 Kernel 一样,修改 Uboot 的设备树即可。
device/config/chips/v853/configs/vision/uboot-board.dts
&pio {
rgb18_pins_a: rgb18@0 {
allwinner,pins = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,pname = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,function = "lcd";
allwinner,muxsel = <2>;
allwinner,drive = <3>;
allwinner,pull = <0>;
};
rgb18_pins_b: rgb18@1 {
allwinner,pins = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,pname = "PD0", "PD1", "PD2", "PD3", "PD4", "PD5", "PD6", "PD7", "PD8", "PD9", \
"PD10", "PD11", "PD12", "PD13", "PD14", "PD15", "PD16", "PD17", "PD18", "PD19", \
"PD20", "PD21";
allwinner,function = "rgb18_suspend";
allwinner,muxsel = <0xf>;
allwinner,drive = <1>;
allwinner,pull = <0>;
};
};
&lcd0 {
base_config_start = <1>;
lcd_used = <1>;
lcd_driver_name = "default_lcd";
lcd_if = <0>;
lcd_x = <800>;
lcd_y = <480>;
lcd_width = <108>;
lcd_height = <64>;
lcd_dclk_freq = <24>;
lcd_pwm_used = <1>;
lcd_pwm_ch = <9>;
lcd_pwm_freq = <50000>;
lcd_pwm_pol = <0>;
lcd_pwm_max_limit = <255>;
lcd_ht = <816>;
lcd_hbp = <12>;
lcd_hspw = <4>;
lcd_vt = <496>;
lcd_vbp = <12>;
lcd_vspw = <4>;
lcd_lvds_if = <0>;
lcd_lvds_colordepth = <1>;
lcd_lvds_mode = <0>;
lcd_frm = <0>;
lcd_io_phase = <0x0000>;
lcd_gamma_en = <0>;
lcd_bright_curve_en = <0>;
lcd_cmap_en = <0>;
deu_mode = <0>;
lcdgamma4iep = <22>;
smart_color = <90>;
pinctrl-0 = <&rgb18_pins_a>;
pinctrl-1 = <&rgb18_pins_b>;
base_config_end = <1>;
};
如果希望修改开机 LOGO,可以修改下面路径里的这个图片文件
openwrt/target/v853/v853-common/boot-resource/boot-resource/bootlogo.bmp
注意,图片不宜过大,太大的图片容易导致加载缓慢。而且图片需要 BMP 格式,24位深
打包烧写即可,替换 bootlogo 不需要编译
更换logo,摄像头无法显示
在按上述方法进行logo更换之后,可能会出现开机logo显示正常,而开机摄像头黑屏不显示的问题。
分析看到mpp部分是正常初始化的,合理怀疑是显示格式问题,在对比正常和不正常显示的信息后,发现其中不正常时代码显示fmt[8],目前差异就体现在这里。
下面是正常和不正常显示的cat /sys/class/disp/disp/attr/sys信息:
- logo正常/摄像头正常:
screen 0:
de_rate 300000000 hz, ref_fps:58
mgr0: 320x320 fmt[rgb] cs[0x204] range[full] eotf[0x4] bits[8bits] err[0] force_sync[0] unblank direct_show[false] iommu[1]
dmabuf: cache[0] cache max[0] umap skip[0] umap skip max[14]
lcd output backlight(150) fps:60.2 320x 320
err:0 skip:126 irq:911 vsync:0 vsync_skip:0
BUF enable ch[0] lyr[0] z[0] prem[N] a[pixel 128] fmt[ 77] fb[1920,1080; 960, 540; 0, 0] crop[ 0, 0,1920,1080] frame[ 0, 0, 320, 320] addr[49000000,491fe000, 0] flags[0x 0] trd[0,0]
depth[ 0]
BUF enable ch[1] lyr[0] z[16] prem[N] a[pixel 255] fmt[ 0] fb[ 320, 320; 320, 320; 320, 320] crop[ 0, 320, 320, 320] frame[ 0, 0, 320, 320] addr[48400000, 0, 0] flags[0x 0] trd[0,0]
2.logo正常/摄像头不正常:
screen 0:
de_rate 300000000 hz, ref_fps:58
mgr0: 320x320 fmt[rgb] cs[0x204] range[full] eotf[0x4] bits[8bits] err[0] force_sync[0] unblank direct_show[false] iommu[1]
dmabuf: cache[0] cache max[0] umap skip[0] umap skip max[22]
lcd output backlight(150) fps:60.2 320x 320
err:0 skip:134 irq:2555 vsync:0 vsync_skip:0
BUF enable ch[0] lyr[0] z[0] prem[N] a[pixel 128] fmt[ 77] fb[1920,1080; 960, 540; 0, 0] crop[ 0, 0,1920,1080] frame[ 0, 0, 320, 320] addr[49300000,494fe000, 0] flags[0x 0] trd[0,0]
depth[ 0]
BUF enable ch[1] lyr[0] z[16] prem[N] a[pixel 255] fmt[ 8] fb[ 320, 320; 320, 320; 320, 320] crop[ 0, 320, 320, 320] frame[ 0, 0, 320, 320] addr[48400000, 0, 0] flags[0x 0] trd[0,0]
对比得知图层的格式不对,正常的排除顺序为检查logo位数,图层是否配置正确,最后尝试关闭图层、设置透明空白尝试。
针对上述情况,可以将logo图片设置为32位并且设置透明层为白色,来让logo和摄像头均正常显示。