59 linux lcd驱动

本文详细介绍了Linux系统中LCD驱动的工作原理,包括Framebuffer机制、LCD驱动的编写流程和设备树配置。Framebuffer是应用程序操作LCD显存的机制,通过/dev/fbX设备进行交互。LCD驱动主要任务是初始化fb_info结构体并注册到内核。在设备树中,通过修改lcdif节点的属性来适配不同分辨率的LCD。此外,文章还涉及到了LCD背光控制和使用内核按键驱动唤醒LCD的设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值