1 引入Framebuffer s3c2440裸机-LCD编程一、LCD硬件原理
s3c2440裸机编程-LDC | Hexo (fuzidage.github.io)
介绍了LDC的基本原理。裸机 LCD 驱动编写流程如下:
初始化 I.MX6U 的 eLCDIF 控制器,屏幕宽(width)、高(height)、hspw、 hbp、hfp、vspw、vbp 和 vfp
等信息。
初始化 LCD 像素时钟。
设置 RGBLCD 显存属性。
应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。
同理linux系统下也是希望应用程序来直接操作一块内存来实现实现在 LCD 上显示字符、图片等信息,Framebuffer就是用来干这件事的。Framebuffer
翻译过来就是帧缓冲,简称 fb
。
作用:把显示设备描述成一个缓冲区,允许应用程序通过帧缓冲定义好的接口访问这些图形设备,从而不用关心具体的硬件细节。
因此需要在底层framebuffer框架
去对接具体的显示设备,显示设备控制器。
2 Framebuffer驱动介绍 2.1 Framebuffer设备节点 当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)
的设备,应用程序通 过访问/dev/fbX
这个设备就可以访问 LCD。
2.2 Framebuffer框架
drivers/video/fbmem.c
:主要任务是创建graphics类
、注册FB的字符设备驱动(主设备号是29)、提供register_framebuffer
接口给具体framebuffer驱动编写着来注册fb设备的。
1 #define FB_MAJOR 29 /* /dev/fb* framebuffers */
2.3 Framebuffer数据结构 2.3.1 fb_info fb_info结构体记录了帧缓冲设备的全部信息,包括设备的设置参数、状态以及操作函数指针,对于每一个帧缓冲设备都必须对应一个fb_info结构体实例。
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 struct fb_info { atomic_t count; int node; int flags; struct mutex lock ; struct mutex mm_lock ; struct fb_var_screeninfo var ; struct fb_fix_screeninfo fix ; struct fb_monspecs monspecs ; struct work_struct queue ; struct fb_pixmap pixmap ; struct fb_pixmap sprite ; struct fb_cmap cmap ; struct list_head modelist ; struct fb_videomode *mode ; #ifdef 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 ; int class_flag; ... char __iomem *screen_base; unsigned long screen_size; void *pseudo_palette; };
2.3.1.1 fb_ops 帧缓冲操作函数集,包含open,release,read,write等操作函数。
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 struct fb_ops { struct module *owner ; int (*fb_open)(struct fb_info *info, int user); int (*fb_release)(struct fb_info *info, int user); ssize_t (*fb_read)(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos); ssize_t (*fb_write)(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos); int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info); int (*fb_set_par)(struct fb_info *info); int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info); int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info); int (*fb_blank)(int blank, struct fb_info *info); int (*fb_pan_display)(struct fb_var_screeninfo *var, 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); int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor); int (*fb_sync)(struct fb_info *info); int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg); int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd, unsigned long arg); int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma); void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps, struct fb_var_screeninfo *var); void (*fb_destroy)(struct fb_info *info); int (*fb_debug_enter)(struct fb_info *info); int (*fb_debug_leave)(struct fb_info *info); };
2.3.1.2 fb_var_screeninfo 记录用户可修改的显示控制器参数 ,包括了屏幕的分辨率和每个像素点的比特数bpp,pixclock等。
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 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 colorspace; __u32 reserved[4 ]; };
2.3.1.2.1 引入可视屏幕和虚拟屏幕
1 2 3 4 5 (1 )可视屏幕:LCD分辨率,这是硬件相关的。比如:LCD屏幕的分辨率是800 x480,那可视屏幕的最大分辨率就是800 x480; (2 )虚拟屏幕:我们在内核中开辟的帧缓冲区的大小。比如:屏幕分辨率是800 x480,但是我们可以将帧缓冲区开辟成1920 x1080, 在刷新屏幕时可以直接将1080 p的图像一次性刷新到帧缓冲区中; (3 )虚拟屏到可视屏的偏移量:虚拟屏大小是超过可视屏幕的大小,偏移量决定了可视屏显示虚拟屏的哪一个部分; (4 )总结:通过改变虚拟屏到可视屏的偏移量,可以将虚拟屏的不同部分图像显示到可视屏中,而不需要每次都刷新帧缓冲区;
2.3.1.3 fb_fix_screeninfo 记录了用户不能修改的显示控制器的参数 ,比如说屏幕缓冲区的物理地址、长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 capabilities; __u16 reserved[2 ]; };
2.3.1.4 fb_bitfield 描述每一像素缓冲区的组织方式,包括域偏移、位域长度和MSB指示。
1 2 3 4 5 6 struct fb_bitfield { __u32 offset; __u32 length; __u32 msb_right; };
2.4 Framebuffer源码分析 2.4.1 编写fb驱动大致流程
构建fb_info结构体
register_framebuffer
注册fb_info
到fb框架
中,驱动框架会自动创建/dev/fbx
设备节点
app通过open、ioctl等函数接口去操作设备节点/dev/fb0
,驱动框架就会调用fb_info实例化中对应的open、ioctl接口去完成具体的硬件操作。
2.4.2 fb子系统注册卸载 如果定义了MODULE
宏就表示要fb子系统单独编译成ko文件,否则用subsys_initcall
编译进内核。入口在drivers\video\fbdev\core\fbmem.c
创建proc条目/proc/fb
注册成字符设备,fb主设备号定义在include\uapi\linux\major.h
#define FB_MAJOR 29 /* /dev/fb* framebuffers */
建立graphics
类。
2.4.2.1 fb注册卸载相关函数 2.4.2.1.1 framebuffer_alloc 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 struct fb_info *framebuffer_alloc (size_t size, struct device *dev) { #define BYTES_PER_LONG (BITS_PER_LONG/8) #define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG)) int fb_info_size = sizeof (struct fb_info); struct fb_info *info ; char *p; if (size) fb_info_size += PADDING; p = kzalloc(fb_info_size + size, GFP_KERNEL); if (!p) return NULL ; info = (struct fb_info *) p; if (size) info->par = p + fb_info_size; info->device = dev; #ifdef CONFIG_FB_BACKLIGHT mutex_init(&info->bl_curve_mutex); #endif return info; #undef PADDING #undef BYTES_PER_LONG }
1 2 3 4 5 6 (1)famebuffer_alloc函数是用来申请一个struct fb_info结构体的,传参的size是设备私有数据的大小; (2)申请sizeof(struct fb_info) + PADDING + size大小的空间分配给fb_info结构体类型的指针info, 加上PADDING 字节是为了后面的设备私有数据保持BYTES_PER_LONG字节对齐; (3)将fb_info结构体后面size大小且BYTES_PER_LONG 字节的设备私有数据地址赋值info->par; (4)将传入的参数dev赋值给info->device,作为父设备; (5)返回创建好的struct fb_into结构体指针info;
2.4.2.1.2 register_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 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 int register_framebuffer (struct fb_info *fb_info) { int i; struct fb_event event ; struct fb_videomode mode ; if (num_registered_fb == FB_MAX) return -ENXIO; if (fb_check_foreignness(fb_info)) return -ENOSYS; num_registered_fb++; for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break ; fb_info->node = i; 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); 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 (!lock_fb_info(fb_info)) return -ENODEV; fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); unlock_fb_info(fb_info); return 0 ; }
2.4.2.1.3 unregister_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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int unregister_framebuffer (struct fb_info *fb_info) { struct fb_event event ; int i, ret = 0 ; i = fb_info->node; if (!registered_fb[i]) { ret = -EINVAL; goto done; } if (!lock_fb_info(fb_info)) return -ENODEV; event.info = fb_info; ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event); unlock_fb_info(fb_info); if (ret) { ret = -EINVAL; goto done; } if (fb_info->pixmap.addr && (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) kfree(fb_info->pixmap.addr); fb_destroy_modelist(&fb_info->modelist); registered_fb[i]=NULL ; num_registered_fb--; fb_cleanup_device(fb_info); device_destroy(fb_class, MKDEV(FB_MAJOR, i)); event.info = fb_info; fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); if (fb_info->fbops->fb_destroy) fb_info->fbops->fb_destroy(fb_info); done: return ret; }
2.4.3 fb_ops分析 fb_ops
中的操作函数属于框架部分,并不和具体的硬件相关,在进行一些处理后最后都是调用struct fb_info
结构体中fb_ops
定义的操作方法;
!
2.4.3.1 fb_open 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 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 ; if (fbidx >= FB_MAX) return -ENODEV; info = registered_fb[fbidx]; if (!info) request_module("fb%d" , fbidx); info = registered_fb[fbidx]; if (!info) return -ENODEV; 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); } out: mutex_unlock(&info->lock); return res; }
2.4.3.2 fb_write 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 static ssize_t fb_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) { unsigned long p = *ppos; struct inode *inode = file->f_path.dentry->d_inode; int fbidx = iminor(inode); struct fb_info *info = registered_fb[fbidx]; u32 *buffer, *src; u32 __iomem *dst; int c, i, 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_write) return info->fbops->fb_write(info, buf, count, ppos); total_size = info->screen_size; if (total_size == 0 ) total_size = info->fix.smem_len; if (p > total_size) return -EFBIG; if (count > total_size) { err = -EFBIG; count = total_size; } if (count + p > total_size) { if (!err) err = -ENOSPC; count = total_size - p; } buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL); if (!buffer) return -ENOMEM; dst = (u32 __iomem *) (info->screen_base + p); if (info->fbops->fb_sync) info->fbops->fb_sync(info); while (count) { c = (count > PAGE_SIZE) ? PAGE_SIZE : count; src = buffer; if (copy_from_user(src, buf, c)) { err = -EFAULT; break ; } for (i = c >> 2 ; i--; ) fb_writel(*src++, dst++); if (c & 3 ) { u8 *src8 = (u8 *) src; u8 __iomem *dst8 = (u8 __iomem *) dst; for (i = c & 3 ; i--; ) fb_writeb(*src8++, dst8++); dst = (u32 __iomem *) dst8; } *ppos += c; buf += c; cnt += c; count -= c; } kfree(buffer); return (cnt) ? cnt : err; }
2.4.3.3 fb_mmap 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 static int fb_mmap (struct file *file, struct vm_area_struct * vma) { int fbidx = iminor(file->f_path.dentry->d_inode); struct fb_info *info = registered_fb[fbidx]; struct fb_ops *fb = info->fbops; unsigned long off; unsigned long start; u32 len; if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) return -EINVAL; off = vma->vm_pgoff << PAGE_SHIFT; if (!fb) return -ENODEV; mutex_lock(&info->mm_lock); if (fb->fb_mmap) { int res; res = fb->fb_mmap(info, vma); mutex_unlock(&info->mm_lock); return res; } start = info->fix.smem_start; len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); if (off >= len) { off -= len; if (info->var.accel_flags) { mutex_unlock(&info->mm_lock); return -EINVAL; } start = info->fix.mmio_start; len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); } mutex_unlock(&info->mm_lock); start &= PAGE_MASK; if ((vma->vm_end - vma->vm_start + off) > len) return -EINVAL; off += start; vma->vm_pgoff = off >> PAGE_SHIFT; vma->vm_flags |= VM_IO | VM_RESERVED; fb_pgprotect(file, vma, off); if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) return -EAGAIN; return 0 ; }
2.4.3.4 fb_ioctl
宏定义
功能说明
FBIOGET_VSCREENINFO
获取屏幕可变参数
FBIOPUT_VSCREENINFO
设置屏幕可变参数
FBIOGET_FSCREENINFO
获取屏幕固定参数
FBIOPUTCMAP
设置颜色表
FBIOGETCMAP
获取颜色表
FBIOPAN_DISPLAY
动视窗显示
FBIO_CURSOR
光标设置,目前不支持
FBIOGET_CON2FBMAP
获取指定帧缓冲控制台对应的帧缓冲设备
FBIOPUT_CON2FBMAP
置指定的帧缓冲控制台对应的帧缓冲设备
FBIOBLANK
显示空白
3 Framebuffer驱动实例
3.1定义fb_info实例
以飞思卡尔nxp的LCD控制器来说,叫做lcdif
。位于drivers/video/fbdev/mxsfb.c
,以像素时钟模式为例:
3.1.0 控制器dts配置 打开imx6ull.dtsi
:
1 2 3 4 5 6 7 8 9 10 lcdif: lcdif@021 c8000 { compatible = "fsl,imx6u l-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" ; };
compatible
匹配,probe
执行。
3.1.1 mxsfb_probe过程分析 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 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; ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0 , dev_name(&pdev->dev), host); if (ret) { dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n" , irq, ret); ret = -ENODEV; goto fb_release; } host->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(host->base)) { dev_err(&pdev->dev, "ioremap failed\n" ); ret = PTR_ERR(host->base); goto fb_release; } host->pdev = pdev; platform_set_drvdata(pdev, host); host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data]; host->clk_pix = devm_clk_get(&host->pdev->dev, "pix" ); if (IS_ERR(host->clk_pix)) { host->clk_pix = NULL ; ret = PTR_ERR(host->clk_pix); goto fb_release; } host->clk_axi = devm_clk_get(&host->pdev->dev, "axi" ); if (IS_ERR(host->clk_axi)) { host->clk_axi = NULL ; ret = PTR_ERR(host->clk_axi); goto fb_release; } host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi" ); if (IS_ERR(host->clk_disp_axi)) { host->clk_disp_axi = NULL ; ret = PTR_ERR(host->clk_disp_axi); goto fb_release; } host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd" ); if (IS_ERR(host->reg_lcd)) host->reg_lcd = NULL ; fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof (u32) * 16 , GFP_KERNEL); if (!fb_info->pseudo_palette) { ret = -ENOMEM; goto fb_release; } INIT_LIST_HEAD(&fb_info->modelist); pm_runtime_enable(&host->pdev->dev); ret = mxsfb_init_fbinfo(host); if (ret != 0 ) goto fb_pm_runtime_disable; mxsfb_dispdrv_init(pdev, fb_info); if (!host->dispdrv) { pinctrl = devm_pinctrl_get_select_default(&pdev->dev); if (IS_ERR(pinctrl)) { ret = PTR_ERR(pinctrl); goto fb_pm_runtime_disable; } } if (!host->enabled) { writel(0 , host->base + LCDC_CTRL); mxsfb_set_par(fb_info); mxsfb_enable_controller(fb_info); pm_runtime_get_sync(&host->pdev->dev); } ret = register_framebuffer(fb_info); if (ret != 0 ) { dev_err(&pdev->dev, "Failed to register framebuffer\n" ); goto fb_destroy; } console_lock(); ret = fb_blank(fb_info, FB_BLANK_UNBLANK); console_unlock(); if (ret < 0 ) { dev_err(&pdev->dev, "Failed to unblank framebuffer\n" ); goto fb_unregister; } dev_info(&pdev->dev, "initialized\n" ); return 0 ; fb_unregister: unregister_framebuffer(fb_info); fb_destroy: if (host->enabled) clk_disable_unprepare(host->clk_pix); fb_destroy_modelist(&fb_info->modelist); fb_pm_runtime_disable: pm_runtime_disable(&host->pdev->dev); devm_kfree(&pdev->dev, fb_info->pseudo_palette); fb_release: framebuffer_release(fb_info); devm_kfree(&pdev->dev, host); return ret; }
host
结构体指针变量,表示LDCIF
控制器,包含Framebuffer设备详细信息,比如时钟
、eLCDIF
控制器寄存器基地址
、fb_info
等。
从dts中提取gpio, irq, res,时钟
等信息。初始化host, fb_info
等结构体。
host->base = devm_ioremap_resource(&pdev->dev, res);
对io内存进行ioremap
, 把eLCDIF
控制器地址映射成虚拟地址。
mxsfb_init_fbinfo
:
设置eLCDIF
控制器具体的fb_ops
1 2 3 4 5 6 7 8 9 10 11 12 13 static struct fb_ops mxsfb_ops = { .owner = THIS_MODULE, .fb_check_var = mxsfb_check_var, .fb_set_par = mxsfb_set_par, .fb_setcolreg = mxsfb_setcolreg, .fb_ioctl = mxsfb_ioctl, .fb_blank = mxsfb_blank, .fb_pan_display = mxsfb_pan_display, .fb_mmap = mxsfb_mmap, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, };
从dts获取LCD 的各个参数信息,然后调用mxsfb_map_videomem
申请framebuffer空间,也就是显存。
fb_videomode_to_var
设置ldc的可变属性
设置控制器寄存器信息
1 2 3 writel(0 , host->base + LCDC_CTRL); mxsfb_set_par(fb_info); mxsfb_enable_controller(fb_info);
调用 register_framebuffer
函数向 Linux 内核注册 fb_info
。
在mxsfb.c
中已经定义了 eLCDIF
控制器各个寄存器相比于基地址的偏移值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #define LCDC_CTRL 0x00 #define LCDC_CTRL1 0x10 #define LCDC_V4_CTRL2 0x20 #define LCDC_V3_TRANSFER_COUNT 0x20 #define LCDC_V4_TRANSFER_COUNT 0x30 #define LCDC_V4_CUR_BUF 0x40 #define LCDC_V4_NEXT_BUF 0x50 #define LCDC_V3_CUR_BUF 0x30 #define LCDC_V3_NEXT_BUF 0x40 #define LCDC_TIMING 0x60 #define LCDC_VDCTRL0 0x70 #define LCDC_VDCTRL1 0x80 #define LCDC_VDCTRL2 0x90 #define LCDC_VDCTRL3 0xa0 #define LCDC_VDCTRL4 0xb0 #define LCDC_DVICTRL0 0xc0 #define LCDC_DVICTRL1 0xd0 #define LCDC_DVICTRL2 0xe0 #define LCDC_DVICTRL3 0xf0 #define LCDC_DVICTRL4 0x100 #define LCDC_V4_DATA 0x180 #define LCDC_V3_DATA 0x1b0 #define LCDC_V4_DEBUG0 0x1d0 #define LCDC_V3_DEBUG0 0x1f0
3.2 LCD屏幕dts描述 3.2.1 屏幕 IO 配置 除了eLCDIF
控制器,对LCD设备也需要进行描述,主要是引脚pinmux。比如imx6ull-alientek-emmc.dts
这块板子对应的LCD屏幕使用引脚如下,iomuxc
节点下有如下节点:
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 pinctrl_lcdif_dat: lcdifdatgrp { fsl,pins = < MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79 >; }; pinctrl_lcdif_ctrl: lcdifctrlgrp { fsl,pins = < MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79 >; }; pinctrl_pwm1: pwm1grp { fsl,pins = < MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 >; };
以及iomixc_snvs下有一个reset节点:
1 2 3 pinctrl_lcdif_dat,为 RGB LCD 的 24 根数据线配置项 pinctrl_lcdif_ctrl,RGB LCD 的 4 根控制线配置项,包括 CLK、 ENABLE、VSYNC 和 HSYNC pinctrl_pwm1,LCD 背光 PWM 引脚配置项
可以看到控制和数据引脚的电器属性默认nxp都帮我们设置成了0x79。
3.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 &lcdif { pinctrl-names = "default" ; pinctrl-0 = <&pinctrl_lcdif_dat &pinctrl_lcdif_ctrl &pinctrl_lcdif_reset>; display = <&display0>; status = "okay" ; display0: display { bits-per-pixel = <16 >; bus-width = <24 >; display-timings { native-mode = <&timing0>; timing0: timing0 { clock-frequency = <9200000 >; hactive = <480 >; vactive = <272 >; hfront-porch = <8 >; hback-porch = <4 >; hsync-len = <41 >; vback-porch = <2 >; vfront-porch = <4 >; vsync-len = <10 >; hsync-active = <0 >; vsync-active = <0 >; de-active = <1 >; pixelclk-active = <0 >; }; }; }; };
display0
子节点,描述 LCD 的参数信息,包括bpp, bus-width
,时序特性,这些参数跟随具体的屏厂屏幕规格走。例如另一款屏幕ATK7016(7 寸 1024*600)
屏幕:可以看到这款屏幕是RGB888
的,bpp是3byte
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 display0: display { bits-per-pixel = <24 >; bus-width = <24 >; display-timings { native-mode = <&timing0>; timing0: timing0 { clock-frequency = <51200000 >; hactive = <1024 >; vactive = <600 >; hfront-porch = <160 >; hback-porch = <140 >; hsync-len = <20 >; vback-porch = <20 >; vfront-porch = <12 >; vsync-len = <3 >; hsync-active = <0 >; vsync-active = <0 >; de-active = <1 >; pixelclk-active = <0 >; }; }; };
3.2.3 背光节点 LCD 背光要用到 PWM1
,因此也要设置 PWM1
节点,如果背光只用简单的gpio
,那么只能控制亮灭。无法控制亮度, 在imx6ull.dtsi
文件中找到pwm1
描述:
1 2 3 4 5 6 7 8 9 pwm1: pwm@02080000 { compatible = "fsl,imx6ul-pwm" , "fsl,imx27-pwm" ; reg = <0x02080000 0x4000 >; interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_PWM1>, <&clks IMX6UL_CLK_PWM1>; clock-names = "ipg" , "per" ; #pwm-cells = <2> ; };
imx6ull 的 PWM 驱动文件为 drivers/pwm/pwm-imx.c
,具体见Linux下PWM子系统 - fuzidage - 博客园 (cnblogs.com)
字符设备驱动-PWM子系统 | Hexo (fuzidage.github.io) 。只要会使用pwm1即可,打开imx6ull-alientek-emmc.dts
这块板子:
往pwm1添加如下内容,设置好pwm1对应的引脚:
1 2 3 4 5 &pwm1 { pinctrl-names = "default" ; pinctrl-0 = <&pinctrl_pwm1>; status = "okay" ; };
也就是MX6UL_PAD_GPIO1_IO08__PWM1_OUT
,将gpio1_8
设成pwm输出。
3.2.3.1 backlight设置 我们还需要一个节点来将 LCD 背光
和 PWM1_OUT
连接起来。这个节点就是 backlight
, backlight
节点描述可以参考 Documentation/devicetree/indings/video/backlight/pwm-backlight.txt
。
1 2 3 4 5 6 7 backlight { compatible = "pwm-backlight" ; pwms = <&pwm1 0 5000000 >; brightness-levels = <0 4 8 16 32 64 128 255 >; default -brightness-level = <6 >; status = "okay" ; };
设置背 8 级背光(0~7)
,分别为 0、4、8、16、32、64、128、255
,对应占空比为 0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%
,如果嫌少的话可以自行添加一 些其他的背光等级值。
设置默认背光等级为 6,也就是 50.19%
的亮度
backlight 节点说明:
1 2 3 4 5 6 7 1. 节点名称要为“backlight” 2. compatible 属性值要为“pwm-backlight”,因此可以通过在 Linux 内核中搜索 “ pwm-backlight ” 来查找PWM背光控制驱动程序 , 文件为 drivers/video/backlight/pwm_bl.c 3. pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1, pwm 频率设置为 200Hz 4. brightness-levels 属性描述亮度级别,范围为 0~255,0 表示 PWM 占空比为 0%,也就 是亮度最低, 255 表示 100%占空比,也就是亮度最高 5. default-brightness-level 属性为默认亮度级别
4 LCD测试 4.1 使能 Linux logo Linux 内核启动的时候可以选择显示小企鹅 logo,一般默认关闭。
1 2 3 4 5 6 -> Device Drivers -> Graphics support -> Bootup logo (LOGO [=y]) -> Standard black and white Linux logo -> Standard 16-color Linux logo -> Standard 224-color Linux logo
三个选项分别对应黑白、16 位、24 位色彩格式的 logo,我们把这三个都选 中,都编译进 Linux 内核里面。
4.2 LCD 作为终端控制台console
u-boot中bootargs设置
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250: /home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0: off'
第一次设置 console=tty1
, 也就是设置 LCD 屏幕为控制台,第二遍又设置 console=ttymxc0,115200
,也就是设置串口也作为控制台。大家重启开发板就会发 现 LCD 和串口都会显示 Linux 启动 log 信息。
修改/etc/inittab
添加下面这行,
tty1::askfirst:-/bin/sh
修改完成以后保存/etc/inittab
并退出,然后重启开发板,重启以后开发板 LCD 屏幕最后一 行会显示下面一行语句:
Please press Enter to activate this console
为什么请参考: linux内核-4.rootfs构建移植
Linux内核-rootfs构建移植 | Hexo (fuzidage.github.io)
大家也可以接上一个 USB 键盘,Linux 内核默认已经使能了 USB 键盘驱动 了,因此可以直接使用 USB 键盘Enter键。
当然也可以利用input子系统来用一个gpio按键做成Enter键。见: linux驱动-17-input子系统
字符设备驱动-input子系统 | Hexo (fuzidage.github.io)
4.3 LCD 背光调节命令 前面背光设备树节点设置了 8 个等级的背光调节,可以设置为 0~7
,我 们可以通过设置背光等级来实现 LCD 背光亮度的调节:
/sys/devices/platform/backlight/backlight/backlight
echo 7 > brightness
设置亮度为100%
, echo 0 >brightness
设置成熄灭背光。
4.4 LCD屏幕自动熄灭 4.1.1 按键盘唤醒 默认情况下 10 分钟以后 LCD 就会熄屏,这个并不是代码有问题,而是 Linux 内核设置的。按下回车键就会唤醒屏幕。
4.1.2 关闭10分钟自动熄屏
drivers/tty/vt/vt.c
中, blankinterval
变量控制着 LCD 关闭时间,默认是 10*60
,也就是 10 分钟。将 blankinterval
的值改为 0 即可关闭 10 分钟熄屏的功能。
1 2 181 static int blankinterval = 10 *60 ;182 core_param(consoleblank, blankinterval, int , 0444 );
echo -e '\033[9;0]' > /dev/tty0
4.5 测试代码 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 #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <linux/fb.h> #include <sys/mman.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/ioctl.h> #define PAUSE() \ do { \ printf("---------------press Enter key to continue!---------------\n" ); \ getchar(); \ } while (0) #if 1 #define RED 0xFFFF0000 #define GREEN 0xFF00FF00 #define BLUE 0xFF0000FF #define YELLOW 0xFFFFFF00 #define WHITE 0xFFFFFFFF #define BLACK 0xFF000000 void fill_color (uint32_t *fb_addr, uint32_t bit_map, int psize) { int i; for (i=0 ; i<psize; i++) { *fb_addr = bit_map; fb_addr++; } } #else #define RED 0xFC00 #define GREEN 0x83E0 #define BLUE 0x801F #define YELLOW 0xFFE0 #define WHITE 0xFFFF #define BLACK 0x8000 void fill_color (short *fb_addr, short bit_map, int psize) { int i; for (i=0 ; i<psize; i++) { *fb_addr = bit_map; fb_addr++; } } #endif void _fb_get_info(int fp, struct fb_fix_screeninfo *finfo, struct fb_var_screeninfo *vinfo){ long screensize=0 ; if (ioctl(fp, FBIOGET_FSCREENINFO, finfo)){ printf ("Error reading fixed information/n" ); exit (2 ); } if (ioctl(fp, FBIOGET_VSCREENINFO, vinfo)){ printf ("Error reading variable information/n" ); exit (3 ); } screensize = finfo->line_length * vinfo->yres; printf ("The ID=%s\n" , finfo->id); printf ("The phy mem = 0x%x, total size = %d(byte)\n" , finfo->smem_start, finfo->smem_len); printf ("line length = %d(byte)\n" , finfo->line_length); printf ("xres = %d, yres = %d, bits_per_pixel = %d\n" , vinfo->xres, vinfo->yres, vinfo->bits_per_pixel); printf ("xresv = %d, yresv = %d\n" , vinfo->xres_virtual, vinfo->yres_virtual); printf ("vinfo.xoffset = %d, vinfo.yoffset = %d\n" , vinfo->xoffset, vinfo->yoffset); printf ("vinfo.vmode is :%d\n" , vinfo->vmode); printf ("finfo.ypanstep is :%d\n" , finfo->ypanstep); printf ("vinfo.red.offset=0x%x\n" , vinfo->red.offset); printf ("vinfo.red.length=0x%x\n" , vinfo->red.length); printf ("vinfo.green.offset=0x%x\n" , vinfo->green.offset); printf ("vinfo.green.length=0x%x\n" , vinfo->green.length); printf ("vinfo.blue.offset=0x%x\n" , vinfo->blue.offset); printf ("vinfo.blue.length=0x%x\n" , vinfo->blue.length); printf ("vinfo.transp.offset=0x%x\n" , vinfo->transp.offset); printf ("vinfo.transp.length=0x%x\n" , vinfo->transp.length); printf ("Expected screensize = %d(byte), using %d frame\n" , screensize, finfo->smem_len/screensize); } int main () { int fp=0 ; struct fb_var_screeninfo vinfo ; struct fb_fix_screeninfo finfo ; void *fbp = NULL ; char *test_fbp = NULL ; int x = 0 , y = 0 ; long location = 0 ; int i; int num = 2 ; int pix_size=0 ; fp = open("/dev/fb0" , O_RDWR); if (fp < 0 ) { printf ("Error : Can not open framebuffer device/n" ); exit (1 ); } printf ("-- Default fb info --\n" ); _fb_get_info(fp, &finfo, &vinfo); #if 1 vinfo.xres = 720 ; vinfo.yres = 1280 ; if (ioctl(fp, FBIOPUT_VSCREENINFO, &vinfo)){ printf ("Error putting variable information/n" ); exit (3 ); } printf ("-- Updated fb info --\n" ); _fb_get_info(fp, &finfo, &vinfo); #endif fbp = mmap(0 , finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fp, 0 ); if (fbp == MAP_FAILED){ printf ("Error: failed to map framebuffer device to memory.\n" ); exit (4 ); } printf ("Get virt mem = %p\n" , fbp); pix_size = vinfo.xres * vinfo.yres; vinfo.xoffset = 0 ; vinfo.yoffset = 0 ; while (num--) { printf ("\ndrawing YELLOW......\n" ); fill_color(fbp, YELLOW, pix_size); sleep(3 ); printf ("\ndrawing BLUE......\n" ); fill_color(fbp, BLUE, pix_size); sleep(3 ); printf ("\ndrawing RED......\n" ); fill_color(fbp, RED, pix_size); sleep(3 ); PAUSE(); } #if 1 x = 10 ; y = 10 ; location = x * (vinfo.bits_per_pixel / 8 ) + y * finfo.line_length; test_fbp = fbp + location; printf ("draw line.......\n" ); for (i = 0 ; i < (vinfo.xres - x); i++) *test_fbp++ = i+30 ; PAUSE(); #endif munmap(fbp, finfo.smem_len); close (fp); return 0 ; }