Framebuffer是用一个视频输出设备从包含完整的帧数据的一个内存缓冲区中来驱动一个视频显示设备。也就是说Framebuffer是一块内存保存着一帧的图像,向这块内存写入数据就相当于向屏幕中写入数据,如果使用32位的数据来表示一个像素点(使用BBP表示),假设屏幕的显示频分辨率为1920x1080, 那么Framebuffer所需要的内存为1920x1080x32/8=8,294,400字节约等于7.9M。
简单来说Framebuffer把屏幕上的每个点映射成一段线性内存空间, 程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。
7.1. Framebuffer子系统简介¶Framebuffer子系统为用户空间操作显示设备提供了统一的接口,屏蔽了底层硬件之间的差异,用户只需要操作一块内存缓冲区即可把需要的图像显示到LCD设备上。Framebuffer子系统主要分为两个部分,如下图所示:
核心层: 主要实现字符设备的创建,为不同的显示设备提供文件通用处理接口;同时创建graphics设备类,占据主设备号29。
硬件设备层: 主要提供显示设备的时序、显存、像素格式等硬件信息,实现显示设备的私有文件接口,并创建显示设备文件/dev/fbx(x=0~n)暴露给用户空间。硬件设备层的代码需要驱动开发人员根据具体的显示设备提供给内核。
7.1.1. 核心层分析¶7.1.1.1. 注册字符设备、创建设备类¶在内核启动时,Framebuffer子系统的 fbmem_init() 函数会被系统自动调用,该函数的具体实现如下:
fbmem_init (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132static int __initfbmem_init(void){ int ret; if (!proc_create_seq("fb", 0, NULL, &proc_fb_seq_ops))return -ENOMEM; ret = register_chrdev(FB_MAJOR, "fb", &fb_fops); if (ret) {printk("unable to get major %d for fb devs\n", FB_MAJOR);goto err_chrdev; } fb_class = class_create(THIS_MODULE, "graphics"); if (IS_ERR(fb_class)) {ret = PTR_ERR(fb_class);pr_warn("Unable to create fb class; errno = %d\n", ret);fb_class = NULL;goto err_class; } fb_console_init(); return 0;err_class: unregister_chrdev(FB_MAJOR, "fb");err_chrdev: remove_proc_entry("fb", NULL); return ret;}第9行:register_chrdev注册一个设备号为FB_MAJOR的字符设备,与该设备号绑定的file_operations结构体为fb_fops,fb_fops为显示设备提供通用的文件操作接口。
FB_MAJOR 宏定义
FB_MAJOR 宏定义 (位于 内核源码/include/uapi/linux/major.h)¶1#define FB_MAJOR29fb_fops文件操作接口
fb_fops 文件接口 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 9101112131415161718192021static 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,#if defined(HAVE_ARCH_FB_UNMAPPED_AREA) || \ (defined(CONFIG_FB_PROVIDE_GET_FB_UNMAPPED_AREA) && \!defined(CONFIG_MMU)) .get_unmapped_area = get_fb_unmapped_area,#endif#ifdef CONFIG_FB_DEFERRED_IO .fsync = fb_deferred_io_fsync,#endif .llseek = default_llseek,};第15行:调用class_create()函数在/sys/class/目录下创建graphics设备类。
7.1.1.2. fb_fops 文件通用处理接口¶7.1.1.2.1. fb_open() 函数¶fb_open() 函数 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 9101112131415161718192021222324static int fb_open(struct inode *inode, struct file *file){ int fbidx = iminor(inode); struct fb_info *info; int res = 0; info = get_fb_info(fbidx); ... file->private_data = info; if (info->fbops->fb_open) {res = info->fbops->fb_open(info,1);if (res) module_put(info->fbops->owner); }#ifdef CONFIG_FB_DEFERRED_IO if (info->fbdefio)fb_deferred_io_open(info, inode, file);#endifout: mutex_unlock(&info->lock); if (res)put_fb_info(info); return res;}第3行:调用iminor函数获取inode设备文件对应的次设备号;
第7行:调用get_fb_info函数根据设备文件的次设备号,从registered_fb全局数组找到对应的fb_info结构体;
第10-11行:判断LCD设备的fb_info结构体的fbops成员是否有提供fb_open函数,如果有提供就执行该函数;
7.1.1.2.2. fb_read() 函数¶fb_read() 函数 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364static ssize_tfb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos){ unsigned long p = *ppos; struct fb_info *info = file_fb_info(file); u8 *buffer, *dst; u8 __iomem *src; int c, cnt = 0, err = 0; unsigned long total_size; if (!info || ! info->screen_base) return -ENODEV; if (info->state != FBINFO_STATE_RUNNING) return -EPERM; if (info->fbops->fb_read) return info->fbops->fb_read(info, buf, count, ppos); total_size = info->screen_size; if (total_size == 0) total_size = info->fix.smem_len; if (p >= total_size) return 0; if (count >= total_size) count = total_size; if (count + p > total_size) count = total_size - p; buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,GFP_KERNEL); if (!buffer) return -ENOMEM; src = (u8 __iomem *) (info->screen_base + p); if (info->fbops->fb_sync) info->fbops->fb_sync(info); while (count) { c = (count > PAGE_SIZE) ? PAGE_SIZE : count; dst = buffer; fb_memcpy_fromfb(dst, src, c); dst += c; src += c; if (copy_to_user(buf, buffer, c)) { err = -EFAULT; break; } *ppos += c; buf += c; cnt += c; count -= c; } kfree(buffer); return (err) ? err : cnt;}第5行:file_fb_info()函数的作用也是根据文件的次设备号,从registered_fb全局数组找到对应的fb_info结构体;
第17-18行:判断LCD设备的fb_info结构体是否有提供私有的fb_read文件操作接口,如果有提供直接调用LCD的私有fb_read函数并返回;若没有提供,则继续往下执行通用的fb_read函数;
第34~35行:分配一块最大为PAGE_SIZE(值为4096)的buffer;
第39行:把源地址指向需要从framebuffer读取数据的起始位置;
第44~59行:使用copy_to_user()函数把从framebuffer读取到的数据拷贝到用户空间。
7.1.1.2.3. fb_write() 函数¶fb_write()函数和fb_read()函数实现的几乎是相同的,不相同的地方是fb_read()函数使用copy_to_user()把内核空间的数据拷贝到用户空间,而fb_write()函数是使用copy_from_user()把用户空间的数据拷贝到内核空间。大家可自行阅读fbmem.c的代码对比这两个函数。
7.1.1.2.4. fb_ioctl() 函数¶fb_ioctl() 函数 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶12345678static 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_ioctl()函数从全局数组registered_fb获取到fb_info结构体后,调用的是do_fb_ioctl()函数,通过传入的cmd命令参数设置或者获取fb_info结构体的相关信息。do_fb_ioctl()函数的部分代码如下:
do_fb_ioctl() 函数 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg){ ... switch (cmd) { case FBIOGET_VSCREENINFO: if (!lock_fb_info(info)) return -ENODEV; var = info->var; unlock_fb_info(info); ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0; break; case FBIOPUT_VSCREENINFO: if (copy_from_user(&var, argp, sizeof(var))) return -EFAULT; console_lock(); if (!lock_fb_info(info)) { console_unlock(); return -ENODEV; } info->flags |= FBINFO_MISC_USEREVENT; ret = fb_set_var(info, &var); info->flags &= ~FBINFO_MISC_USEREVENT; unlock_fb_info(info); console_unlock(); if (!ret && copy_to_user(argp, &var, sizeof(var))) ret = -EFAULT; break; case FBIOGET_FSCREENINFO: if (!lock_fb_info(info)) return -ENODEV; fix = info->fix; unlock_fb_info(info); ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0; break; ...}以上是Framebuffer应用编程时用到的几个常用的命令:
FBIOGET_VSCREENINFO:获取可变参数fb_var_screeninfo结构体;
FBIOPUT_VSCREENINFO:设置可变参数fb_var_screeninfo结构体;
FBIOGET_FSCREENINFO:获取固定参数fb_fix_screeninfo结构体。
7.1.2. 硬件设备层分析¶在我们编写具体的LCD驱动程序时,我们只需要关系硬件设备层,在硬件设备层根据具体的单板和LCD屏幕参数编写对应驱动程序。每一个LCD驱动对应一个fb_info结构体,编写驱动程序时需要把它注册到核心层的registered_fb数组,以便内核管理。
7.1.2.1. fb_info 结构体¶fb_info 结构体 (位于 内核源码/include/linux/fb.h)¶ 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768struct fb_info { atomic_t count; int node; int flags; /** -1 by default, set to a FB_ROTATE_* value by the driver, if it knows* a lcd is not mounted upright and fbcon should rotate to compensate.*/ int fbcon_rotate_hint; struct mutex lock; /* Lock for open/release/ioctl funcs */ struct mutex mm_lock;/* Lock for fb_mmap and smem_* fields */ struct fb_var_screeninfo var;/* Current var */ struct fb_fix_screeninfo fix;/* Current fix */ struct fb_monspecs monspecs;/* Current Monitor specs */ struct work_struct queue;/* Framebuffer event queue */ struct fb_pixmap pixmap;/* Image hardware mapper */ struct fb_pixmap sprite;/* Cursor hardware mapper */ struct fb_cmap cmap;/* Current cmap */ struct list_head modelist; /* mode list */ struct fb_videomode *mode; /* current mode */#ifdef CONFIG_FB_BACKLIGHT /* assigned backlight device */ /* set before framebuffer registration, remove after unregister */ struct backlight_device *bl_dev; /* Backlight level curve */ struct mutex bl_curve_mutex; u8 bl_curve[FB_BACKLIGHT_LEVELS];#endif#ifdef CONFIG_FB_DEFERRED_IO struct delayed_work deferred_work; struct fb_deferred_io *fbdefio;#endif struct fb_ops *fbops; struct device *device; /* This is the parent */ struct device *dev; /* This is this fb device */ int class_flag;/* private sysfs flags */#ifdef CONFIG_FB_TILEBLITTING struct fb_tile_ops *tileops;/* Tile Blitting */#endif union { char __iomem *screen_base; /* Virtual address */ char *screen_buffer; }; unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ void *pseudo_palette;/* Fake palette of 16 colors */#define FBINFO_STATE_RUNNING 0#define FBINFO_STATE_SUSPENDED1 u32 state; /* Hardware state i.e suspend */ void *fbcon_par;/* fbcon use-only private area */ /* From here on everything is device dependent */ void *par; /* we need the PCI or similar aperture base/size not smem_start/size as smem_start may just be an object allocated inside the aperture so may not actually overlap */ struct apertures_struct { unsigned int count; struct aperture { resource_size_t base; resource_size_t size; } ranges[0]; } *apertures; bool skip_vt_switch; /* no VT switch on suspend/resume required */};var: 用于提供显示设备的可变参数,包括显示设备的分辨率、显示时序和像素格式等硬件信息;
fix: 用于提供显示设备的固定参数,包括显示设备的行长度、显存大小、显存物理基地址等信息;
screen_base:用于提供显示设备的显存虚拟的基地址;
screen_szie:保存LCD显存的大小;
fbops:显示设备的私有文件操作接口。
7.1.2.2. register_framebuffer() 函数¶register_framebuffer() 函数 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 910int register_framebuffer(struct fb_info *fb_info){ int ret; mutex_lock(®istration_lock); ret = do_register_framebuffer(fb_info); mutex_unlock(®istration_lock); return ret;}从以上代码可知,真正注册fb_info结构体的函数是do_register_framebuffer(),该函数代码如下:
do_register_framebuffer() 函数 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶ 1 2 3 4 5 6 7 8 910111213141516static int do_register_framebuffer(struct fb_info *fb_info){ ... for (i = 0 ; i node = i; ... fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL, "fb%d", i); ... registered_fb[i] = fb_info; ...}第4-6行:找出registered_fb数组中空闲的元素的下标;
第11-12行:以registered_fb数组空闲元素的下标作为LCD设备的次设备号,在/dev/目录下创建fbi(i为registered_fb数组空闲元素的下标)设备文件;
第14行:把LCD硬件设备对应的fb_info结构体存入registered_fb数组。registered_fb数组如下:
registered_fb 数组 (位于 内核源码/drivers/video/fbdev/core/fbmem.c)¶1struct fb_info *registered_fb[FB_MAX]FB_MAX 宏 (位于 内核源码/include/uapi/linux/fb.h)¶1#define FB_MAX32 /* sufficient for now */7.2. Framebuffer子系统实验¶本章配套源码和设备树插件位于 ~/linux_driver/fb_sub_system 目录下。
LCD的硬件原理可参考 eLCDIF—液晶显示 章节。
7.2.1. 设备树插件实现¶LCD配置参数主要包括LCD引脚相关配置和LCD显示参数配置。其中背光控制引脚是独立出来的,修改背光引脚的同时也要修改对应的PWM设备信息。
7.2.1.1. LCD引脚配置¶这里我们使用pinctrl子系统配置LCD接口和背光控制引脚,对应的设备树引脚配置如下:
lcd引脚配置 (位于 linux_driver/fb_sub_system/imx-fire-lcd5-no-ts-overlay.dts)¶ 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243pinctrl_lcdif_ctrl: lcdifctrlgrp {fsl,pins = ;};pinctrl_lcdif_dat: lcdifdatgrp {fsl,pins = ;};pinctrl_pwm1: pwm1grp {fsl,pins = ;};如果修改了LCD显示和背光的引脚,需要修改以上代码的引脚。
7.2.1.2. LCD背光设置¶lcd背光pwm设置 (位于 linux_driver/fb_sub_system/imx-fire-lcd5-no-ts-overlay.dts)¶1234567backlight {compatible = "pwm-backlight";pwms = ;brightness-levels = ;default-brightness-level = ;status = "okay";};背光引脚被复用为PWM1的输出,如果修改了背光引脚也要在这里修改使用的pwm。
7.2.1.3. LCD属性设置¶当我们需用到不同的LCD时,由于不同的LCD的配置参数不同,例如分辨率、时钟、无效行数等。以下是本实验LCD的配置参数:
lcd配置参数 (位于 linux_driver/fb_sub_system/imx-fire-lcd5-no-ts-overlay.dts)¶ 1 2 3 4 5 6 7 8 91011121314151617181920/*-------第一组---------*/clock-frequency = ;hactive = ;vactive = ;/*-------第二组---------*/hfront-porch = ;hback-porch = ;vback-porch = ;vfront-porch = ;/*-------第三组---------*/hsync-len = ;vsync-len = ;/*-------第四组---------*/hsync-active = ;vsync-active = ;de-active = ;pixelclk-active = ;配置参数可分为四组:
第一组是设置分辨率和时钟。
第二组设置“可视区域”,它们的缩写就是我们常说的HFP、hbp、vbp、vfp、行同步信号到第一个像素点的延时时间,单位(像素),一行的最后一个像素点到下一个行同步信号的延时时间(单位像素),帧同步信号到第一个有效行之间的时间,最后一行到下一个帧同步信号 之间的时间。
第三组,设置行同步信号和帧同步信号的脉宽。
第四组,设置行同步信号、帧同步信号、数据信号、像素时钟信号的极性。
以上内容要根据自己使用的显示屏说明文档配置。
重要
本实验的使用野火的5.0寸800x480RGB电容触摸屏,设备树插件完整代码位于~/linux_driver/fb_sub_system/imx-fire-lcd5-no-ts-overlay.dts。若想使用内核自带的LCD驱动(位于 内核源码/drivers/video/fbdev/mxsfb.c),请自行编译~/linux_driver/fb_sub_system/imx-fire-lcd5-no-touchscreen-overlay.dts设备树插件并拷贝到开发板测试。
7.2.2. 驱动程序实现¶编程思路:
分配fb_info结构体;
设置fb_info结构体;
向核心层注册fb_info结构体;
根据LCD的相关参数设置LCD控制器。
注: LCD接口相关的引脚配置,在驱动加载时pinctrl子系统会根据设备树自动把它配置为LCD相关的引脚功能,在我们的驱动程序里无需配置。其次,LCD背光部分这里我们使用内核自带的背光驱动程序(位于 内核源码/drivers/video/backlight/pwm_bl.c)。LCD驱动和背光驱动是两个独立的驱动模块,关于背光模块是如何在LCD模块显示图像时及时打开背光的问题,感兴趣的可以查阅“Linux内核事件通知链”相关的资料。
7.2.2.1. 驱动入口和出口函数实现¶本实验的驱动程序代码是基于平台设备驱动编写的,驱动入口和出口函数仅用于平台驱动的注册和注销,代码如下:
LCD 驱动入口和出口函数 (位于 linux_driver/fb_sub_system/fb_sub_system.c)¶ 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627 static struct of_device_idlcd_of_match[] = { {.compatible = "fire,lcd_drv",}, {}, }; static struct platform_driver lcd_driver = { .probe = lcd_driver_probe, .remove = lcd_driver_remove, .driver = { .name = "lcd_drv", .of_match_table = lcd_of_match, }, }; static int __init lcd_driver_init(void) { return platform_driver_register(&lcd_driver); } static void __exit lcd_driver_exit(void) { platform_driver_unregister(&lcd_driver); } module_init(lcd_driver_init); module_exit(lcd_driver_exit); MODULE_LICENSE("GPL");第1~4行:定义LCD设备树匹配表。
第6~13行:定义LCD平台驱动结构体。
第7~8行:在驱动加载注册平台驱动时会与设备树进行匹配,若匹配成功则会执行.probe函数;在驱动卸载注销平台驱动时.remove函数会被执行;我们可以在.probe函数实现一些初始化的工作,在.remove函数实现一些清理工作。
第11行:.of_match_table 用于和设备树节点匹配。
第15~18行:在驱动程序的入口函数注册平台驱动。
第20~23行:在驱动程序的出口函数注销平台驱动。
第25行:把lcd_driver_init修饰为驱动的入口函数。
第26行:把lcd_driver_exit修饰为驱动的出口函数。
7.2.2.2. .prob函数实现¶lcd_driver_probe() 函数 (位于 linux_driver/fb_sub_system/fb_sub_system.c)¶ 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687static int lcd_driver_probe(struct platform_device *pdev){ struct device_node *display_np; struct display_timings *timings = NULL; struct display_timing *dt = NULL; struct resource *res = NULL; unsigned int bits_per_pixel; unsigned int bus_width; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); elcdif = devm_ioremap_resource(&pdev->dev, res); display_np = of_parse_phandle(pdev->dev.of_node, "display", 0); timings = of_get_display_timings(display_np); dt = timings->timings[timings->native_mode]; of_property_read_u32(display_np, "bits-per-pixel", &bits_per_pixel); if (bits_per_pixel != 16) {printk(KERN_EMERG"not support %d bpp!\n", bits_per_pixel);return -1; } of_property_read_u32(display_np, "bus-width", &bus_width); clk_pix = devm_clk_get(&pdev->dev, "pix"); clk_axi = devm_clk_get(&pdev->dev, "axi"); clk_set_rate(clk_pix, dt->pixelclock.typ); clk_prepare_enable(clk_pix); clk_prepare_enable(clk_axi); /* 分配一个fb_info结构体 */ lcdfb_info = framebuffer_alloc(0, &pdev->dev); /* LCD屏幕参数设置 */ lcdfb_info->var.xres= dt->hactive.typ; lcdfb_info->var.yres= dt->vactive.typ; lcdfb_info->var.width = dt->hactive.typ; lcdfb_info->var.height = dt->vactive.typ; lcdfb_info->var.xres_virtual = dt->hactive.typ; lcdfb_info->var.yres_virtual = dt->vactive.typ; lcdfb_info->var.bits_per_pixel = bits_per_pixel;/* LCD信号时序设置 */ lcdfb_info->var.pixclock = dt->pixelclock.typ; lcdfb_info->var.left_margin = dt->hback_porch.typ; lcdfb_info->var.right_margin = dt->hfront_porch.typ; lcdfb_info->var.upper_margin = dt->vback_porch.typ; lcdfb_info->var.lower_margin = dt->vfront_porch.typ; lcdfb_info->var.vsync_len = dt->vsync_len.typ; lcdfb_info->var.hsync_len = dt->hsync_len.typ; /* LCD RGB格式设置, 这里使用的是RGB565 */ lcdfb_info->var.red.offset= 11; lcdfb_info->var.red.length= 5; lcdfb_info->var.green.offset = 5; lcdfb_info->var.green.length = 6; lcdfb_info->var.blue.offset = 0; lcdfb_info->var.blue.length = 5; /* 设置固定参数 */ strcpy(lcdfb_info->fix.id, "fire,lcd"); lcdfb_info->fix.type= FB_TYPE_PACKED_PIXELS; lcdfb_info->fix.visual = FB_VISUAL_TRUECOLOR; lcdfb_info->fix.line_length = dt->hactive.typ * bits_per_pixel / 8; lcdfb_info->fix.smem_len= dt->hactive.typ * dt->vactive.typ * bits_per_pixel / 8; /* 其他参数设置 */ lcdfb_info->screen_size = dt->hactive.typ * dt->vactive.typ * bits_per_pixel / 8; /* dma_alloc_writecombine:分配smem_len大小的内存,返回screen_base虚拟地址,对应的物理地址保存在smem_start */ lcdfb_info->screen_base = dma_alloc_writecombine(&pdev->dev, lcdfb_info->fix.smem_len, (dma_addr_t*)&lcdfb_info->fix.smem_start, GFP_KERNEL); lcdfb_info->pseudo_palette = pseudo_palette; lcdfb_info->fbops = &lcdfb_ops;/* elcdif控制器硬件初始化 */ imx6ull_elcdif_init(elcdif, lcdfb_info, dt, bus_width); imx6ull_elcdif_enable(elcdif); /* 注册fb_info结构体 */ register_framebuffer(lcdfb_info); printk(KERN_EMERG"match success!\n"); return 0;}第10~11行:获取节点的内存资源,即获取设备树节点的reg属性的信息,该属性包含了elcdif控制器的寄存器的基地址和寄存器空间的大小,并进行物理地址的映射,返回供我们我们操作寄存器的虚拟地址。
第13~15行:根据句柄的名称display获取对应的display0设备节点,然后进一步解析display0的子节点display-timings得到LCD屏的时序参数信息并保存到display_timing结构体变量dt里。
第17~24行:从设备树中获取lcd的bits-per-pixel参数和总线宽度bus-width,需要注意的是本驱动程序只支持16bpp像素格式。
第26~32行:从设备树获”pix”和”axi”时钟,并使能它。”pix”是像素时钟,需要根据具体的LCD屏参数设置。
第36行:分配一个fb_info结构体。
第38~76行:填充fb_info结构体的var、fix、screen_base、screen_size等硬件信息。
第79~80行:初始化elcdif控制器,并使能它。
第83行:向核心层注册fb_info结构体。
7.2.2.3. .remove函数实现¶lcd_driver_remove() 函数 (位于 linux_driver/fb_sub_system/fb_sub_system.c)¶123456789static int lcd_driver_remove(struct platform_device *pdev){ unregister_framebuffer(lcdfb_info); imx6ull_elcdif_disable(elcdif); framebuffer_release(lcdfb_info); printk(KERN_EMERG"module exit!\n"); return 0;}.remove 函数工作是注销、释放fb_info结构体,并且失能LCD控制器。
7.2.2.4. LCD控制器硬件操作¶LCD控制器的寄存器操作与裸机是一样的,详细的内容可以阅读裸机 eLCDIF—液晶显示 章节。代码如下:
LCD控制器硬件操作 (位于 linux_driver/fb_sub_system/fb_sub_system.c)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101102103104105106107108109110111112113114115static void imx6ull_elcdif_enable(struct imx6ull_elcdif *elcdif){ elcdif->CTRL |= (1var.bits_per_pixel);return -1; } if (bus_width == 16)data_bus_width = 0x0; else if (bus_width == 8)data_bus_width = 0x01; else if (bus_width == 18)data_bus_width = 0x02; else if (bus_width == 24)data_bus_width = 0x03; else {printk(KERN_EMERG"Don't support %d bit data bus mode\n", info->var.bits_per_pixel);return -1; } /* 设置RGB格式和数据宽度 */ elcdif->CTRL &= ~((0x03 vsync_len.typ); elcdif->VDCTRL4 |= (1 hactive.typ fix.smem_start; elcdif->NEXT_BUF = info->fix.smem_start; return 0;}以上代码是参照裸机章节编写的,详细的介绍请阅读 eLCDIF—液晶显示 章节。
7.2.3. 测试应用程序实现¶这里编写一个简单的应用程序用于测试驱动是否正常,主要实现的功能是分别向显示器刷红、绿、蓝三种颜色,最后在显示屏显示一副图片。
测试应用程序 (位于 linux_driver/fb_sub_system/test_app/test_app.c)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137#include #include #include #include #include #include "test_app.h"#include /*显示屏相关头文件*/#include #include extern unsigned int test_picture[];void lcd_show_pixel(unsigned char *fbmem, int line_length, int bpp,int x, int y, unsigned int color){ unsigned char *p8 = (unsigned char *)(fbmem + y * line_length + x * bpp / 8); unsigned short *p16 = (unsigned short *)p8; unsigned int*p32 = (unsigned int *)p8; unsigned int red, green, blue; switch(bpp) {case 8:{ *p8 = (unsigned char)(color & 0xf); break;}case 16:{ red= (color >> 16) & 0xff; green = (color >> 8) & 0xff; blue = (color >> 0) & 0xff; *p16 = (unsigned short)(((red >> 3) 2) 3)