一、总述
1、做过单片机的小伙伴都知道,想要点亮屏幕,首先是对屏幕做初始化,然后把显示数据传入屏幕,就可以控制屏幕显示了,当然这只是简单描述。
2、实际在控制屏幕的过程中,首先是对屏幕这个外部设备进行初始化,比如扫描方式、行列数目,前后门廊啊等等,当然大部分情况下是供应商直接提供的初始化代码。其次是要对单片机的外设接口进行初始化,外设接口是要和屏幕链接的,其中的扫描方式、行列数目等等是有相当一部分需要和屏幕上的参数一一对应。最后,是把显示数据写入到屏幕里面。
3、类比到Linux,无非也是这三个步骤,对屏幕做初始化,对外设接口初始化,然后是数据写入。只是和单片机上直接初始化不同的是,嵌入式linux的设备和驱动是分离的。
4、设备在设备树体现,驱动是附在linux源码中的。设备树体现了各个设备之间的拓补关系,添加和删除设备可以直接在设备树文件上进行修改。驱动虽然附在Linux源码中,但是需要通过配置菜单进行“加载”。linux开机的时候,会读取设备树,根据设备的相关标签,搜索支持的驱动,并初始化相关的设备。
5、所以第一步,修改设备树添加设备。
二、设备树修改
1、f1c200s有一般的屏幕驱动接口,既然有接口,首先是申请io,打开路径/usr/local/f1c200s/kernel/linux-5.7.1/arch/arm/boot/dts下的设备树文件suniv-f1c100s.dtsi,在pio节点下添加
lcd_rgb666_pins: lcd-rgb666-pins { //分号前是给节点起的别名,分号后是节点的名称。首先是申请RGB驱动的引脚
pins = "PD0", "PD1", "PD2", "PD3", "PD4",
"PD5", "PD6", "PD7", "PD8", "PD9",
"PD10", "PD11", "PD12", "PD13", "PD14",
"PD15", "PD16", "PD17", "PD18", "PD19",
"PD20", "PD21";
function = "lcd";
};
申请完io,接下来在soc节点下添加屏驱的节点
tcon0: lcd-controller@1c0c000 { //添加显示驱动的节点,即RGB驱动,@后面是设备的物理地址。
compatible = "allwinner,sun4i-a10-tcon"; //linux5.7.1内,已经没有f1c200s的相关支持,但是实测使用a10型号的驱动依然可用
reg = <0x01c0c000 0x1000>; //寄存器地址
interrupts = <29>; //中断号,是根据f1c200s手册查询得到的
clocks = <&ccu CLK_BUS_LCD>, //这里引用了外部宏定义,所以要添加对应的头文件,默认已经添加过了
<&ccu CLK_TCON>,
<&osc24M>; /* Still unknown */
clock-names = "ahb",
"tcon-ch0",
"tcon-ch1";
clock-output-names = "tcon-pixel-clock";
resets = <&ccu RST_BUS_LCD>; //指定复位单元
reset-names = "lcd"; //复位单元名称?
status = "disabled"; //先禁用设备
ports { //port是管道,或者说端口,设备间的数据通过管道传输。
#address-cells = <1>; //设置子节点的地址占用大小
#size-cells = <0>; //设置子节点寄存器尺寸描述占用大小
tcon0_in: port@0 { //port管道端口0是输入口
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
tcon0_in_be0: endpoint@0 { //port管道端口0的对端口为be0_out_tcon0
reg = <0>;
remote-endpoint = <&be0_out_tcon0>;
};
};
tcon0_out: port@1 { //port管道端口1是输出口
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
};
};
};
2、tcon就是屏驱的设备名称(节点名称)。它的数据来源可以是SDRAM,也可以是be显示后端处理。be显示后端处理是f1c200s的一个功能外设,是专门对图片进行叠加运算的单元。所以接下来是在soc节点下,添加be设备节点
be0: display-backend@1e60000 { //be0是显示后端处理,显示后端处理主要针对图片叠加
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>;
ports { //be0也是拥有两个端口
#address-cells = <1>;
#size-cells = <0>;
be0_in: port@0 { //be0管道端口0的对端是fe0_out_be0
#address-cells = <1>;
#size-cells = <0>;
reg = <0>;
be0_in_fe0: endpoint@0 {
reg = <0>;
remote-endpoint = <&fe0_out_be0>;
};
};
be0_out: port@1 { //be0管道端口1的对端是tcon0_in_be0
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
be0_out_tcon0: endpoint@0 {
reg = <0>;
remote-endpoint = <&tcon0_in_be0>;
};
};
};
};
3、be的数据来源同样可以是SDRAM,但也可以是fe显示前端处理,fe显示前端处理是专门对图片进行缩放的mcu外设。于是还要在soc节点下添加fe设备节点。
fe0: display-frontend@1e00000 { //fe0是显示前端处理,显示前端主要针对图片的缩放。
compatible = "allwinner,sun4i-a10-display-frontend"; //这里也是借用a10的驱动
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 { //fe0管道端口1是输出口,其对端口为be0_in_fe0
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
fe0_out_be0: endpoint@0 {
reg = <0>;
remote-endpoint = <&be0_in_fe0>;
};
};
};
};
4、当然不能忘记配置显示引擎总开关,在根节点下添加de(显示引擎)设备。
de: display-engine {
compatible = "allwinner,sun4i-a10-display-engine";
allwinner,pipelines = <&fe0>;
status = "disabled";
};
因为在设备树里使用了a10相关的宏定义,所以还不能忘记在文件开头的位置添加相关头文件
#include <dt-bindings/dma/sun4i-a10.h>
保存退出,文件suniv-f1c100s.dtsi就修改完了。
5、总结上述并忽略SDRAM的影响,那么数据在屏驱上的流向是,SDRAM->fe->be->tcon。如果再忽略tcon的物理层面上操作液晶的意义,并且把显示屏抽象为一个面板设备panel,那么数据在linux主机上的流向就变成了SDRAM->fe->be->tcon->panel ,这就是几个设备间管道的链接关系。
6、panel的设备节点需要在文件usr/local/f1c200s/kernel/linux-5.7.1/arch/arm/boot/dts/suniv-f1c100s-licheepi-nano.dts内添加。打开文件并在根节点下添加
panel: panel { //面板设备节点
compatible = "ampire,am-480272h3tmqw-t01h", "simple-panel"; //这里是套用了现成的驱动,驱动型号需要根据具体的显示屏进行修改
#address-cells = <1>;
#size-cells = <0>;
enable-gpios = <&pio 4 6 GPIO_ACTIVE_HIGH>; //指定背光脚位,引用了函数,所以需要添加宏定义
port@0 {
reg = <0>;
#address-cells = <1>;
#size-cells = <0>;
panel_input: endpoint@0 { //panel作为驱动tft的设备,数据来源只有一处,就是tcon的输出,也是通过管道获取
reg = <0>;
remote-endpoint = <&tcon0_out_lcd>;
};
};
};
7、然后在根节点之外,也就是文件末尾添加几个设备的状态变更
&de { //启动显示处理功能
status = "okay";
};
&tcon0 { //启动tcon
pinctrl-names = "default";
pinctrl-0 = <&lcd_rgb666_pins>;
status = "okay";
};
&tcon0_out { //接通管道
tcon0_out_lcd: endpoint@0 {
reg = <0>;
remote-endpoint = <&panel_input>;
};
};
8、最后在文件开头添加头文件。保存并退出。就完成了所有的设备树编辑。
#include <dt-bindings/gpio/gpio.h>
三、配置菜单
1、配置好设备树,就需要在配置菜单处,把panel的驱动打开,也就是屏幕设备的相关驱动。
首先执行make menuconfig进入配置菜单,依次选择Device Drivers > Graphics support > Display Panels,然后把光标移动到support for simple panels,点击空格键直到<>中出现*,也就是打开simple panels功能。
2、因为博主的液晶是裸屏面板,所以不需要对屏幕初始化,于是就选择support for simple panels,小伙伴们选择的时候根据自己的设备进行选择。并且在2.6节,对panel设备节点进行配置的时候,compatible项目也要根据对应的设备进行修改,甚至需要修改对应的驱动文件。
3、做完这些,驱动层已经支持了显示,但是应用层还没有调用,所以可以临时打开logo进行验证。
在make menuconfig中,依次选择Device Drivers > Graphics support,光标移动到最下面Bootup logo的位置,按空格选中即可打开logo。
3、最后,保存退出,执行make,然后把生成的zImage和suniv-f1c100s-licheepi-nano.dtb复制到u盘的boot分区,上电就可以看到屏幕左上角的小企鹅啦。
设备的物理地址如何得知?
设备地址,可以通过数据手册获得