LCD 是很常用的一个外设,在 Linux 下 LCD 的使用更加广泛,在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。
一、Framebuffer
1、总结
是一种机制,应用程序操作驱动里面的lcd显存的机制,应用程序通过操作显存在lcd上显示图片信息。
通过framebuffer机制将底层的lcd抽象为 /dev/fbX,应用程序可以通过操作 /dev/fbX 来操作屏幕。
fb_info 结构体就是framebuffer在内核中的表现形式。
屏幕驱动重点就是初始化结构体fb_ino的各个成员。
初始化完成以后通过 int register_framebuffer(struct fb_info *fb_info)
向内核注册刚刚初始化好的 fb_info。
卸载驱动的时候会调用 unregister_framebuffer
来卸载前面注册的 fb_info 结构体。
2、Framebuffer 设备
裸机 LCD 驱动编写流程如下:
1、初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)、 hspw、hbp、 hfp、 vspw、 vbp 和 vfp 等信息。
2、初始化 LCD 像素时钟。
3、设置 RGBLCD 显存。
4、应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。
在 Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。
在裸机中我们可以随意的分配显存;
但是在 Linux 系统中内存的管理很严格,显存是需要申请的,不能任意使用。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。(通过 mmap)
为了解决上述问题, Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb,因此大家在以后的 Linux 学习中见到“Framebuffer”或者“fb”的话第一反应应该想到 RGBLCD 或者显示设备。
fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备,当我们编写好 LCD 驱动以后会生成一个名为 /dev/fbX(X=0~n)
的设备,应用程序通过访问 /dev/fbX
这个设备就可以访问 LCD。NXP 官方的 Linux 内核默认已经开启了 LCD 驱动,因此我们是可以看到 /dev/fb0
这样一个设备,如下:
/dev/fb0 就是 LCD 对应的设备文件, /dev/fb0 是个字符设备,因此肯定有 file_operations 操作集, fb 的 file_operations 操作集定义在 drivers/video/fbdev/core/fbmem.c 文件中,如下所示:
1495 static const struct file_operations fb_fops = {
1496 .owner = THIS_MODULE,
1497 .read = fb_read,
1498 .write = fb_write,
1499 .unlocked_ioctl = fb_ioctl,
1500 #ifdef CONFIG_COMPAT
1501 .compat_ioctl = fb_compat_ioctl,
1502 #endif
1503 .mmap = fb_mmap,
1504 .open = fb_open,
1505 .release = fb_release,
1506 #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
1507 .get_unmapped_area = get_fb_unmapped_area,
1508 #endif
1509 #ifdef CONFIG_FB_DEFERRED_IO
1510 .fsync = fb_deferred_io_fsync,
1511 #endif
1512 .llseek = default_llseek,
1513 };
关于 fb 的详细处理过程就不去深究了,本章我们的重点是驱动起来 ALPHA 开发板上的 LCD。
3、 lcd 驱动简析
1 总结
驱动文件为 mxsfb.c
采用 platform 驱动框架,驱动设备匹配以后,pdev->probe 函数会执行。
结构体 mxsfb_info,是恩智浦自定义的,内嵌了一个 struct fb_info 结构体。二者关联代码如下:
host->fb_info = fb_info;
fb_info->par = host;
host->base
就是内存映射后的 LCDIF 外设基地址。
mxfbs_probe
会调用 mxsfb_init_fbinfo
来初始化结构体 fb_info。
结构体 fb_info 中有个操作函数集: fb_info->fbops
pdev->probe:msxb_probe函数
重点工作:
1、初始化 结构体fb_info,并向内核注册;
2、初始化 LCDIF 控制器;
mxsfb_init_fbinfo_dt
会从设备树读取相关属性信息。(dt就表示device tree)
2、lcd 驱动简析
LCD 裸机例程主要分两部分:
①、获取 LCD 的屏幕参数。
②、根据屏幕参数信息来初始化 eLCDIF 接口控制器。
不同分辨率的 LCD 屏幕其 eLCDIF 控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们本章实验的主要工作就是修改设备树, NXP 官方的设备树已经添加了 LCD 设备节点,只是此节点的 LCD 屏幕信息是针对 NXP 官方 EVK 开发板所使用的 4.3 寸 480*272 编写的,我们需要将其改为我们所使用的屏幕参数。
如下是设备树中 lcd设备节点,位于文件 imx6ull.dtsi :
/* 021c8000 是lcd控制器的地址 */
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};
上面 lcdif 节点信息是所有使用 I.MX6ULL 芯片的板子所共有的,并不是完整的 lcdif 节点信息。在 各个硬件平台的 dts 文件还会追加。
比如向 imx6ullalientek-emmc.dts 中的 lcdif 节点添加其他的属性信息。
可以看出 lcdif 节点 的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdi”,因此在 Linux 源码中搜索这两个字符串即可找到 I.MX6ULL 的 LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.c, mxsfb.c 就是 I.MX6ULL 的 LCD 驱动文件,在此文件中找到如下内容:
1362 static const struct of_device_id mxsfb_dt_ids[] = {
1363 { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
1364 { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
1365 { /* sentinel */ }
1366 };
......
1625 static struct platform_driver mxsfb_driver = {
1626 .probe = mxsfb_probe,
1627 .remove = mxsfb_remove,
1628 .shutdown = mxsfb_shutdown,
1629 .id_table = mxsfb_devtype,
1630 .driver = {
1631 .name = DRIVER_NAME,
1632 .of_match_table = mxsfb_dt_ids,
1633 .pm = &mxsfb_pm_ops,
1634 },
1635 };
1636
1637 module_platform_driver(mxsfb_driver);
从示例代码 59.1.2.2 可以看出,这是一个标准的 platform 驱动,当驱动和设备匹配以后 mxsfb_probe 函数就会执行。
在看 mxsfb_probe 函数之前我们先简单了解一下 Linux 下 Framebuffer 驱动的编写流程, Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构体, fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设备都必须有一个 fb_info。换言之就是, LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info 的过程。 fb_info 结构体定义在 include/linux/fb.h 文件里面。
mxsfb_probe 函数的主要工作内容为:
①、申请 fb_info。
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。
int register_framebuffer(struct fb_info *fb_info)
函数参数和返回值含义如下:
fb_info:需要上报的 fb_info。
返回值: 0,成功;负值,失败
三、驱动编写
其实只要修改设备树节点就好。
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat /* 24 根数据线配置项 */
&pinctrl_lcdif_ctrl>; /* 4根控制线的配置项 */
/*开发板不使用复位,屏蔽*/
/*&pinctrl_lcdif_reset>;*/
display = <&display0>;
status = "okay";
display0: display {
/* 一个像素点占多少比特,32表示 RGB888 格式 */
bits-per-pixel = <32>;
/* 采用 RGB888 格式时总线宽度为 24 */
bus-width = <24>;
display-timings { /*refer to mxsfb.txt,display-timing.txt*/
native-mode = <&timing0>;
timing0: timing0 {
/* 按照不同型号的屏幕来填写下面参数 */
clock-frequency = <31000000>;
hactive = <800>;/* 水平显示区域 */
vactive = <480>;/* 垂直显示区域 */
hfront-porch = <40>;
hback-porch = <88>;
hsync-len = <48>;
vback-porch = <32>;
vfront-porch = <13>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
};
1、屏幕引脚设置
将屏幕引脚电气属性改为 0x49。(要改lcd引脚的驱动能力弱一些,原来是 0x79。)
LCD_DATA7,LCD_DATA15,LCD_DATA27 也是 开发板控制启动的引脚。需要设置3个模拟开关,上电的时候开关断开,屏幕和开发板的线是断开的;若把这3个引脚的驱动设为最强,可能会影响网络功能(所以要降低驱动能力)。
2、bits-per-pixel
表示每个像素点的比特数。
根据绑定文档 mxsfb.txt,比特数为 16 的话表示 rgb565;为 32 的话表示 RGB888 / 666。
这里改为 32。
设备树参数示意图如下:
The parameters are defined as:
+----------+-------------------------------------+----------+-------+
| | ↑ | | |
| | |vback_porch | | |
| | ↓ | | |
+----------#######################################----------+-------+
| # ↑ # | |
| # | # | |
| hback # | # hfront | hsync |
| porch # | hactive # porch | len |
|<-------->#<-------+--------------------------->#<-------->|<----->|
| # | # | |
| # |vactive # | |
| # | # | |
| # ↓ # | |
+----------#######################################----------+-------+
| | ↑ | | |
| | |vfront_porch | | |
| | ↓ | | |
+----------+-------------------------------------+----------+-------+
| | ↑ | | |
| | |vsync_len | | |
| | ↓ | | |
+----------+-------------------------------------+----------+-------+
背光:高电平点亮
一般屏幕背光用pwm控制亮度(通过调节占空比),测试时直接将背光引脚拉高或者拉低
四、测试
- 1、使用内核自带的key按键驱动唤醒。
设备树中要将键码设置为回车键才能唤醒。
唤醒了也看不到企鹅logo了,但若是之前输出了信息在lcd上,唤醒后仍然可以看到。
设备树节点内容如下
/*
* 使用内核自在驱动 , 按照绑定文档的要求来添加一个设备节点
*/
gpio-keys /* 有要求 */
{
compatible = "gpio_keys" ; /* 有要求 */
pinctrl-names = "default";
#address-cells = <1>;
#size-cells = <0>;
pinctrl-0 = <&pinctrl_gpiokey>;
status = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
key0 {
label = "GPIO ENTER KEY";
linux,code = <KEY_ENTER>; /* 有要求,表示回车键 */
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
};
};
甚至可以将usb键盘接到开发板,按enter键也有同样的效果。
-
2、设置lcd为console
该uboot环境变量bootargs
原来:console=ttymxc0,115200
改为:console=tty1 console=ttymxc0,115200
这里我们设置了两遍 console,第一次设置 console=tty1,也就是设置 LCD 屏幕为控制台,第二遍又设置 console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个 console,一个是 LCD,一个是串口,大家重启开发板就会发现 LCD 和串口都会显示 Linux 启动 log 信息。但是此时我们还不能使用 LCD 作为终端进行交互,因为我们的设置还未完成。
修改/etc/inittab 文件:
打开开发板根文件系统中的/etc/inittab 文件,在里面加入下面这一行:
tty1::askfirst:-/bin/sh
重启后即可,在二者上打印log。
向 lcd 上输出 信息:echo 666666666666 66 > /dev/tty1
-
3、lcd背光调节
echo 0~7 > /sys/devices/platform/backlight/backlight/backlight/brightness