本文用于描述 linux 下的 LCD Frambuffer 框架,本文的所有试验均使用 imx6ull 进行,内核版本 Linux4.8
1. 基本概念
1.1 像素(pixel)
像素是图像显示的基本单位。像素是 lcd 显示的最小单位,每一个像素可以显示不同的颜色,颜色的亮度等。像素的颜色由三个最基本的颜色红 、绿 、蓝 来表示,常见的编码方式有 RGB888/RGB565/RGB555
RGB888: (red: 8bit 、green: 8bit 、blue: 8bit )
RGB565: (red: 5bit 、green: 6bit 、blue: 5bit )
RGB555: (red: 5bit 、green: 5bit 、blue: 5bit )
1.2 显示接口
1.2.1 MPU(MIPI DBI) 接口
也称为 MCU 接口,由于其常用于单片机领域而得名。 MCU-LCD 接口的标准术语是 Intel 提出的 8080 总线标准,因此在很多文档中用 I80 来指 MCU-LCD 屏。8080 是一种并行接, 别称很多 DBI(Data Bus interface) 数据总线接口, 微处理器 MPU 接口,MCU 接口,CPU 接口的,实际上都是一回事。它是一种并行、异步、半双工通信协议 ,作用是用于外扩 RAM、ROM 后来又将其应用到了 LCD 接口。数据传输位宽有 8 位、9 位、16 位、18 位、24 位。通常该接口的显示模块被称为LCM, 它由 LCD 图形显存(GRAM) + LCD 控制器 + LCD 屏幕 组成。
优点: 控制简单方便,无需时钟和同步信号。
缺点:GRAM 很贵 ,所以难以做到大屏( 3.8 以上)
其接口时序如下:
引脚
名称
说明
CS
片选信号
低电平有效
RS
数据命令
1:数据 0:命令
WR
读数据/命令
上升沿读取数据/命令
RD
写数据/命令
上升沿写入数据/命令
1.2.2 RGB(MIPI DPI) 接口
是一种并行接口,采用普通的同步、时钟、信号线来传输特定数据,采用 SPI/I2C 等控制线辅助进行命令控制。采用这种接口的 LCD 内部无 GRAM. LCD 图形显存(GRAM) + LCD 控制器 集成在 soc 端。因此 LCD 模组可以将成本控制的更低,屏幕做的更大,多用于手机平板行业。采用同步实时传输的方式,也使得它的显示帧率更快。本文所用 imx6ull 的接口也是该接口. 其接口时序如下:
名称
含义
Vsync
垂直/场同步信号
VBP
垂直后沿,在垂直同步信号之后的无效行数,显示的上边框
VFP
垂直前沿,下一次垂直同步信号来之前的无效行数,显示下边框
Hsync
水平/行同步信号
HBP
水平前肩,水平同步信号之后的无效像素个数,显示左边框框
HFP
水平后肩,下一次水平同步信号来之前的无效像素个数,显示右边边框
DE
有效数据选通信号也称数据使能信号
1.2.3 MIPI DSI 接口
当我们口述mipi接口的时候,大多数描述的都是这个接口。显示串行接口 (DSI) 规范定义了主机处理器和外围设备之间的协议,这些协议符合 MIPI 联盟的移动设备接口规范。为了让我们的图像传输的更快,更稳定,MIPI 联盟定义了该接口,其中包括 DBI-2 [2]、DPI-2 [3] 和 DCS [1] 定义的像素格式和命令集。可以简单理解为 DPI 信号的又一层封装 。当前我们用到的所有便携式设备的屏幕,手机平板等都是采用该接口来传输. 关于其详细描述可阅读 MIPI Alliance Standard for Display Serial Interface 手册。由于其泛用性后续会专门出一篇文章用于描述该接口.除此之外还有 i2c/spi、lvds、edp、dp 等接口,由于笔者未使用或不常使用这些接口就不多介绍了。
1.3 Linux FrameBuffer 子系统
FrameBuffer 也叫帧缓冲,即一帧图片所需要的内存空间。Framebuffer 是一个抽象的概念,在 GPU 能够访问的空间范围内( GPU 的物理地址空间),任意分配一段内存(或显存) ,都可以作为 Framebuffer 使用,只需要在分配后将该内存区域信息,设置到显卡相关的寄存器中即可。在 linux 中我们将要显示的数据写入显存,FB 框架就会帮我们显示出对应的图片。需要注意的是,FB 框架并不会直接显示 FremFuffer 中设置的像素颜色 ,而是会根据设置的值到调色板 中去找到对应颜色再进行显示。Linux FrameBuffer 子系统的本质就是一个主设备号为 29 字符设备 ,在文件系统中描述为 /dev/fbn。整体框架如下所示.
对于 fb 驱动,分配 fb_info, 设置 fb_info, 平台硬件相关操作,注册 fb_info。可见驱动的核心在 fb_info.相关数据结构如下所述。
2. 重要数据结构
2.1 fb_info
frambuffer 子系统核心数据接口,每一个 fb 设备就是一个 fb_info 结构, 包含该 fb 设备的全部信息
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 struct fb_info { atomic_t count; //打开计数 int node; //存放该 fb 在 fb 数组中的下标,也可以理解为次设备信息 int flags; //标志位 struct fb_var_screeninfo var; /* 可变的参数信息 */ struct fb_fix_screeninfo fix; /* 固定的参数信息*/ struct fb_monspecs monspecs; struct work_struct queue; struct fb_pixmap pixmap; // 用于表示一个像素图像(pixmap)的信息 struct fb_pixmap sprite; struct fb_cmap cmap; struct list_head modelist; struct fb_videomode *mode; /* 当前的显示模式*/ #if IS_ENABLED(CONFIG_FB_BACKLIGHT) struct backlight_device *bl_dev; //对应的背光设备 struct mutex bl_curve_mutex; u8 bl_curve[FB_BACKLIGHT_LEVELS]; //背光调整 #endif struct fb_ops *fbops; //基于各个平台正真的硬件操作函数接口 struct device *device; //父设备节点 struct device *dev; //设备节点 union { char __iomem *screen_base; // 虚拟地址 char *screen_buffer; }; unsigned long screen_size; // 虚拟内存大小 void *pseudo_palette; // 伪16色颜色表 #define FBINFO_STATE_RUNNING 0 #define FBINFO_STATE_SUSPENDED 1 u32 state; //当前状态,挂起或者复位 ......
2.2 fb_var_screeninfo
可以通过该结构获取到 LCD 的可变参数信息。
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 struct fb_var_screeninfo { __u32 xres; __u32 yres; __u32 xres_virtual; __u32 yres_virtual; __u32 xoffset; __u32 yoffset; __u32 bits_per_pixel; __u32 grayscale; struct fb_bitfield red ; struct fb_bitfield green ; struct fb_bitfield blue ; struct fb_bitfield transp ; __u32 nonstd; __u32 activate; __u32 height; __u32 width; __u32 accel_flags; __u32 pixclock; __u32 left_margin; __u32 right_margin; __u32 upper_margin; __u32 lower_margin; __u32 hsync_len; __u32 vsync_len; __u32 sync; __u32 vmode; __u32 rotate; __u32 reserved[5 ]; };
1 2 3 4 5 struct fb_bitfield { __u32 offset; __u32 length; __u32 msb_right; };
2.3 fb_fix_screeninfo
lcd 硬件的固定参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct fb_fix_screeninfo { char id[16 ]; unsigned long smem_start; __u32 smem_len; __u32 type; __u32 type_aux; __u32 visual; __u16 xpanstep; __u16 ypanstep; __u16 ywrapstep; __u32 line_length; unsigned long mmio_start; __u32 mmio_len; __u32 accel; __u16 reserved[3 ]; };
2.4 fb_ops
基于各个平台正真的操作函数接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 struct fb_ops { struct module *owner ; ...... int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info); int (*fb_blank)(int blank, struct fb_info *info); void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect); void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region); void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image); ...... };
2.5 fb_pixmap
用于表示一个像素图像 pixmap 的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct fb_pixmap { unsigned int bpp; /* 每个像素使用的位数(Bits per pixel) */ unsigned int depth; /* 每个像素使用的位数(包括额外的位数) */ unsigned int size; /* 像素数据缓冲区的大小(以字节为单位)。 */ unsigned int offset; /* 从像素数据缓冲区的开头到第一个像素的偏移量(以字节为单位) */ // 缓冲区的对齐方式(以字节为单位), 在常见的像素格式中,buf_align 可能会被设置为以下值: // 1 字节对齐:对于 8 位和 16 位像素格式,通常设置为 1。 // 2 字节对齐:对于 24 位和 32 位像素格式,通常设置为 2。 // 4 字节对齐:对于 64 位像素格式,通常设置为 4。 unsigned int buf_align; // 在常见的像素格式中,scan_align 可能会被设置为以下值: // 1 字节对齐:对于 8 位像素格式,通常设置为 1。 // 2 字节对齐:对于 16 位像素格式,通常设置为 2。 // 4 字节对齐:对于 24 位和 32 位像素格式,通常设置为 4。 // 8 字节对齐:对于 64 位像素格式,通常设置为 8。 unsigned int scan_align; /* 扫描线的对齐方式(以字节为单位) */ unsigned int access_align; /* 访问的对齐方式(以字节为单位) */ unsigned int flags; /* 用于标识像素数据的标志位 */ void *addr; /* 指向像素数据缓冲区的虚拟地址 */ struct page **pages; /* 一个指针数组,每个指针指向一块物理内存页面的 struct page 结构体 */ };
2.6 fb_videomode
非常重要的结构体 , 描述一个屏幕的参数, 如分辨率,前后肩等.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct fb_videomode { const char *name; /* optional */ u32 refresh; // 刷新率 u32 xres; // 屏幕像素宽度 u32 yres; // 屏幕像素高度 u32 pixclock; // 像素时钟 u32 left_margin; // 左边框, 即 HBP u32 right_margin; // 右边框, 即 HFP u32 upper_margin; // 上边框, 即 VBP u32 lower_margin; // 下边框, 即 VFP u32 hsync_len; // 水平同步信号, hsync u32 vsync_len; // 垂直同步信号, vsync u32 sync; // 帧同步信息 u32 vmode; // 显示模式 u32 flag; // mode 标志位, 如果是上层设置的 mode , 则应该设置 FB_MODE_USER 标志位. };
3. 源码阅读
3.1 fbmem_init
该函数的实现完成了 3 件事情:
在 proc/ 下创建 fb 设备
注册一个主设备号为 29 的字符设备
创建一个设备类 /sys/graphics
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 static int __init fbmem_init (void ) { int ret; if (!proc_create("fb" , 0 , NULL , &fb_proc_fops)) 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; } return 0 ; err_class: unregister_chrdev(FB_MAJOR, "fb" ); err_chrdev: remove_proc_entry("fb" , NULL ); return ret; }
3.2 register_framebuffer
注册一个真缓冲设备, 其主体结构为 fb_info.
1 2 3 4 5 6 7 8 9 10 11 int 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; } EXPORT_SYMBOL(register_framebuffer);
3.2.1 do_register_framebuffer
注册一个 fb_info, 流程如下
1, 字节序检测根据, 返回结果查看当前设置的 fb 字节序是否有问题.
2, 检测 pci 内存, 移除内存重叠的 fb 设备.
3, 检测当前 fb_info 的注册是否超过上限
4, 创建 fb 设备, /sys/class/fbx 以及 /dev/fbx, 设置私有数据, 创建一系列属性文件节点.
5, 初始化像素信息, 包括像素缓冲区大小, 对齐方式等.
6, 从可变参数中获取屏幕信息, 转换为 mode 结构之后, 注册到 fb_info->modlist
7, 以次设备号为数组下标, 注册 fb_info 到 registered_fb 全局数组中
8, 调用 fb 内核通知链并发出 FB_EVENT_FB_REGISTERED 事件.
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 static int do_register_framebuffer (struct fb_info *fb_info) { int i, ret; struct fb_event event ; struct fb_videomode mode ; if (fb_check_foreignness(fb_info)) return -ENOSYS; ret = do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, fb_is_primary_device(fb_info)); if (ret) return ret; if (num_registered_fb == FB_MAX) return -ENXIO; num_registered_fb++; for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break ; fb_info->node = i; atomic_set (&fb_info->count, 1 ); mutex_init(&fb_info->lock); mutex_init(&fb_info->mm_lock); fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL , "fb%d" , i); if (IS_ERR(fb_info->dev)) { printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n" , i, PTR_ERR(fb_info->dev)); fb_info->dev = NULL ; } else fb_init_device(fb_info); if (fb_info->pixmap.addr == NULL ) { fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); if (fb_info->pixmap.addr) { fb_info->pixmap.size = FBPIXMAPSIZE; fb_info->pixmap.buf_align = 1 ; fb_info->pixmap.scan_align = 1 ; fb_info->pixmap.access_align = 32 ; fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; } } fb_info->pixmap.offset = 0 ; if (!fb_info->pixmap.blit_x) fb_info->pixmap.blit_x = ~(u32)0 ; if (!fb_info->pixmap.blit_y) fb_info->pixmap.blit_y = ~(u32)0 ; if (!fb_info->modelist.prev || !fb_info->modelist.next) INIT_LIST_HEAD(&fb_info->modelist); if (fb_info->skip_vt_switch) pm_vt_switch_required(fb_info->dev, false ); else pm_vt_switch_required(fb_info->dev, true ); fb_var_to_videomode(&mode, &fb_info->var); fb_add_videomode(&mode, &fb_info->modelist); registered_fb[i] = fb_info; event.info = fb_info; if (!lockless_register_fb) console_lock(); if (!lock_fb_info(fb_info)) { if (!lockless_register_fb) console_unlock(); return -ENODEV; } fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); unlock_fb_info(fb_info); if (!lockless_register_fb) console_unlock(); return 0 ; }
3.2.2 fb_check_foreignness
字节序检测根据, 返回结果查看当前设置的字节序是否有问题.
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 static int fb_check_foreignness (struct fb_info *fi) { const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN; fi->flags &= ~FBINFO_FOREIGN_ENDIAN; #ifdef __BIG_ENDIAN fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH; #else fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0 ; #endif if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) { pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to " "support this framebuffer\n" , fi->fix.id); return -ENOSYS; } else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) { pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to " "support this framebuffer\n" , fi->fix.id); return -ENOSYS; } return 0 ; }
该函数用来检测, fb 中数据的大小端配置是否正常:
1, 如果 FBINFO_FOREIGN_ENDIAN 指定采用当前处理器的字节序, 且当前字节处理器字节序为小端, 则应该设置 CONFIG_FB_LITTLE_ENDIAN 这个宏.
2, 如果 FBINFO_FOREIGN_ENDIAN 指定采用当前处理器的字节序, 且当前字节处理器字节序为大端, 则应该设置 CONFIG_FB_BIG_ENDIAN 这个宏.
3, 如果 FBINFO_FOREIGN_ENDIAN 字节序与当前处理器的字节序相反, 且当前字节处理器字节序为大端, 则应该设置 CONFIG_FB_LITTLE_ENDIAN 这个宏.
4, 如果 FBINFO_FOREIGN_ENDIAN 字节序与当前处理器的字节序相反, 且当前字节处理器字节序为小端, 则应该设置 CONFIG_FB_BIG_ENDIAN 这个宏.
FBINFO_FOREIGN_ENDIAN = 0
#ifdef __BIG_ENDIAN
==>
CONFIG_FB_LITTLE_ENDIAN
FBINFO_FOREIGN_ENDIAN = 0
#unifdef __BIG_ENDIAN
==>
CONFIG_FB_BIG_ENDIAN
FBINFO_FOREIGN_ENDIAN = 1
#ifdef __BIG_ENDIAN
==>
CONFIG_FB_LITTLE_ENDIAN
FBINFO_FOREIGN_ENDIAN = 1
#unifdef __BIG_ENDIAN
==>
CONFIG_FB_BIG_ENDIAN
3.2.3 do_remove_conflicting_framebuffers
遍历系统中所有已加载的 framebuffer 设备,检查它们的显存区域是否与当前 framebuffer 设备的显存区域存在重叠,如果存在重叠,则将其标记为冲突设备,并从系统中移除。
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 #define VGA_FB_PHYS 0xA0000 static int do_remove_conflicting_framebuffers (struct apertures_struct *a, const char *name, bool primary) { int i, ret; for (i = 0 ; i < FB_MAX; i++) { struct apertures_struct *gen_aper ; if (!registered_fb[i]) continue ; if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE)) continue ; gen_aper = registered_fb[i]->apertures; if (fb_do_apertures_overlap(gen_aper, a) || (primary && gen_aper && gen_aper->count && gen_aper->ranges[0 ].base == VGA_FB_PHYS)) { printk(KERN_INFO "fb: switching to %s from %s\n" , name, registered_fb[i]->fix.id); ret = do_unregister_framebuffer(registered_fb[i]); if (ret) return ret; } } return 0 ; }
3.2.4 fb_init_device
设置私有数据
设置 FB_SYSFS_FLAG_ATTR 标志
创建一系列属性文件
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 int fb_init_device (struct fb_info *fb_info) { int i, error = 0 ; dev_set_drvdata(fb_info->dev, fb_info); fb_info->class_flag |= FB_SYSFS_FLAG_ATTR; for (i = 0 ; i < ARRAY_SIZE(device_attrs); i++) { error = device_create_file(fb_info->dev, &device_attrs[i]); if (error) break ; } if (error) { while (--i >= 0 ) device_remove_file(fb_info->dev, &device_attrs[i]); fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; } return 0 ; } static struct device_attribute device_attrs [] = { __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp), __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank), __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console), __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor), __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode), __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes), __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan), __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual), __ATTR(name, S_IRUGO, show_name, NULL ), __ATTR(stride, S_IRUGO, show_stride, NULL ), __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate), __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate), #ifdef CONFIG_FB_BACKLIGHT __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve), #endif };
3.2.5 fb_var_to_videomode
非常重要的内核接口, 将 fb_var_screeninfo 参数转换为 fb_videomode 中的参数.
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 void fb_var_to_videomode (struct fb_videomode *mode, const struct fb_var_screeninfo *var) { u32 pixclock, hfreq, htotal, vtotal; mode->name = NULL ; mode->xres = var->xres; mode->yres = var->yres; mode->pixclock = var->pixclock; mode->hsync_len = var->hsync_len; mode->vsync_len = var->vsync_len; mode->left_margin = var->left_margin; mode->right_margin = var->right_margin; mode->upper_margin = var->upper_margin; mode->lower_margin = var->lower_margin; mode->sync = var->sync; mode->vmode = var->vmode & FB_VMODE_MASK; mode->flag = FB_MODE_IS_FROM_VAR; mode->refresh = 0 ; if (!var->pixclock) return ; pixclock = PICOS2KHZ(var->pixclock) * 1000 ; htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin; vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin; if (var->vmode & FB_VMODE_INTERLACED) vtotal /= 2 ; if (var->vmode & FB_VMODE_DOUBLE) vtotal *= 2 ; hfreq = pixclock / htotal; mode->refresh = hfreq / vtotal; }
3.4 fb_open
fb 框架的 open 函数, 通过次设备号获取到对应的 fb_info, 并检查其是否设置 info->fbops->owner
, 未设置将返回 err, 最后调用真正的 fb 设备的 open 函数 info->fbops->fb_open.
read 和 ioctl 等函数同理, 不再分析.
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 static int fb_open (struct inode *inode, struct file *file) __acquires (&info->lock) __releases (&info->lock) { int fbidx = iminor(inode); struct fb_info *info ; int res = 0 ; info = get_fb_info(fbidx); if (!info) { request_module("fb%d" , fbidx); info = get_fb_info(fbidx); if (!info) return -ENODEV; } if (IS_ERR(info)) return PTR_ERR(info); mutex_lock(&info->lock); if (!try_module_get(info->fbops->owner)) { res = -ENODEV; goto out; } 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); #endif out: mutex_unlock(&info->lock); if (res) put_fb_info(info); return res; }
4. 基于 imx6ull 编写一个 fb 驱动
该驱动根据伟东山视频编写, 有兴趣可自行购买 https://www.100ask.net/
4.1 数据手册阅读.
4.1.1 Overview
增强型液晶显示接口 Enhanced LCD Interface(eLCDIF)是一种通用的显示控制器,用于驱动各种大小和能力不同的显示设备. eLCDIF块支持以下功能:
显示一个异步并行 MPU 接口,用于命令和数据传输到一个集成的帧缓冲区。
显示支持移动图片和需要 RGB 接口模式(DOTCLK 界面)。
VSYNC 的数据传输高速模式
接受 ITU-R BT 的数字视频编码器。656 格式 4:2:2 YCbCr数字组件视频,并将其转换为模拟电视信号
eLCDIF为所支持的接口提供了完全可编程的功能:
总线主接口到源帧缓冲区数据,以进行显示刷新。这个接口也可以用于驱动“智能”显示器的数据。
PIO 接口,以管理“智能”显示器和 SoC 之间的数据传输
根据I/O mux选项,可支持8/16/18/24/32位LCD数据总线。
可编程的定时和参数的 MPU,VSYNC,和 DOTCLK LCD接口,以支持各种显示器。
ITU-R BT.656模式(这里称为数字视频接口或DVI模式),包括渐进到隔行功能以及 RGB 到 YCbCr 4:2:2 颜色空间转换,以支持525/60和625/50操作。
4.1.2 LCDIF_CTRL
位域
名
读写
描述
[31]
SFTRST
R/W
软件复位,正常工作时应设为 0;如果设为 1,它会复位整个 LCD 控制器
[30]
CLKGATE
R/W
时钟开关,0:正常工作时要设置为 0;1:关闭 LCD 控制器时钟
[29]
YCBCR422_INPUT
R/W
0 表示输入数据是 rgb 数据, 1 表示为 YCbCr 4:2:2 格式数据.
[28]
READ_WRITEB
R/W
设置为 1 则是8080读, 写模式时必须设置 0
[27]
WAIT_FOR_VSYNC_EDGE
R/W
设置为 1 则会等待 vsync 信号, 仅仅用于 vsync mode
[26]
DATA_SHIFT_DIR
R/W
0 数据为左移 SHIFT_NUM_BITS, 1 数据右移 SHIFT_NUM_BITS 位, DIV 模式下对时序不起作用
[25:21]
SHIFT_NUM_BITS
R/W
数据位移多少位
[20]
DVI_MODE
R/W
设置为 1 时为 DIV 模式接口, 即 ITU-R BT.656
[19]
BYPASS_COUNT
R/W
DOTCLK 和 DVI 模式下需要设置为 1; MPU 和 VSYNC 模式时设为 0
[18]
VSYNC_MODE
R/W
使用 VSYNC 模式时,设置为 1
[17]
DOTCLK_MODE
R/W
使用 DOTCLK 模式时,设置为 1
[16]
DATA_SELECT
R/W
仅 MPU 下使用, 0 cmd mode; 1 date cmd
[15:14]
INPUT_DATA_SWIZZLE
R/W
显存中像素颜色的数据转给 LCD 控制器时,字节位置是否交换:0x0: NO_SWAP,不交换; 0x0: LITTLE_ENDIAN , 小字节序,跟 NO_SWAP 一样;0x1: BIG_ENDIAN_SWAP,字节 0、3 交换;字节 1、2 交换;0x1: SWAP_ALL_BYTES,字节 0、3 交换;字节 1、2 交换;0x2: HWD_SWAP,半字交换,即 0x12345678 转为 0x56781234 0x3: HWD_BYTE_SWAP,在每个半字内部放换字节,即 0x12345678 转换为 0x34127856
[13:12]
CSC_DATA_SWIZZLE
R/W
显存中的数据被传入 LCD 控制器内部并被转换为 24BPP 后,在它被转给 LCD 接口之前,字节位置是否交换:0x0:NO_SWAP,不交换;0x0:LITTLE_ENDIAN , 小字节序 , 跟NO_SWAP 一样;0x1:BIG_ENDIAN_SWAP,字节 0、3 交换;字节 1、2 交换;0x1:SWAP_ALL_BYTES,字节 0、3 交换;字节 1、2 交换;0x2:HWD_SWAP,半字交换,即 0x12345678转为 0x567812340x3:HWD_BYTE_SWAP,在每个半字内部放换字节, 即 0x12345678 转换为 0x34127856
[13:12]
CSC_DATA_SWIZZLE
R/W
显存中的数据被传入 LCD 控制器内部并被转换为 24BPP 后,在它被转给 LCD 接口之前,字节位置是否交换:0x0:NO_SWAP,不交换;0x0:LITTLE_ENDIAN , 小字节序 , 跟NO_SWAP 一样;0x1:BIG_ENDIAN_SWAP,字节 0、3 交换;字节 1、2 交换;0x1:SWAP_ALL_BYTES,字节 0、3 交换;字节 1、2 交换;0x2:HWD_SWAP,半字交换,即 0x12345678转为 0x567812340x3:HWD_BYTE_SWAP,在每个半字内部放换字节,即 0x12345678 转换为 0x34127856
[11:10]
LCD_DATABUS_WIDTH
R/W
LCD 数据总线宽度,就是对外输出的 LCD 数据的位宽.有多少根线 0x0:16 位;0x1:8 位;0x2:18 位;0x3:24 位
[9:8]
WORD_LENGTH
R/W
输入的数据格式,即显存中每个像素占多少位,0x0:16 位;0x1:8 位;0x2:18 位;0x3:24 位
[7]
GB_TO_YCBCR422_CSC
R/W
设置为 1 时,使能颜色空间转换:RGB 转为 YCbCr
[6]
ENABLE_PXP_HANDSHAKE
R/W
当 LCDIF_MASTER 设置为 1 时,再设置这位,则 LCD 控制器跟 PXP 之间的握手机制被关闭(我们不关心)
[5]
MASTER
R/W
设置为 1 时,LCD 控制器成为 bus master
[4]
RSRVD0
R/W
保留
[3]
DATA_FORMAT_16_BIT
R/W
WORD_LENGTH 为 0 时,表示一个像素用 16 位,此位作用如下:0:数据格式为 ARGB565 1:数据格式为 ARGB555
[2]
DATA_FORMAT_18_BIT
R/W
WORD_LENGTH 为 2 时,表示一个像素用 18位,RGB 数据还是保存在 32 位数据里,此位作用如下:0:低 18 位用来表示 RGB666,高 14 位无效1:高 18 位用来表示 RGB666,低 14 位无效
[1]
DATA_FORMAT_24_BIT
R/W
WORD_LENGTH 为 3 时,表示一个像素用 24位,此位作用如下:0:所有的 24 位数据都有效,格式为 RGB888 1:转给 LCD 控制器的数据是 24 位的,但只用到其中的 18 位,每个字节用来表示一个颜色,每字节中高 2 位无效
4.1.3 LCDIF_CTRL1
位域
名
读写
描述
[19:16]
BYTE_PACKING_FORMAT
R/W
用来表示一个 32 位的 word 中,哪些字节是有效的,即哪些字节是用来表示颜色的。bit16、17、18、19 分别对应 byte0、1、2、3;某位为 1,就表示对应的字节有效。默认值是 0xf,表示 32 位的 word 中,所有字节都有效。对于 8bpp,可以忽略本设置,所有的字节都是有效的;对于 16bpp,bit[1:0]、bit[3:2]分别对应一个字节,组合中的 2 位都为 1 时,对应的字节才有效;对于 24bpp,0x7 表示 32 位数据中只用到 3 个字节,这称为“24 bit unpacked format”,即 ARGB,其中的 A 字节被丢弃
[0]
RESET
R/W
复位 LCD,0:LCD_RESET 引脚输出低电平;1:LCD_RESET 引脚输出高电平
4.1.4 LCDIF_TRANSFER_COUNT
位域
名
读写
描述
[31:16]
V_COUNT
R/W
一帧中有多少行数据
[15:0]
V_COUNT
R/W
一帧中有多少列数据
4.1.5 LCDIF_VDCTRL0
位域
名
读写
描述
[29]
VSYNC_OEB
R/W
用来控制 VSYNC 信号,对于 DOTCLK 模式,设为 0,0:VSYNC 是输出引脚,用 LCD 控制器产生;1:VSYNC 是输入引脚
[28]
ENABLE_PRESENT
R/W
在 DOTCLK 模式下,硬件是否会产生数据使能信号 ENALBE:0:不产生;1:产生
[27]
VSYNC_POL
R/W
用来决定 VSYNC 脉冲的极性:0:低脉冲;1:高脉冲
[26]
VSYNC_POL
R/W
用来决定 HSYNC 脉冲的极性:0:低脉冲;1:高脉冲
[25]
DOTCLK_POL
R/W
用来决定 DOTCLK 的极性: 0:LCD 控制器在 DOTCLK 下降沿发送数据,LCD 在上升沿捕获数据;1:反过来
[24]
ENABLE_POL
R/W
用来决定 ENABLE 信号的极性: 0:数据有效期间,ENABLE 信号为低;1:反过来
[21]
VSYNC_PERIOD_UNIT
R/W
用来决定 VSYNC_PERIOD 的单位: 0:单位是像素时钟(pix_clk),这在 VSYNC 模式下使用;1:单位是“整行”,这在 DOTCLK 模式下使用
[20]
VSYNC_PULSE_WIDTH_UNIT
R/W
用来决定 VSYNC_PULSE_WIDTH 的单位: 0:单位是像素时钟(pix_clk);1:单位是“整行”
[19]
HALF_LINE
R/W
VSYNC 周期是否周加上半行的时间: 0:VSYNC 周期=VSYNC_PERIOD;1 : VSYNC 周 期=VSYNC_PERIOD+HORIZONTAL_PERIOD/2
[18]
R/W
0:第 1 帧将在一行的中间结束,第 2 帧在一行的中间开始;1:所有帧结束前都加上半行时间,这样所有帧都会起始于“行的开头”
[17:0]
VSYNC_PULSE_WIDTH
R/W
VSYNC 脉冲的宽度
4.1.6 LCDIF_VDCTRL1
位域
名
读写
描述
[29]
VSYNC_PERIOD
R/W
两个垂直同步信号之间的间隔,即垂直方向同步信号的总周期;单位由 VSYNC_PERIOD_UNIT 决定
4.1.7 LCDIF_VDCTRL2
位域
名
读写
描述
[[31:18]
HSYNC_PULSE_WIDTH
R/W
HSYNC 脉冲的宽度(单位:pix_clk)
[[17:0]
HSYNC_PERIOD
R/W
整行的宽度,即两个 HYSNC 信号之间的宽度(单位:pix_clk)
4.1.8 LCDIF_VDCTRL3
位域
名
读写
描述
[28]
VSYNC_ONLY
R/W
0:DOTCLK 模式时必须设置为 0;1:VSYNC 模式时必须设置为 1
[27:16]
HORIZONTAL_WAIT_CNT
R/W
水平方向上的等待像素个数,等于 thp+thb
[[15:0]
VERTICAL_WAIT_CNT
R/W
垂直方向上的等待行数,等于 tvp+tvb
4.1.9 LCDIF_VDCTRL4
位域
名
读写
描述
[31:29]
DOTCLK_DLY_SELs
R/W
在 LCD 控制器内部的 DOTCLK 输出到LCD_DOTCK 引脚时,延时多久:0:2ns;1:4ns;2:6ns;3:8ns;其他值保留
[18]
SYNC_SIGNALS_ON
R/W
DOTCLK 模式下必须设为 1
[17:0]
DOTCLK_H_VALID_DATA_CNT
R/W
水 平 方 向 上 的 有 效 像素个数 (pix_clk),即分辨率的 y
4.1.10 LCDIF_CUR_BUF
位域
名
读写
描述
[31:0]
ADDR
R/W
LCD 控制器正在传输的当前帧在显存中的地址
4.1.11 LCDIF_NEXT_BUF
位域
名
读写
描述
[31:0]
ADDR
R/W
LCD 下一帧在显存中的地址
4.2 编写一个最简单的 fb 驱动
4.2.1 头文件
lcd 控制器寄存器对应结构体
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 #ifndef _IMX6ULL_SOC_H_ #define _IMX6ULL_SOC_H_ struct imx6ull_lcdif { volatile unsigned int CTRL; volatile unsigned int CTRL_SET; volatile unsigned int CTRL_CLR; volatile unsigned int CTRL_TOG; volatile unsigned int CTRL1; volatile unsigned int CTRL1_SET; volatile unsigned int CTRL1_CLR; volatile unsigned int CTRL1_TOG; volatile unsigned int CTRL2; volatile unsigned int CTRL2_SET; volatile unsigned int CTRL2_CLR; volatile unsigned int CTRL2_TOG; volatile unsigned int TRANSFER_COUNT; unsigned char RESERVED_0[12 ]; volatile unsigned int CUR_BUF; unsigned char RESERVED_1[12 ]; volatile unsigned int NEXT_BUF; unsigned char RESERVED_2[12 ]; volatile unsigned int TIMING; unsigned char RESERVED_3[12 ]; volatile unsigned int VDCTRL0; volatile unsigned int VDCTRL0_SET; volatile unsigned int VDCTRL0_CLR; volatile unsigned int VDCTRL0_TOG; volatile unsigned int VDCTRL1; unsigned char RESERVED_4[12 ]; volatile unsigned int VDCTRL2; unsigned char RESERVED_5[12 ]; volatile unsigned int VDCTRL3; unsigned char RESERVED_6[12 ]; volatile unsigned int VDCTRL4; unsigned char RESERVED_7[12 ]; volatile unsigned int DVICTRL0; unsigned char RESERVED_8[12 ]; volatile unsigned int DVICTRL1; unsigned char RESERVED_9[12 ]; volatile unsigned int DVICTRL2; unsigned char RESERVED_10[12 ]; volatile unsigned int DVICTRL3; unsigned char RESERVED_11[12 ]; volatile unsigned int DVICTRL4; unsigned char RESERVED_12[12 ]; volatile unsigned int CSC_COEFF0; unsigned char RESERVED_13[12 ]; volatile unsigned int CSC_COEFF1; unsigned char RESERVED_14[12 ]; volatile unsigned int CSC_COEFF2; unsigned char RESERVED_15[12 ]; volatile unsigned int CSC_COEFF3; unsigned char RESERVED_16[12 ]; volatile unsigned int CSC_COEFF4; unsigned char RESERVED_17[12 ]; volatile unsigned int CSC_OFFSET; unsigned char RESERVED_18[12 ]; volatile unsigned int CSC_LIMIT; unsigned char RESERVED_19[12 ]; volatile unsigned int DATA; unsigned char RESERVED_20[12 ]; volatile unsigned int BM_ERROR_STAT; unsigned char RESERVED_21[12 ]; volatile unsigned int CRC_STAT; unsigned char RESERVED_22[12 ]; volatile unsigned int STAT; unsigned char RESERVED_23[76 ]; volatile unsigned int THRES; unsigned char RESERVED_24[12 ]; volatile unsigned int AS_CTRL; unsigned char RESERVED_25[12 ]; volatile unsigned int AS_BUF; unsigned char RESERVED_26[12 ]; volatile unsigned int AS_NEXT_BUF; unsigned char RESERVED_27[12 ]; volatile unsigned int AS_CLRKEYLOW; unsigned char RESERVED_28[12 ]; volatile unsigned int AS_CLRKEYHIGH; unsigned char RESERVED_29[12 ]; volatile unsigned int SYNC_DELAY; }; #endif
4.2.2 驱动实现
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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 #include <linux/module.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/fb.h> #include <linux/init.h> #include <linux/dma-mapping.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/cpufreq.h> #include <linux/io.h> #include <video/display_timing.h> #include <video/of_display_timing.h> #include <linux/gpio/consumer.h> #include <asm/div64.h> #include <asm/mach/map.h> #include "my_lcd_con.h" struct fb_device { struct platform_device *pdev ; struct clk *clk_pix ; struct clk *clk_axi ; struct display_timing *dt ; struct imx6ull_lcdif * lcdif ; char fb_bbp; char lcd_bbp; struct resource * res ; }; static unsigned int pseudo_palette[16 ];static inline unsigned int chan_to_field (unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff ; chan >>= 16 - bf->length; return chan << bf->offset; } static int mylcd_setcolreg (unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { unsigned int val; switch (info->fix.visual) { case FB_VISUAL_TRUECOLOR: if (regno < 16 ) { u32 *pal = info->pseudo_palette; val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); pal[regno] = val; } break ; default : return 1 ; } return 0 ; } static struct fb_ops my_fb_ops = { .owner = THIS_MODULE, .fb_read = fb_sys_read, .fb_write = fb_sys_write, .fb_fillrect = sys_fillrect, .fb_copyarea = sys_copyarea, .fb_imageblit = sys_imageblit, .fb_setcolreg = mylcd_setcolreg, }; int lcd_of_read_mode (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct fb_device *fb_dev = info->par; struct device_node *display_np = NULL ; struct display_timings *timings = NULL ; display_np = of_parse_phandle(pdev->dev.of_node, "display" , 0 ); if (!display_np){ dev_err(&pdev->dev, "Failed to read display node\n" ); return -EINVAL; } timings = of_get_display_timings(display_np); if (!timings){ dev_err(&pdev->dev, "Failed to read display timings\n" ); return -EINVAL; } fb_dev->dt = timings->timings[timings->native_mode]; return 0 ; } static int set_fb_info_var (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct fb_device *fb_dev = info->par; struct display_timing *dt = fb_dev->dt; info->var.xres = dt->hactive.typ; info->var.yres = dt->vactive.typ; info->var.xres_virtual = info->var.xres; info->var.yres_virtual = info->var.yres; info->var.bits_per_pixel = 16 ; info->var.red.offset = 11 ; info->var.red.length = 5 ; info->var.green.offset = 5 ; info->var.green.length = 6 ; info->var.blue.offset = 0 ; info->var.blue.length = 5 ; return 0 ; } static int set_fb_info_fix (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); dma_addr_t dma_addr; strcpy (info->fix.id, "mylcd" ); info->fix.smem_len = info->var.xres * info->var.yres * info->var.bits_per_pixel / 8 ; if (24 == info->var.bits_per_pixel) info->fix.smem_len = info->var.xres * info->var.yres * 4 ; info->screen_base = dma_alloc_wc(info->dev, info->fix.smem_len, &dma_addr,GFP_KERNEL); info->fix.smem_start = dma_addr; info->fix.type = FB_TYPE_PACKED_PIXELS; info->fix.visual = FB_VISUAL_TRUECOLOR; info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8 ; if (info->var.bits_per_pixel == 24 ) info->fix.line_length = info->var.xres * 4 ; return 0 ; } static void lcd_controler_enable (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct fb_device *fb_dev = info->par; struct imx6ull_lcdif *lcdif = fb_dev->lcdif; lcdif->CTRL |= (1 << 0 ); } static int lcd_controler_init (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct fb_device *fb_dev = info->par; struct imx6ull_lcdif *lcdif = fb_dev->lcdif; struct display_timing *dt = fb_dev->dt; unsigned int hsync_pol = 0 ; unsigned int vsync_pol = 0 ; unsigned int de_pol = 0 ; unsigned int clk_pol = 0 ; int lcd_data_bus_width = 0 ; int fb_width = 0 ; if (fb_dev->lcd_bbp == 24 ) lcd_data_bus_width = 3 ; else if (fb_dev->lcd_bbp == 18 ) lcd_data_bus_width = 2 ; else if (fb_dev->lcd_bbp == 8 ) lcd_data_bus_width = 1 ; else if (fb_dev->lcd_bbp) lcd_data_bus_width = 0 ; if (fb_dev->lcd_bbp == 24 ) fb_width = 3 ; else if (fb_dev->lcd_bbp == 18 ) fb_width = 2 ; else if (fb_dev->lcd_bbp == 8 ) fb_width = 1 ; else if (fb_dev->lcd_bbp == 16 ) fb_width = 0 ; lcdif->CTRL = (1 << 19 ) | (1 << 17 ) | (lcd_data_bus_width << 10 ) | (fb_width << 8 ) | (1 << 5 ); if (fb_dev->fb_bbp == 32 || fb_dev->fb_bbp == 24 ){ lcdif->CTRL1 &= ~(0xf << 16 ); lcdif->CTRL1 |= (0x7 << 16 ); }else { lcdif->CTRL1 |= (0xf << 16 ); } lcdif->TRANSFER_COUNT = (dt->vactive.typ << 16 ) | (dt->hactive.typ << 0 ); if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH) hsync_pol = 1 ; if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH) vsync_pol = 1 ; if (dt->flags & DISPLAY_FLAGS_DE_HIGH) de_pol = 1 ; if (dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE) clk_pol =1 ; lcdif->VDCTRL0 = (1 << 28 )|( vsync_pol << 27 )\ |( hsync_pol << 26 )\ |( clk_pol << 25 )\ |( de_pol << 24 )\ |(1 << 21 )|(1 << 20 )|( dt->vsync_len.typ << 0 ); lcdif->VDCTRL1 = (dt->vfront_porch.typ + dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ); lcdif->VDCTRL2 = (dt->hsync_len.typ << 18 ) | (dt->hfront_porch.typ + dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ); lcdif->VDCTRL3 = (( dt->hback_porch.typ + dt->hsync_len.typ) << 16 ) | (dt->vback_porch.typ + dt->vsync_len.typ); lcdif->VDCTRL4 = (1 <<18 ) | (dt->hactive.typ); lcdif->CUR_BUF = info->fix.smem_start; lcdif->NEXT_BUF = info->fix.smem_start; return 0 ; } static int lcd_clk_init (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); struct fb_device *fb_dev = info->par; fb_dev->clk_pix = devm_clk_get(&pdev->dev, "pix" ); fb_dev->clk_axi = devm_clk_get(&pdev->dev, "axi" ); clk_set_rate(fb_dev->clk_pix, fb_dev->dt->pixelclock.typ); clk_prepare_enable(fb_dev->clk_pix); clk_prepare_enable(fb_dev->clk_axi); return 0 ; } static int myfb_probe (struct platform_device *pdev) { int ret = 0 ; struct fb_info *info ; struct fb_device *fb_dev = NULL ; pr_err("my framebuffer device probe\n" ); info = framebuffer_alloc(sizeof (struct fb_device), &pdev->dev); if (!info) return -ENOMEM; fb_dev = info->par; platform_set_drvdata(pdev, info); lcd_of_read_mode(pdev); lcd_clk_init(pdev); set_fb_info_var(pdev); set_fb_info_fix(pdev); info->fbops = &my_fb_ops; info->pseudo_palette = pseudo_palette; info->flags = FBINFO_DEFAULT; fb_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); fb_dev->lcdif = devm_ioremap_resource(&pdev->dev, fb_dev->res); fb_dev->fb_bbp = 16 ; fb_dev->lcd_bbp = 16 ; lcd_controler_init(pdev); ret = register_framebuffer(info); if (ret < 0 ) { dev_err(&pdev->dev, "failed to register framebuffer\n" ); } lcd_controler_enable(pdev); pr_err("myfb framebuffer device registered\n" ); return 0 ; } static int myfb_remove (struct platform_device *pdev) { struct fb_info *info = platform_get_drvdata(pdev); unregister_framebuffer(info); dma_free_wc(&pdev->dev, PAGE_ALIGN(info->fix.smem_len), info->screen_base, info->fix.smem_start); framebuffer_release(info); printk("myfb framebuffer device unregistered\n" ); return 0 ; } static const struct of_device_id myfb_dt_ids [] = { { .compatible = "mylcd" , }, { } }; MODULE_DEVICE_TABLE(of, myfb_dt_ids); static struct platform_driver myfb_driver = { .probe = myfb_probe, .remove = myfb_remove, .driver = { .name = "myfb" , .owner = THIS_MODULE, .of_match_table = myfb_dt_ids, }, }; module_platform_driver(myfb_driver); MODULE_AUTHOR("baron" ); MODULE_DESCRIPTION("Simple framebuffer driver" ); MODULE_LICENSE("GPL" );