注:文章都是通过阅读各位前辈总结的资料、Android 7.1.2 && Linux(kernel 3.18)Qualcomm平台源码、加上自己的思考分析总结出来的,其中难免有理解不对的地方,欢迎大家批评指正。文章为个人学习、研究、欣赏之用,图文内容整理自互联网,如有侵权,请联系删除,禁止转载(©Qualcomm Technologies, Inc. 版权所有),谢谢。
【特别感谢 - 高通Android平台-应用空间操作framebuffer dump LCD总结】 【特别感谢 - linux qcom LCD framwork】 【特别感谢 - msm8610 lcd driver code analysis】
Google Pixel、Pixel XL 内核代码(文章基于 Kernel-3.18): Kernel source for Pixel and Pixel XL - GitHub
AOSP 源码(文章基于 Android 7.1.2): Android 系统全套源代码分享 (更新到 8.1.0_r1)
🌀🌀:专注于Linux && Android Multimedia(Camera、Video、Audio、Display)系统分析与研究
【Android Display System 系统分析系列】: 【Android Display System(1):Android 7.1.2 (Android N) Android Graphics 系统 分析】 【Android Display System(2):Android Display System 系统分析之Android EGL && OpenGL】 【Android Display System(3):Android Display System 系统分析之HardwareRenderer.draw()绘制流程分析】 【Android Display System(4):Android Display System 系统分析之Gralloc && HWComposer模块分析】 【Android Display System(5):Android Display System 系统分析之Display Driver Architecture】
路径: kernel\msm-3.18\drivers\video\msm\mdss
MDSS driver software block diagram
mdss_fb → Top-level IOCTL/native framebufferinterface
mdss_mdp.c → MDP resources(clocks/irq/bus-bw/power)
mdss_mdp_overlay → Overlay/DMA top-levelAPI
mdss_mdp_ctl → Controls the hardware abstraction to club the (LM + DSPP + Ping-pong + interface)
mdss_mdp_pipe → SRC pipe related handling
mdss_mdp_intf_cmd/mdss_mdp_intf_video/mdss_mdp_intf_writeback → MDP panel interface relatedhandling
mdss_mdp_pp → Postprocessing related implementation
mdss_mdp_rotator → Rotator APIs (overlay_set/overlay_playinterface)
mdss_mdp_pp.c → Postprocessing relatedmaterial
注: 首先说明,由于博主不是kernel开发方向的,可能理解不够透彻,还请看官见谅,主要是为了理解Kernel Display原理。为了加深对显示屏工作原理的理解,首先先看两个操作LCD显示屏的例子 绪论(总体架构图):
(一)、直接操作framebuffer显示图像 1.1、直接操作framebuffer显示图像 1.1.1、源代码 panel_test.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 99 100 101 102 #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <linux/fb.h> #include <sys/mman.h> #include <stdlib.h> #include "yellow_face.zif" int main () { int fbfd = 0 ; struct fb_var_screeninfo vinfo ; struct fb_fix_screeninfo finfo ; struct fb_cmap cmapinfo ; long int screensize = 0 ; char *fbp = 0 ; int x = 0 , y = 0 ; long int location = 0 ; int b,g,r; fbfd = open("/dev/graphics/fb0" , O_RDWR,0 ); if (fbfd < 0 ) { printf ("Error: cannot open framebuffer device.%x\n" ,fbfd); exit (1 ); } printf ("The framebuffer device was opened successfully.\n" ); if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo)) { printf ("Error reading fixed information.\n" ); exit (2 ); } printf ("\ntype:0x%x\n" , finfo.type ); printf ("visual:%d\n" , finfo.visual ); printf ("line_length:%d\n" , finfo.line_length ); printf ("\nsmem_start:0x%lx,smem_len:%u\n" , finfo.smem_start, finfo.smem_len ); printf ("mmio_start:0x%lx ,mmio_len:%u\n" , finfo.mmio_start, finfo.mmio_len ); if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo)) { printf ("Error reading variable information.\n" ); exit (3 ); } printf ("%dx%d, %dbpp,xres_virtual=%d,yres_virtual=%dvinfo.xoffset=%d,vinfo.yoffset=%d\n" , vinfo.xres, vinfo.yres, vinfo.bits_per_pixel,vinfo.xres_virtual,vinfo.yres_virtual,vinfo.xoffset,vinfo.yoffset); screensize = finfo.line_length * vinfo.yres_virtual; fbp = (char *)mmap(0 , screensize, PROT_READ | PROT_WRITE, MAP_SHARED,fbfd, 0 ); if ((int )fbp == -1 ) { printf ("Error: failed to map framebuffer device to memory.\n" ); exit (4 ); } printf ("The framebuffer device was mapped to memory successfully.\n" ); b = 10 ; g = 100 ; r = 100 ; for ( y = 0 ; y < 340 ; y++ ) for ( x = 0 ; x < 420 ; x++ ) { location = (x+100 ) * (vinfo.bits_per_pixel/8 ) + (y+100 ) * finfo.line_length; if ( vinfo.bits_per_pixel == 32 ) { *(fbp + location) = b; *(fbp + location + 1 ) = g; *(fbp + location + 2 ) = r; *(fbp + location + 3 ) = 0 ; } } unsigned char *pTemp = (unsigned char *)fbp; int i, j; x = 400 ; y = 400 ; int right = 700 ; int bottom = 1000 ; for (i=y; i< bottom; i++) { for (j=x; j<right; j++) { unsigned short data = yellow_face_data[(((i-y) % 128 ) * 128 ) + ((j-x) %128 )]; pTemp[i*finfo.line_length + (j*4 ) + 2 ] = (unsigned char )((data & 0xF800 ) >> 11 << 3 ); pTemp[i*finfo.line_length + (j*4 ) + 1 ] = (unsigned char )((data & 0x7E0 ) >> 5 << 2 ); pTemp[i*finfo.line_length + (j*4 ) + 0 ] = (unsigned char )((data & 0x1F ) << 3 ); } } if (ioctl(fbfd, FBIOPAN_DISPLAY, &vinfo)) { printf ("Error FBIOPAN_DISPLAY information.\n" ); exit (5 ); } sleep(10 ); munmap(fbp,finfo.smem_len); close(fbfd); return 0 ; }
Android.mk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # Copyright 2006 -2014 The Android Open Source Project LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= panel_test.c LOCAL_SHARED_LIBRARIES := $(common_libs) libqdutils libdl liblog libbase libcutils LOCAL_C_INCLUDES := $(common_includes) $(kernel_includes) LOCAL_ADDITIONAL_DEPENDENCIES := $(common_deps) $(kernel_deps) LOCAL_MODULE := panel_test LOCAL_CFLAGS := -Werror include $(BUILD_EXECUTABLE) include $(call first-makefiles-under,$(LOCAL_PATH))
yellow_face.zif yellow_face.zif
1.1.2、编译测试 编译会生成panel_test,然后进行测试。
注意事项: 1、adb shell stop 杀掉surfaceflinger 之后在测试; 2、设置背光 echo 255 > /sys/class/leds/lcd-backlight/brightness
1、连接adb 2、adb push panel_test system/bin 3、进入adb shell 4、stop 5、echo 255 > /sys/class/leds/lcd-backlight/brightness 6、system/bin/panel_test
1.1.3、显示效果
1.1.4、视频(加深理解、自☯备☯梯☯子) Android Frame Buffer and Screen Shots Tutorial Mplayer on Android Through Chroot and Frame Buffer
1.1.5、驱动总体概览图
(二)、FrameBuffer驱动程序分析 FrameBuffer通常作为LCD控制器或者其他显示设备的驱动,FrameBuffer驱动是一个字符设备,设备节点是/dev/fbX(Android 设备为/dev/graphics/fb0),主设备号为29,次设备号递增,用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。Framebuffer设备为上层应用程序提供系统调用,也为下一层的特定硬件驱动提供接口;那些底层硬件驱动需要用到这儿的接口来向系统内核注册它们自己。
Linux中的PCI设备可以将其控制寄存器映射到物理内存空间,而后,对这些控制寄存器的访问变成了对理内存的访问,因此,这些寄存器又被称为”memio”。一旦被映射到物理内存,Linux的普通进程就可以通过mmap将这些内存I/O映射到进程地址空间,这样就可以直接访问这些寄存器了。
FrameBuffer设备属于字符设备,采用了文件层—驱动层的接口方式,Linux为帧缓冲设备定义了驱动层的接口fb_info结构,在文件层上,用户调用file_operations的函数操作,间接调用fb_info中的fb_ops函数集来操作硬件。
2.1、 Framebuffer数据结构 2.1.1、 fb_info fb_info是Linux为帧缓冲设备定义的驱动层接口。它不仅包含了底层函数,而且还有记录设备状态的数据。每个帧缓冲设备都与一个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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 [->kernel\include\linux\fb.h] 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 ; struct file *file ; #ifdef CONFIG_FB_DEFERRED_IO struct delayed_work deferred_work ; struct fb_deferred_io *fbdefio ; #endif struct fb_ops *fbops ; struct device *device ; struct device *dev ; int class_flag; #ifdef CONFIG_FB_TILEBLITTING struct fb_tile_ops *tileops ; #endif char __iomem *screen_base; unsigned long screen_size; void *pseudo_palette; #define FBINFO_STATE_RUNNING 0 #define FBINFO_STATE_SUSPENDED 1 u32 state; void *fbcon_par; void *par; struct apertures_struct { unsigned int count; struct aperture { resource_size_t base; resource_size_t size; } ranges[0 ]; } *apertures; bool skip_vt_switch; };
2.1.2、fb_var_screeninfo
fb_var_screeninfo:用于记录用户可修改的显示控制器参数,包括屏幕分辨率、每个像素点的比特数等
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 [->\include\uapi\linux\fb.h] 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 ]; };
2.1.3、fb_fix_screeninfo fb_fix_screeninfo:记录了用户不能修改的显示控制器的参数,这些参数是在驱动初始化时设置的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [->\include\uapi\linux\fb.h] 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 ]; };
fb_ops是提供给底层设备驱动的一个接口。当我们编写一个FrameBuffer的时候,就要依照Linux FrameBuffer编程的套路,填写fb_ops结构体。
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 [->\include\linux\fb.h] 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); void (*fb_rotate)(struct fb_info *info, int angle); int (*fb_sync)(struct fb_info *info); int (*fb_ioctl)(struct fb_info *info, unsigned int cmd, unsigned long arg); int (*fb_ioctl_v2)(struct fb_info *info, unsigned int cmd, unsigned long arg, struct file *file); int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd, unsigned long arg); int (*fb_compat_ioctl_v2)(struct fb_info *info, unsigned cmd, unsigned long arg, struct file *file); 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.2、Framebuffer驱动注册过程 在系统启动时,内核调用所有注册驱动程序的驱动程序初始化函数。 为了帧缓冲区驱动程序,调用mdss_fb_init。 mdss_fb_init注册mdss_fb_driver。驱动在mdss_fb.c文件中注册。
1 2 3 4 5 6 7 8 9 10 11 12 13 [-\drivers\video\msm\mdss\mdss_fb.c] static struct platform_driver mdss_fb_driver = { .probe = mdss_fb_probe, .remove = mdss_fb_remove, .suspend = mdss_fb_suspend, .resume = mdss_fb_resume, .shutdown = mdss_fb_shutdown, .driver = { .name = "mdss_fb" , .of_match_table = mdss_fb_dt_match, .pm = &mdss_fb_pm_ops, }, };
在调用init之后,内核调用每个平台驱动程序的探测函数。 在调用mdss_fb_probe时,函数执行资源分配并调用mdss_fb_register。 可以有多个帧缓冲区(fb)设备(节点)。 该驱动程序通过调用mdss_fb_register来注册各个fb设备,后者又调用register_framebuffer。 HDMI和主显示器是各个fb设备的例子。 以下操作已注册:
首先看一下mdss_fb_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 [->\drivers\video\msm\mdss\mdss_fb.c] static int mdss_fb_probe (struct platform_device *pdev) { struct msm_fb_data_type *mfd = NULL ; struct mdss_panel_data *pdata ; struct fb_info *fbi ; int rc; pdata = dev_get_platdata(&pdev->dev); fbi = framebuffer_alloc(sizeof (struct msm_fb_data_type), NULL ); mfd = (struct msm_fb_data_type *)fbi->par; mfd->key = MFD_KEY; mfd->fbi = fbi; mfd->panel_info = &pdata->panel_info; mfd->panel.type = pdata->panel_info.type; mfd->panel.id = mfd->index; mfd->fb_page = MDSS_FB_NUM; mfd->index = fbi_list_index; mfd->mdp_fb_page_protection = MDP_FB_PAGE_PROTECTION_WRITECOMBINE; mfd->ext_ad_ctrl = -1 ; ...... platform_set_drvdata(pdev, mfd); rc = mdss_fb_register(mfd); ...... return rc; }
首先调用framebuffer_alloc()函数返回一个fb_info 结构体,然后调用mdss_fb_register(mfd)
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 [->\drivers\video\msm\mdss\mdss_fb.c] static int mdss_fb_register (struct msm_fb_data_type *mfd) { int ret = -ENODEV; int bpp; char panel_name[20 ]; struct mdss_panel_info *panel_info = mfd ->panel_info ; struct fb_info *fbi = mfd ->fbi ; struct fb_fix_screeninfo *fix ; struct fb_var_screeninfo *var ; int *id; fix = &fbi->fix; var = &fbi->var; fix->type_aux = 0 ; fix->visual = FB_VISUAL_TRUECOLOR; fix->ywrapstep = 0 ; fix->mmio_start = 0 ; fix->mmio_len = 0 ; fix->accel = FB_ACCEL_NONE; var->xoffset = 0 , var->yoffset = 0 , var->grayscale = 0 , var->nonstd = 0 , var->activate = FB_ACTIVATE_VBL, var->height = -1 , var->width = -1 , var->accel_flags = 0 , var->sync = 0 , var->rotate = 0 , mfd->op_enable = false ; switch (mfd->fb_imgType) { case MDP_RGB_565: ...... var->transp.offset = 0 ; var->transp.length = 0 ; bpp = 2 ; break ; case MDP_RGB_888: ...... var->transp.offset = 0 ; var->transp.length = 0 ; bpp = 3 ; break ; case MDP_ARGB_8888: ...... var->transp.offset = 0 ; var->transp.length = 8 ; bpp = 4 ; break ; case MDP_RGBA_8888: ...... var->transp.offset = 24 ; var->transp.length = 8 ; bpp = 4 ; break ; case MDP_YCRYCB_H2V1: ...... var->transp.offset = 0 ; var->transp.length = 0 ; bpp = 2 ; break ; default : return ret; } mdss_panelinfo_to_fb_var(panel_info, var); fix->type = panel_info->is_3d_panel; if (mfd->mdp.fb_stride) fix->line_length = mfd->mdp.fb_stride(mfd->index, var->xres, bpp); else fix->line_length = var->xres * bpp; var->xres_virtual = var->xres; var->yres_virtual = panel_info->yres * mfd->fb_page; var->bits_per_pixel = bpp * 8 ; fix->smem_len = PAGE_ALIGN(fix->line_length * var->yres) * mfd->fb_page; id = (int *)&mfd->panel; snprintf (fix->id, sizeof (fix->id), "mdssfb_%x" , (u32) *id); fbi->fbops = &mdss_fb_ops; fbi->flags = FBINFO_FLAG_DEFAULT; fbi->pseudo_palette = mdss_fb_pseudo_palette; mfd->ref_cnt = 0 ; mfd->panel_power_state = MDSS_PANEL_POWER_OFF; mfd->dcm_state = DCM_UNINIT; if (mdss_fb_alloc_fbmem(mfd)) pr_warn("unable to allocate fb memory in fb register\n" ); ...... ret = fb_alloc_cmap(&fbi->cmap, 256 , 0 ); if (ret) pr_err("fb_alloc_cmap() failed!\n" ); if (register_framebuffer(fbi) < 0 ) { fb_dealloc_cmap(&fbi->cmap); mfd->op_enable = false ; return -EPERM; } snprintf (panel_name, ARRAY_SIZE(panel_name), "mdss_panel_fb%d" , mfd->index); mdss_panel_debugfs_init(panel_info, panel_name); pr_info("FrameBuffer[%d] %dx%d registered successfully!\n" , mfd->index, fbi->var.xres, fbi->var.yres); return 0 ; }
任何一个特定硬件Framebuffer驱动在初始化时都必须向fbmem.c注册,FrameBuffer模块提供了驱动注册接口函数register_framebuffer:
1 2 3 4 5 6 7 8 9 10 11 12 [->\drivers\video\fbdev\core\fbmem.c] 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; }
参数fb_info描述特定硬件的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 [->\drivers\video\fbdev\core\fbmem.c] static int do_register_framebuffer (struct fb_info *fb_info) { int i; struct fb_event event ; struct fb_videomode mode ; if (fb_check_foreignness(fb_info)) return -ENOSYS; do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, fb_is_primary_device(fb_info)); 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); 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 ; }
注册过程就是将指定的设备驱动信息fb_info存放到registered_fb数组中。因此在注册具体的fb_info时,首先要构造一个fb_info数据结构,并初始化该数据结构,该结构用于描述一个特定的FrameBuffer驱动。
(三)、MDP driver MDP也被注册为平台驱动程序。 mdp3_driver_init执行驱动程序init。
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 [->\drivers\video\msm\mdss\mdp3.c] static struct platform_driver mdp3_driver = { .probe = mdp3_probe, .remove = mdp3_remove, .suspend = mdp3_suspend, .resume = mdp3_resume, .shutdown = NULL , .driver = { .name = "mdp3" , .of_match_table = mdp3_dt_match, .pm = &mdp3_pm_ops, }, }; static int __init mdp3_driver_init (void ) { int ret; ret = platform_driver_register(&mdp3_driver); if (ret) { pr_err("register mdp3 driver failed!\n" ); return ret; } return 0 ; } static int mdp3_probe (struct platform_device *pdev) { int rc; static struct msm_mdp_interface mdp3_interface = { .init_fnc = mdp3_init, .fb_mem_get_iommu_domain = mdp3_fb_mem_get_iommu_domain, .panel_register_done = mdp3_panel_register_done, .fb_stride = mdp3_fb_stride, .check_dsi_status = mdp3_check_dsi_ctrl_status, }; struct mdp3_intr_cb underrun_cb = { .cb = mdp3_dma_underrun_intr_handler, .data = NULL , }; pr_debug("%s: START\n" , __func__); if (!pdev->dev.of_node) { pr_err("MDP driver only supports device tree probe\n" ); return -ENOTSUPP; } if (mdp3_res) { pr_err("MDP already initialized\n" ); return -EINVAL; } mdp3_res = devm_kzalloc(&pdev->dev, sizeof (struct mdp3_hw_resource), GFP_KERNEL); if (mdp3_res == NULL ) return -ENOMEM; pdev->id = 0 ; mdp3_res->pdev = pdev; mutex_init(&mdp3_res->res_mutex); spin_lock_init(&mdp3_res->irq_lock); platform_set_drvdata(pdev, mdp3_res); atomic_set(&mdp3_res->active_intf_cnt, 0 ); mutex_init(&mdp3_res->reg_bus_lock); INIT_LIST_HEAD(&mdp3_res->reg_bus_clist); mdp3_res->mdss_util = mdss_get_util_intf(); if (mdp3_res->mdss_util == NULL ) { pr_err("Failed to get mdss utility functions\n" ); rc = -ENODEV; goto get_util_fail; } mdp3_res->mdss_util->get_iommu_domain = mdp3_get_iommu_domain; mdp3_res->mdss_util->iommu_attached = is_mdss_iommu_attached; mdp3_res->mdss_util->iommu_ctrl = mdp3_iommu_ctrl; mdp3_res->mdss_util->bus_scale_set_quota = mdp3_bus_scale_set_quota; mdp3_res->mdss_util->panel_intf_type = mdp3_panel_intf_type; mdp3_res->mdss_util->dyn_clk_gating_ctrl = mdp3_dynamic_clock_gating_ctrl; mdp3_res->mdss_util->panel_intf_type = mdp3_panel_intf_type; mdp3_res->mdss_util->panel_intf_status = mdp3_panel_get_intf_status; rc = mdp3_parse_dt(pdev); if (rc) goto probe_done; rc = mdp3_res_init(); if (rc) { pr_err("unable to initialize mdp3 resources\n" ); goto probe_done; } mdp3_res->fs_ena = false ; mdp3_res->fs = devm_regulator_get(&pdev->dev, "vdd" ); if (IS_ERR_OR_NULL(mdp3_res->fs)) { pr_err("unable to get mdss gdsc regulator\n" ); return -EINVAL; } rc = mdp3_debug_init(pdev); if (rc) { pr_err("unable to initialize mdp debugging\n" ); goto probe_done; } pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT_MS); if (mdp3_res->idle_pc_enabled) { pr_debug("%s: Enabling autosuspend\n" , __func__); pm_runtime_use_autosuspend(&pdev->dev); } pm_runtime_set_suspended(&pdev->dev); pm_runtime_enable(&pdev->dev); if (!pm_runtime_enabled(&pdev->dev)) { rc = mdp3_footswitch_ctrl(1 ); if (rc) { pr_err("unable to turn on FS\n" ); goto probe_done; } } rc = mdp3_check_version(); if (rc) { pr_err("mdp3 check version failed\n" ); goto probe_done; } rc = mdp3_register_sysfs(pdev); if (rc) pr_err("unable to register mdp sysfs nodes\n" ); rc = mdss_fb_register_mdp_instance(&mdp3_interface); if (rc) pr_err("unable to register mdp instance\n" ); rc = mdp3_set_intr_callback(MDP3_INTR_LCDC_UNDERFLOW, &underrun_cb); if (rc) pr_err("unable to configure interrupt callback\n" ); rc = mdss_smmu_init(mdss_res, &pdev->dev); if (rc) pr_err("mdss smmu init failed\n" ); mdp3_res->mdss_util->mdp_probe_done = true ; pr_debug("%s: END\n" , __func__); return rc; }
内核调用MDP探测函数mdp3_probe。 在探测器上,驱动程序从设备树中获取面板信息,并通过调用mdp3_parse_dt来解析信息。 在mdp3_ctrl_on期间,MDP驱动程序调用mdp3_ctrl_res_req_clk并请求MDP和Vsync时钟。 在mdp3_ctrl_off期间,驱动程序请求关闭MDP和Vsync时钟。
(四)、DSI controller driver (lcd驱动 dsi) msm_dsi_v2_driver_init执行驱动程序初始化。 msm_dsi_v2_driver_init调用 msm_dsi_v2_register_driver注册驱动程序。 总体时序图:
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 [->\drivers\video\msm\mdss\dsi_host_v2.c] static struct platform_driver msm_dsi_v2_driver = { .probe = msm_dsi_probe, .remove = msm_dsi_remove, .shutdown = NULL , .driver = { .name = "msm_dsi_v2" , .of_match_table = msm_dsi_v2_dt_match, }, }; static int msm_dsi_v2_register_driver (void ) { return platform_driver_register(&msm_dsi_v2_driver); } static int msm_dsi_probe (struct platform_device *pdev) { struct dsi_interface intf ; char panel_cfg[MDSS_MAX_PANEL_LEN]; struct mdss_dsi_ctrl_pdata *ctrl_pdata = NULL ; int rc = 0 ; struct device_node *dsi_pan_node = NULL ; bool cmd_cfg_cont_splash = false ; struct resource *mdss_dsi_mres ; int i; pr_debug("%s\n" , __func__); rc = msm_dsi_init(); pdev->id = 0 ; ctrl_pdata = platform_get_drvdata(pdev); if (!ctrl_pdata) { ctrl_pdata = devm_kzalloc(&pdev->dev, sizeof (struct mdss_dsi_ctrl_pdata), GFP_KERNEL); platform_set_drvdata(pdev, ctrl_pdata); } ctrl_pdata->mdss_util = mdss_get_util_intf(); if (mdp3_res->mdss_util == NULL ) { pr_err("Failed to get mdss utility functions\n" ); return -ENODEV; } mdss_dsi_mres = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); if (!mdss_dsi_mres) { } else { dsi_host_private->dsi_reg_size = resource_size(mdss_dsi_mres); dsi_host_private->dsi_base = ioremap(mdss_dsi_mres->start, dsi_host_private->dsi_reg_size); } mdss_dsi_mres = platform_get_resource(pdev, IORESOURCE_IRQ, 0 ); rc = of_platform_populate(pdev->dev.of_node, NULL , NULL , &pdev->dev); rc = dsi_get_panel_cfg(panel_cfg); dsi_pan_node = dsi_find_panel_of_node(pdev, panel_cfg); cmd_cfg_cont_splash = mdp3_panel_get_boot_cfg() ? true : false ; rc = mdss_dsi_panel_init(dsi_pan_node, ctrl_pdata, cmd_cfg_cont_splash); rc = dsi_ctrl_config_init(pdev, ctrl_pdata); msm_dsi_parse_lane_swap(pdev->dev.of_node, &(ctrl_pdata->dlane_swap)); for (i = 0 ; i < DSI_MAX_PM; i++) { rc = msm_dsi_io_init(pdev, &(ctrl_pdata->power_data[i])); } pr_debug("%s: Dsi Ctrl->0 initialized\n" , __func__); dsi_host_private->dis_dev = pdev->dev; intf.on = msm_dsi_on; intf.off = msm_dsi_off; intf.cont_on = msm_dsi_cont_on; intf.clk_ctrl = msm_dsi_clk_ctrl; intf.op_mode_config = msm_dsi_op_mode_config; intf.index = 0 ; intf.private = NULL ; dsi_register_interface(&intf); msm_dsi_debug_init(); msm_dsi_ctrl_init(ctrl_pdata); rc = msm_dsi_irq_init(&pdev->dev, mdss_dsi_mres->start, ctrl_pdata); rc = dsi_panel_device_register_v2(pdev, ctrl_pdata); pr_debug("%s success\n" , __func__); return 0 ; }
内核调用msm_dsi_probe。 面板被检测到。 msm_dsi_probe调用mdss_dsi_panel_init函数。 mdss_dsi_panel_init调用mdss_panel_parse_dt来获取面板参数。 MDP驱动程序使用该事件与DSI驱动程序进行通信。 DSI驱动程序具有mdss_dsi_event_handler,这是MDP核心事件的回调处理程序。 mdss_panel.h定义了MDP核心事件。
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 [->\drivers\video\msm\mdss\mdss_panel.h] enum mdss_intf_events { MDSS_EVENT_RESET = 1 , MDSS_EVENT_LINK_READY, MDSS_EVENT_UNBLANK, MDSS_EVENT_PANEL_ON, MDSS_EVENT_POST_PANEL_ON, MDSS_EVENT_BLANK, MDSS_EVENT_PANEL_OFF, MDSS_EVENT_CLOSE, MDSS_EVENT_SUSPEND, MDSS_EVENT_RESUME, MDSS_EVENT_CHECK_PARAMS, MDSS_EVENT_CONT_SPLASH_BEGIN, MDSS_EVENT_CONT_SPLASH_FINISH, MDSS_EVENT_PANEL_UPDATE_FPS, MDSS_EVENT_FB_REGISTERED, MDSS_EVENT_PANEL_CLK_CTRL, MDSS_EVENT_DSI_CMDLIST_KOFF, MDSS_EVENT_ENABLE_PARTIAL_ROI, MDSS_EVENT_DSC_PPS_SEND, MDSS_EVENT_DSI_STREAM_SIZE, MDSS_EVENT_DSI_UPDATE_PANEL_DATA, MDSS_EVENT_REGISTER_RECOVERY_HANDLER, MDSS_EVENT_REGISTER_MDP_CALLBACK, MDSS_EVENT_DSI_PANEL_STATUS, MDSS_EVENT_DSI_DYNAMIC_SWITCH, MDSS_EVENT_DSI_RECONFIG_CMD, MDSS_EVENT_DSI_RESET_WRITE_PTR, MDSS_EVENT_PANEL_TIMING_SWITCH, MDSS_EVENT_MAX, };
在msm_dsi_on期间,通过调用msm_dsi_clk_enable打开DSI时钟。 在msm_dsi_off期间,通过调用msm_dsi_clk_disable关闭clks。
(五)、 Panel driver (面板 dsi) MDSS : Multimedia Display sub system DSI: Display Serial Interface
qcom,mdss-dsi-force-clock-lane-hs; // faulse :clock每帧回lp11 ture: clock不回 qcom,mdss-dsi-hfp-power-mode; // data 每行回lp11,对应的hfp要修改成300以上
面板信息位于kernel\arch\arm\boot\dts\中的.dtsi文件中。 这包含所有面板特定的命令,例如on,off和reset(mdss-dsi-on-command,mdss-dsi-off-command,mdss-dsi-reset-sequence),BL控制和其他面板 独立参数。 例如: msm8610-mdss.dtsi (文件名通常为 msmxxx-mdss.dtsi 指定了mdss 的 mdp 和 dsi)
5.1、.dtsi文件解析 1 2 3 4 5 6 7 8 9 10 11 mdss_mdp: qcom,mdss_mdp@fd900000 { compatible = "qcom,mdss_mdp3" ; ---------- mdss_dsi0: qcom,mdss_dsi@fdd00000 { compatible = "qcom,msm-dsi-v2" ; 或者 mdss_dsi0: qcom,mdss_dsi_ctrl0@1 a94000 { compatible = "qcom,mdss-dsi-ctrl" ;
通过下面函数向 mdss_fb.c 注册了fb_info结构 (包含在mdss_dsi_ctrl_pdata结构中)
1 2 3 4 5 6 7 8 9 10 11 12 drivers\video\msm\mdss\dsi_host_v2.c (lcd驱动 dsi) dsi_panel_device_register_v2(struct platform_device *dev,struct mdss_dsi_ctrl_pdata *ctrl_pdata) static const struct of_device_id msm_dsi_v2_dt_match [] = { {.compatible = "qcom,msm-dsi-v2" }, {} }; 或者 drivers\video\msm\mdss\mdss_dsi.c
msm8610-asus.dts (指定mdp中的哪一个配置) 通常在dts文件的 mdss_dsi0 lab里面通过 qcom,dsi-pref-prim-pan 属性 指定使用哪一个lcd配置
1 2 3 &mdss_dsi0 { qcom,dsi-pref-prim-pan = <&dsi_fl10802_fwvga_vid>; };
dsi-panel-fl10802-fwvga-video.dtsi
1 2 3 4 5 6 7 8 9 &mdss_mdp { dsi_fl10802_fwvga_vid: qcom,mdss_dsi_fl10802_fwvga_video { qcom,mdss-dsi-panel-name = "fl10802 fwvga video mode dsi panel" ; qcom,mdss-dsi-drive-ic = "fl10802" ; qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; qcom,mdss-dsi-panel-type = "dsi_video_mode" ; qcom,mdss-dsi-panel-destination = "display_1" ; ... }
5.2、mdss_mdp 和 mdss_dsi0 的关系 mdss_mdp 相当于一个数组,里面定义了很多不同lcd显示屏的配置项包括分辨率等等
面板驱动程序可以根据连接的实际面板数量具有多个节点。 mdss_register_panel注册面板驱动程序: msm_dsi_probe→dsi_panel_device_register_v2→mdss_register_panel→ of_platform_device_create 以下面板控制功能可用并在mdss_dsi_panel_init中初始化: ctrl_pdata→on = mdss_dsi_panel_on; ctrl_pdata→off = mdss_dsi_panel_off; ctrl_pdata→panel_data.set_backlight = mdss_dsi_panel_bl_ctrl; 例如,在mdp3_ctrl.c中,函数mdp3_ctrl_on()会调用以下DSI处理程序 MDSS_EVENT_UNBLANK和MDSS_EVENT_PANEL_ON事件如下所示: rc = panel→event_handler(panel,MDSS_EVENT_UNBLANK,NULL); rc | =面板→event_handler(面板,MDSS_EVENT_PANEL_ON,NULL);
5.3、通过内核接口打开和关闭显示器 5.3.1、启动
按照上面的部分所述注册设备后: mdss_fb_open→mdss_fb_blank_sub→pdata→on(面板ON功能) 注意:对于命令模式面板,如果面板在启动时已打开,则会跳过该启动序列 以避免关闭/ -on的文物。
5.3.2、暂停/恢复 挂起/恢复时,调用fb驱动程序挂起/恢复和MDP驱动程序挂起/恢复。 fb驱动程序依次调用面板驱动程序的开/关功能。
5.3.2.1、暂停序列 ![Alt text | center](https://raw.githubusercontent.com/zhoujinjianmm/zhoujinjian.com.images/master/display.system/DS05-11-suspend sequence.png)
Kernelcall→mdss_fb_release_all→mdss_fb_blank→mdss_fb_blank_sub→mdp3_ctrl_off→ mdp3_ctrl_off发送两个事件:
MDSS_EVENT_PANEL_OFF - dsi_event_handler接收事件。 当事件发生时 接收到时,调用小组关闭序列。
MDSS_EVENT_BLANK - 该事件由调用的dsi_event_handler处理 mdss_dsi_off。
Kernelcall→mdp3_suspend - 未使用5.3.2.2、恢复序列
![Alt text | center](https://raw.githubusercontent.com/zhoujinjianmm/zhoujinjian.com.images/master/display.system/DS05-12-resume sequence.png)
Kernelcall→mdss_fb_blank→mdss_fb_blank_sub→mdp3_ctrl_on→ mdp3_ctrl_on发送两个事件:
MDSS_EVENT_UNBLANK - dsi_event_handler接收事件。 当事件发生时 收到,DSI-on被调用。
MDSS_EVENT_PANEL_ON - 事件由dsi_event_handler处理,dsi_event_handler发送 面板上的序列。 Kernelcall→mdp3_resume - 未使用
5.4、图像更新到面板 ![Alt text | center](https://raw.githubusercontent.com/zhoujinjianmm/zhoujinjian.com.images/master/display.system/DS05-13-flow for display subsystem commit.png)
用户必须确保MSMFB_OVERLAY_SET IOCTL在呼叫之前至少被调用一次 到MSMFB_OVERLAY_PLAY IOCLT(例如ioctl(fd, MSMFB_OVERLAY_PLAY, &od))。MSMFB_OVERLAY_PLAY队列缓冲区 显示在面板上。 用户使用MSMFB_DISPLAY_COMMIT调用fb IOCLT。这会启动一个呼叫 mdss_fb_display_commit 并安排工作队列。此工作队列处理程序调用 msm_fb_pan_display_ex,然后调用mdp3_ctrl_pan_display。 mdss_fb_ioctl→(MSMFB_DISPLAY_COMMIT)→mdss_fb_display_commit→ mdss_fb_pan_display_ex→计划工作mdss_fb_commit_wq_handler msm_fb_commit_wq_handler→mdp3_ctrl_display_commit_kickoff→mdp3_dmap_update 一次呼叫后,可以有多个对PLAY和COMMIT IOCLT的呼叫 MSMFB_OVERLAY_SET IOCTL。面板更新完成并且设备完成后 需要进入挂起或关闭状态,用户可以调用MSMFB_OVERLAY_UNSET。
注意:工作队列架构正在被即将发布的线程取代, 这些文件未被捕获。视频和命令模式面板的面板更新略有不同。为了在命令模式面板中,mdp3_dmap_update函数会等待,直到前一个图像更新为止完成使用MDP DMA开始新帧更新
if(dma-> output_config.out_sel == MDP3_DMA_OUTPUT_SEL_DSI_CMD){ cb_type = MDP3_DMA_CALLBACK_TYPE_DMA_DONE; 如果(intf-> active) wait_for_completion_killable(DMA-> dma_comp);
对于视频面板,在DMA触发后,mdp3_dmap_update等待vsync。
(六)、Msm8610 lcd driver 内核初始化分析 请参考【msm8610 lcd driver code analysis】
(七)、参考资料(特别感谢各位前辈的分析和图示): (๑乛◡乛๑) 、( ͡° ͜ʖ ͡°)、(ಡωಡ)!!!累~ 时至今日,终于完整的分析了Android Display System 总体框架流程,不禁感叹计算机世界的博大精深,在这个系列的分析中历练了如何拆解分析一个庞大复杂的模块、学习收获良多,同时也了解了自身知识的欠缺,由于涉及知识较多较广,博主也未能完全吃透,其中分析有误的地方还请各位见谅。所谓路漫漫其修远兮,吾将上下而求索。 Todo:专注于Linux && Android Multimedia(Camera、Video、Audio、Display)系统分析与研究,Multimedia System 任还有许多未解之惑,需恶补Linux内核知识,少年,加油(➽➽➽)
(八)、参考资料(特别感谢各位前辈的分析和图示): Graphics Stack Update 高通Android平台-应用空间操作framebuffer dump LCD总结 msm8610 lcd driver code analysis linux qcom LCD framwork Qualcomm平台 display bring up 过程详解 高通8x25平台display模块总结 Android 中的 framebuffer FrameBuffer驱动程序分析 Android Framebuffer介绍及使用 Android 图形系统