看了不少人写的LCD驱动解释,看之前很懵逼,看之后还是很懵逼。都是放一大堆内核代码,我当时就想吐槽,能写就写,写不明白放一大堆代码是啥意思。后来,实在没办法,只能去看内核代码,结果,真香,原来别人放一堆代码是有用的。就很邪门,翻翻内核源码,不用细究,看看大概的框架,突然就悟了,所以如果大家看完我这篇博客后还是不太明白,去翻翻源码吧!

前言

首先,LCD驱动,linux中本身就包含了,并不需要我们自己去编写,我们要做的只是根据自己的LCD改改参数即可。驱动具体是如何现实的呢?我认为主要分为两个部分,一个部分在“fbmem.c这个文件中,我们想想,如何用户需要自己去修改一些底层的配置,难道让他们去修改驱动么?显然是不能的,所以是不是得提供应用层的接口函数?这不还得是走一遍老套路,进行字符设备的注册。另一个部分就是真正的LCD驱动了 ,通过字符设备提供的接口函数可以修改真正的LCD驱动(NXP 官方编写的 Linux 下的 LCD 驱动mxsfb.c(当然这是不一定的,你也可以自己在写个驱动))”

在这里插入图片描述

什么是Framebuffer设备

Framebuffer 是用一个视频输出设备从包含完整的帧数据的一个内存缓冲区中来驱动一个视频显
示设备。也就是说 Framebuffer 是一块内存保存着一帧的图像,向这块内存写入数据就相当于向
屏幕中写入数据,如果使用 32 位的数据来表示一个像素点(使用 BBP 表示),假设屏幕的显示
频分辨率为 1920x1080,那么 Framebuffer 所需要的内存为 1920x1080x32/8=8,294,400 字节约等于
7.9M。
简单来说 Framebuffer 把屏幕上的每个点映射成一段线性内存空间,程序可以简单的改变这段内
存的值来改变屏幕上某一点的颜色。

Framebuffer 子系统为用户空间操作显示设备提供了统一的接口,屏蔽了底层硬件之间的差异,用
户只需要操作一块内存缓冲区即可把需要的图像显示到 LCD 设备上

第一步注册字符设备类 ,创建设备类

在内核启动时,LCD驱动的第一步是,调用fbmem_init()

fbmem_init(void)
{
	proc_create("fb", 0, NULL, &fb_proc_fops);

	if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
		printk("unable to get major %d for fb devs\n", FB_MAJOR);

	fb_class = class_create(THIS_MODULE, "graphics");
	if (IS_ERR(fb_class)) {
		printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
		fb_class = NULL;
	}
	return 0;
}

#ifdef MODULE
module_init(fbmem_init);

好好瞅瞅,是不是很眼熟,这不就是我们平时写字符设备的入口函数么 ,看到这个,fb_fops没,显然它是显示设备提供通用的文件操作接口,代码如下:

static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
};

fd_read 和 fd_write的代码就不放了,几乎字符设备驱动必备函数,感兴趣的可以自己去看看,我暂时没时间深究,不过这两个函数,一般都是进行应用层和内核层数据的交互嘛。重点来看看 fb_ioctl()函数:

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct fb_info *info = file_fb_info(file);

	if (!info)
		return -ENODEV;
	return do_fb_ioctl(info, cmd, arg);
}

来来来,家人们看这个 fb_info ,这个结构体就是真正LCD驱动的核心了,这个提醒一下,大家有点印象,fb_ioctl() 函数从全局数组 registered_fb 获取到 fb_info 结构体后,调用的是 do_fb_ioctl() 函数通过传入的 cmd 命令参数设置或者获取 fb_info 结构体的相关信息,do_fb_ioctl这个函数大家自己去翻翻。
所以到这里为止,都是方便在应用层来修改底层的一下参数,接下来就是LCD驱动的编写了

LCD硬件设备层分析

在我们编写具体的 LCD 驱动程序时,我们只需要关系硬件设备层,在硬件设备层根据具体的单
板和 LCD 屏幕参数编写对应驱动程序。每一个 LCD 驱动对应一个 fb_info 结构体(自己去fb.c 这个文件找),编写驱动程序时需要把它注册到核心层的 registered_fb 数组,以便内核管理。

我们简单看一下 NXP 官方编写的 Linux 下的 LCD 驱动,打开 imx6ull.dtsi,然后找到 lcdif

节点内容,如下所示:

 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";
 };
 static const struct of_device_id mxsfb_dt_ids[] = {
   { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
   { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
   { /* sentinel */ }
   };

static struct platform_driver mxsfb_driver = {
	.probe = mxsfb_probe,
	.remove = mxsfb_remove,
	.shutdown = mxsfb_shutdown,
	.id_table = mxsfb_devtype,
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = mxsfb_dt_ids,
		   .pm = &mxsfb_pm_ops,
	},
};

platform 通过 compatible 进行 ,drive和device的匹配 。这是一个标准的 platform 驱动,当驱动和设备匹配以后mxsfb_probe 函数就会执行,我们来看看mxsfb_probe函数

static int mxsfb_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id =
			of_match_device(mxsfb_dt_ids, &pdev->dev);
	struct resource *res;
	struct mxsfb_info *host;
	struct fb_info *fb_info;
	struct pinctrl *pinctrl;
	int irq = platform_get_irq(pdev, 0);
	int gpio, ret;

	if (of_id)
		pdev->id_entry = of_id->data;

	gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
	if (gpio == -EPROBE_DEFER)
		return -EPROBE_DEFER;

	if (gpio_is_valid(gpio)) {
		ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");
		if (ret) {
			dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);
			return ret;
		}
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "Cannot get memory IO resource\n");
		return -ENODEV;
	}

	host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
	if (!host) {
		dev_err(&pdev->dev, "Failed to allocate IO resource\n");
		return -ENOMEM;
	}
.............................
...........................
	fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
	if (!fb_info) {
		dev_err(&pdev->dev, "Failed to allocate fbdev\n");
		devm_kfree(&pdev->dev, host);
		return -ENOMEM;
	}
	host->fb_info = fb_info;
	fb_info->par = host;

	
	dev_info(&pdev->dev, "initialized\n");

	return 0;
	}

mxsfb_probe 函数的主要工作内容为:
①、申请 fb_info。(虽迟但到)
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。register_framebuffer
函数原型如下:

int register_framebuffer(struct fb_info *fb_info)

LCD驱动的流程就算走完了,希望对你们有帮助!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐