一、驱动总体概述
本次的驱动代码是Samsung公司为s5pv210这款SoC编写的framebuffer驱动,对应于s5pv210中的内部外设Display Controller (FIMD)模块。
驱动代码是基于platform平台总线编写的。
1、驱动代码的源文件分布:
(1):drivers/video/samsung/s3cfb.c, 驱动主体
(2):drivers/video/samsung/s3cfb_fimd6x.c,里面有很多LCD硬件操作的函数
(3):arch/arm/mach-s5pv210/mach-x210.c,负责提供platform_device,这个文件里面提供了很多的基于platform总线编写的驱动需要的platform_device。
mach文件是每一个移植好的内核都会提供这个文件的,例如这里的mach-x210.c文件是开发板厂商从三星提供的mach文件移植而来的。
(4):arch/arm/plat-s5p/devs.c,为platform_device提供一些硬件描述信息
2、当我们接触到一种新的驱动框架的时候,怎么能够找到驱动框架源代码(入口函数)所在哪个源文件中?
(1):经验:靠经验的前提是你之前就已经接触过很多的驱动框架,你能够靠你的经验大概猜出来是哪些文件
(2):可以分析内核源码树中menuconfig、Makefile、Kconfig等
(3):内核编译后检查编译结果中的.o文件
二、platform_driver平台设备驱动部分
1、注册/卸载平台驱动:s3cfb_register/s3cfb_unregister (drivers\video\samsung\s3cfb.c)
(1)platform_driver结构体变量s3cfb_driver
1 static struct platform_driver s3cfb_driver = { 2 .probe = s3cfb_probe, // 平台的probe函数 3 .remove = __devexit_p(s3cfb_remove), 4 .driver = { 5 .name = S3CFB_NAME, // 平台设备驱动的名字 s3cfb 6 .owner = THIS_MODULE, 7 }, 8 };
2、相关的数据结构
1 struct s3c_platform_fb { 2 int hw_ver; 3 char clk_name[16]; 4 int nr_wins; // 这个表示虚拟窗口的数量 5 int nr_buffers[5]; 6 int default_win; // 这个表示当前默认的窗口 7 int swap; 8 phys_addr_t pmem_start; /* starting physical address of memory region */ // 显存的物理起始地址 9 size_t pmem_size; /* size of memory region */ // 显存的字节大小 10 void *lcd; 11 void (*cfg_gpio)(struct platform_device *dev); // LCD相关gpio的配置 12 int (*backlight_on)(struct platform_device *dev); // 打开LCD的背光 13 int (*backlight_onoff)(struct platform_device *dev, int onoff); // 关闭LCD的背光 14 int (*reset_lcd)(struct platform_device *dev); // 复位LCD 15 int (*clk_on)(struct platform_device *pdev, struct clk **s3cfb_clk); // LCD相关的时钟打开 16 int (*clk_off)(struct platform_device *pdev, struct clk **clk); // LCD相关的时钟关闭 17 };
1 struct s3cfb_global { 2 /* general */ 3 void __iomem *regs; // SoC中LCD控制器部分相关的寄存器地址的基地址(虚拟地址) Display Controller (FIMD)模块 4 struct mutex lock; // 互斥锁 5 struct device *dev; // 表示本fb设备的device指针 6 struct clk *clock; 7 struct regulator *regulator; 8 int irq; // 本LCD使用到的中断号 9 struct fb_info **fb; // fb_info 的二重指针 用来指向一个 fb_info 指针数组 10 struct completion fb_complete; 11 12 /* fimd */ 13 int enabled; 14 int dsi; 15 int interlace; 16 enum s3cfb_output_t output; // LCD的输出模式 17 enum s3cfb_rgb_mode_t rgb_mode; // RGB色彩模式 18 struct s3cfb_lcd *lcd; // 用来描述一个LCD的硬件信息 19 20 #ifdef CONFIG_HAS_WAKELOCK 21 struct early_suspend early_suspend; 22 struct wake_lock idle_lock; 23 #endif 24 25 #ifdef CONFIG_CPU_FREQ 26 struct notifier_block freq_transition; 27 struct notifier_block freq_policy; 28 #endif 29 30 };
1 struct s3cfb_lcd { 2 int width; // 水平像素 3 int height; // 垂直像素 4 int p_width; // 物理宽度 mm 5 int p_height; // 物理高度mm 6 int bpp; // 像素深度 7 int freq; // LCD的刷新率 8 struct s3cfb_lcd_timing timing; // LCD时序相关的参数 9 struct s3cfb_lcd_polarity polarity; // 这个是用来表示LCD的各种电平信号是否需要进行翻转 10 11 void (*init_ldi)(void); // 用来初始化 LDI 我不知道LDI是什么东西 12 void (*deinit_ldi)(void); 13 };
3、函数详解
(1)s3cfb_probe函数分析:
1 static int __devinit s3cfb_probe(struct platform_device *pdev) 2 { 3 struct s3c_platform_fb *pdata; // 这个是三星封装的一个用来表示平台设备层的私有数据的结构体 4 struct s3cfb_global *fbdev; // 设备驱动部分封装的一个全局的结构体,这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的 5 struct resource *res; // 定义一个资源结构体指针 6 int i, j, ret = 0; 7 8 fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); // 给 fpdev 申请分配内存 9 if (!fbdev) { 10 dev_err(&pdev->dev, "failed to allocate for " 11 "global fb structure\n"); 12 ret = -ENOMEM; 13 goto err_global; 14 } 15 fbdev->dev = &pdev->dev; // 通过 fbdev->dev 指向 pdev->dev /sys/devices/platform/s3cfb/ 这个目录作为fb设备的父设备目录 16 17 fbdev->regulator = regulator_get(&pdev->dev, "pd"); // 调整器 : 动态电流和电压控制,具体的我也不清楚 18 if (!fbdev->regulator) { 19 dev_err(fbdev->dev, "failed to get regulator\n"); 20 ret = -EINVAL; 21 goto err_regulator; 22 } 23 ret = regulator_enable(fbdev->regulator); 24 if (ret < 0) { 25 dev_err(fbdev->dev, "failed to enable regulator\n"); 26 ret = -EINVAL; 27 goto err_regulator; 28 } 29 pdata = to_fb_plat(&pdev->dev); // 获取平台设备层的私有数据 pdev->dev-> platform_data 存放在 pdata中 30 if (!pdata) { 31 dev_err(fbdev->dev, "failed to get platform data\n"); 32 ret = -EINVAL; 33 goto err_pdata; 34 } 35 36 fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd; // 通过fbdev->lcd 指向 pdata->lcd 37 38 if (pdata->cfg_gpio) // 如果平台设备的私有数据中的cfg_gpio指向了一个有效的配置LCD相关的gpio的方法 39 pdata->cfg_gpio(pdev); // 则调用这个函数 40 41 if (pdata->clk_on) // 打开LCD相关的时钟设置 42 pdata->clk_on(pdev, &fbdev->clock); 43 44 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取平台设备的IO资源 45 if (!res) { 46 dev_err(fbdev->dev, "failed to get io memory region\n"); 47 ret = -EINVAL; 48 goto err_io; 49 } 50 51 res = request_mem_region(res->start, // 请求进行物理地址到虚拟地址的映射 52 res->end - res->start + 1, pdev->name); 53 if (!res) { 54 dev_err(fbdev->dev, "failed to request io memory region\n"); 55 ret = -EINVAL; 56 goto err_io; 57 } 58 59 fbdev->regs = ioremap(res->start, res->end - res->start + 1); // 申请物理地址到虚拟地址的映射,将映射得到的虚拟地址存放在 fbdev->regs 60 if (!fbdev->regs) { 61 dev_err(fbdev->dev, "failed to remap io region\n"); 62 ret = -EINVAL; 63 goto err_mem; 64 } 65 66 s3cfb_set_vsync_interrupt(fbdev, 1); // 使能vsync中断(场同步信号中断) 67 s3cfb_set_global_interrupt(fbdev, 1); // 全局中断使能: 使能视频帧中断 和 使能视频中断 68 s3cfb_init_global(fbdev); // 全局初始化 69 70 if (s3cfb_alloc_framebuffer(fbdev)) { // 给fb_info 申请分配内存 并构建fb_info结构体 71 ret = -ENOMEM; 72 goto err_alloc; 73 } 74 75 if (s3cfb_register_framebuffer(fbdev)) { // 注册fb设备 内部其实就是调用了FB驱动框架层中 register_framebuffer 函数进行注册 76 ret = -EINVAL; 77 goto err_register; 78 } 79 80 s3cfb_set_clock(fbdev); // 时钟设置 81 s3cfb_set_window(fbdev, pdata->default_win, 1); // 虚拟窗口相关的设置 82 83 s3cfb_display_on(fbdev); // 打开LCD显示 84 85 fbdev->irq = platform_get_irq(pdev, 0); // 获取平台设备私有数据中的 中断号资源 86 if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED, // 申请中断 87 pdev->name, fbdev)) { 88 dev_err(fbdev->dev, "request_irq failed\n"); 89 ret = -EINVAL; 90 goto err_irq; 91 } 92 93 #ifdef CONFIG_FB_S3C_LCD_INIT 94 if (pdata->backlight_on) 95 pdata->backlight_on(pdev); 96 97 if (!bootloaderfb && pdata->reset_lcd) 98 pdata->reset_lcd(pdev); 99 #endif 100 101 #ifdef CONFIG_HAS_EARLYSUSPEND 102 fbdev->early_suspend.suspend = s3cfb_early_suspend; 103 fbdev->early_suspend.resume = s3cfb_late_resume; 104 fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; 105 register_early_suspend(&fbdev->early_suspend); 106 #endif 107 108 ret = device_create_file(&(pdev->dev), &dev_attr_win_power); // 在平台设备下 /sys/devices/platform/pdev_dev/dev_attr_win_power 属性文件 109 if (ret < 0) // pdev_dev表示的就是我们的平台设备的名字 110 dev_err(fbdev->dev, "failed to add sysfs entries\n"); 111 112 dev_info(fbdev->dev, "registered successfully\n"); 113 114 #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) // 下面这个是处理Linux启动logo 相关的代码 115 if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) { 116 printk("Start display and show logo\n"); 117 /* Start display and show logo on boot */ 118 fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]); 119 fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR); 120 } 121 #endif 122 mdelay(100); 123 if (pdata->backlight_on) // 打开背光 124 pdata->backlight_on(pdev); 125 126 return 0; 127 128 err_irq: 129 s3cfb_display_off(fbdev); 130 s3cfb_set_window(fbdev, pdata->default_win, 0); 131 for (i = pdata->default_win; 132 i < pdata->nr_wins + pdata->default_win; i++) { 133 j = i % pdata->nr_wins; 134 unregister_framebuffer(fbdev->fb[j]); 135 } 136 err_register: 137 for (i = 0; i < pdata->nr_wins; i++) { 138 if (i == pdata->default_win) 139 s3cfb_unmap_default_video_memory(fbdev->fb[i]); 140 framebuffer_release(fbdev->fb[i]); 141 } 142 kfree(fbdev->fb); 143 144 err_alloc: 145 iounmap(fbdev->regs); 146 147 err_mem: 148 release_mem_region(res->start, 149 res->end - res->start + 1); 150 151 err_io: 152 pdata->clk_off(pdev, &fbdev->clock); 153 154 err_pdata: 155 regulator_disable(fbdev->regulator); 156 157 err_regulator: 158 kfree(fbdev); 159 160 err_global: 161 return ret; 162 }
(2)s3cfb_init_global
1 static int s3cfb_init_global(struct s3cfb_global *ctrl) 2 { 3 ctrl->output = OUTPUT_RGB; // 设置初始模式 4 ctrl->rgb_mode = MODE_RGB_P; // 设置RGB色彩模式 5 6 init_completion(&ctrl->fb_complete); // 初始化完成量(注: 完成量也是一种内核提供的同步机制) 7 mutex_init(&ctrl->lock); 8 9 s3cfb_set_output(ctrl); // 寄存器配置LCD的输出模式 10 s3cfb_set_display_mode(ctrl); // 寄存器配置LCD的显示模式 11 s3cfb_set_polarity(ctrl); // 寄存器配置信号电平翻转 12 s3cfb_set_timing(ctrl); // 寄存器配置LCD时序参数 13 s3cfb_set_lcd_size(ctrl); // 寄存器配置LCD的水平、垂直像素大小 14 15 return 0; 16 }
(3)s3cfb_alloc_framebuffer
1 static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl) 2 { 3 struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); // 通过 ctrl->dev 去获取平台设备的私有数据 4 int ret, i; 5 6 ctrl->fb = kmalloc(pdata->nr_wins * // 给ctrl->fb 的这个fb_info指针数组分配内存 7 sizeof(*(ctrl->fb)), GFP_KERNEL); // 数量 nr_wins 8 if (!ctrl->fb) { 9 dev_err(ctrl->dev, "not enough memory\n"); 10 ret = -ENOMEM; 11 goto err_alloc; 12 } 13 14 for (i = 0; i < pdata->nr_wins; i++) { // 给fb_info 指针数组中的每一个指针申请分配内存 15 ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb), 16 ctrl->dev); 17 if (!ctrl->fb[i]) { 18 dev_err(ctrl->dev, "not enough memory\n"); 19 ret = -ENOMEM; 20 goto err_alloc_fb; 21 } 22 23 s3cfb_init_fbinfo(ctrl, i); // 初始化fb_info 这个结构体 就是去构建fb_info 24 25 if (i == pdata->default_win) { 26 if (s3cfb_map_video_memory(ctrl->fb[i])) { // 给FB显存确定内存地址和分配空间(注意只是对默认的fb设备分配了,一个虚拟的显示窗口其实就是抽象为一个fb设备,多个窗口其实是会进行叠加的) 27 dev_err(ctrl->dev, 28 "failed to map video memory " 29 "for default window (%d)\n", i); 30 ret = -ENOMEM; 31 goto err_map_video_mem; 32 } 33 } 34 } 35 36 return 0; 37 38 err_alloc_fb: 39 while (--i >= 0) { 40 if (i == pdata->default_win) 41 s3cfb_unmap_default_video_memory(ctrl->fb[i]); 42 43 err_map_video_mem: 44 framebuffer_release(ctrl->fb[i]); 45 } 46 kfree(ctrl->fb); 47 48 err_alloc: 49 return ret; 50 } 51 52 53 54 struct fb_info *framebuffer_alloc(size_t size, struct device *dev) 55 { 56 #define BYTES_PER_LONG (BITS_PER_LONG/8) 57 #define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG)) 58 int fb_info_size = sizeof(struct fb_info); // 获取fb_info结构体类型的字节大小 59 struct fb_info *info; 60 char *p; 61 62 if (size) 63 fb_info_size += PADDING; 64 65 p = kzalloc(fb_info_size + size, GFP_KERNEL); 66 67 if (!p) 68 return NULL; 69 70 info = (struct fb_info *) p; 71 72 if (size) 73 info->par = p + fb_info_size; 74 75 info->device = dev; // 指定我们的 fb 设备的父类设备是平台设备 /sys/devices/platform/plat_xxxdev/ 这个目录,也就是我们将来创建的设备就在这个目录下 76 77 #ifdef CONFIG_FB_BACKLIGHT 78 mutex_init(&info->bl_curve_mutex); 79 #endif 80 81 return info; 82 #undef PADDING 83 #undef BYTES_PER_LONG 84 } 85 86 87 88 static int s3cfb_map_video_memory(struct fb_info *fb) 89 { 90 struct fb_fix_screeninfo *fix = &fb->fix; 91 struct s3cfb_window *win = fb->par; 92 struct s3cfb_global *fbdev = 93 platform_get_drvdata(to_platform_device(fb->device)); 94 struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev); 95 96 if (win->owner == DMA_MEM_OTHER) { 97 fix->smem_start = win->other_mem_addr; 98 fix->smem_len = win->other_mem_size; 99 return 0; 100 } 101 102 if (fb->screen_base) // 如果我们之前就已经确定了FB的显存地址的虚拟地址,那么就直接退出,因为这个函数的作用就是给显存确定虚拟内存地址并分配内存空间 103 return 0; 104 105 if (pdata && pdata->pmem_start && (pdata->pmem_size >= fix->smem_len)) { // 如果我们的平台设备中的私有数据中已经确定了显存的物理地址和大小 106 fix->smem_start = pdata->pmem_start; // 那么就使用平台设备私有数据中定义的 107 fb->screen_base = ioremap_wc(fix->smem_start, pdata->pmem_size); 108 } else 109 fb->screen_base = dma_alloc_writecombine(fbdev->dev, // 否则的话我们就自己申请分配显存空间 110 PAGE_ALIGN(fix->smem_len), 111 (unsigned int *) 112 &fix->smem_start, GFP_KERNEL); 113 114 if (!fb->screen_base) 115 return -ENOMEM; 116 117 dev_info(fbdev->dev, "[fb%d] dma: 0x%08x, cpu: 0x%08x, " 118 "size: 0x%08x\n", win->id, 119 (unsigned int)fix->smem_start, 120 (unsigned int)fb->screen_base, fix->smem_len); 121 122 memset(fb->screen_base, 0, fix->smem_len); // 将FB显存清零 123 win->owner = DMA_MEM_FIMD; 124 125 return 0; 126 }
三、platform_device平台设备部分
fb的驱动是基于platform平台总线的,所以需要提供platform_device(注册平台设备)和platform_driver(注册平台驱动)。前面讲的是平台驱动部分
那么它对应的平台设备的注册在什么地方呢? 答案就是之前说的mach文件中,我这里是 arch\arm\mach-s5pv210\mach-x210.c 这个文件。
之前说了,这个文件中注册了很多的系统中可能用到的平台设备,将来写驱动的时候,只需要注册平台驱动即可,当然如果没有,可能就需要自己去添加。
这个文件中将所有的平台设备结构体都放在一个 struct platform_device *类型的数组smdkc110_devices中,将所有定义好的platform_device结构体挂接到这个数组中去,
在 smdkc110_machine_init 函数中将所有的平台设备都进行了注册。 如下: smdkc110_machine_init 这个函数其实是被链接在Linux启动的各个初始化段中的某一个,所以
当系统启动的时候,执行了初始化段中的函数时,smdkc110_machine_init 函数就会被调用。
smdkc110_machine_init
platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices)); // 平台设备的注册
s3cfb_set_platdata(&ek070tn93_fb_data); // 给平台设备设置私有数据
1、struct platform_device s3c_device_fb变量
s3c_device_fb 是fb的平台总线驱动下提供的 platform_device 类型变量,这个变量定义在:arch\arm\plat-s5p\devs.c 文件中
1 struct platform_device s3c_device_fb = { 2 .name = "s3cfb", // 平台设备的名字 3 .id = -1, 4 .num_resources = ARRAY_SIZE(s3cfb_resource), // 平台设备的资源数量 5 .resource = s3cfb_resource, // 平台设备的资源 6 .dev = { 7 .dma_mask = &fb_dma_mask, 8 .coherent_dma_mask = 0xffffffffUL 9 } 10 };
(1)从定义的变量中可以看出来,并没有挂接设备的私有数据到s3c_device_fb变量中,因为platform_device结构体中device结构体下的platform_data指针并没有被赋值
那么是不是这个平台设备没有私有数据呢?
答案是肯定有的,因为前面在分析平台驱动部分时都使用了平台设备的私有数据,那么之前说过,数据有使用的地方,肯定是有产生数据的地方,一定要弄清楚这么一个关系。
那么数据的产生地在那呢? 其实就是在smdkc110_machine_init函数中,这个函数中通过调用另一个函数(s3cfb_set_platdata)来挂接fb平台设备的私有数据。
s3cfb_set_platdata(&ek070tn93_fb_data);
1 static struct s3c_platform_fb ek070tn93_fb_data __initdata = { 2 .hw_ver = 0x62, 3 .nr_wins = 5, 4 .default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, // 默认开启的虚拟窗口 5 .swap = FB_SWAP_WORD | FB_SWAP_HWORD, 6 7 .lcd = &ek070tn93, // 描述LCD硬件信息的结构体 8 .cfg_gpio = ek070tn93_cfg_gpio, // 配置LCD相关的gpio的方法 9 .backlight_on = ek070tn93_backlight_on, // 使能LCD背光 10 .backlight_onoff = ek070tn93_backlight_off, // 关闭LCD背光 11 .reset_lcd = ek070tn93_reset_lcd, // 复位LCD 12 };
当我们要去移植一款LCD时,一般只需要对这个结构体里面的内容进行的更改,例如 gpio、LCD的硬件信息等等。
1):s3cfb_set_platdata函数分析:
1 void __init s3cfb_set_platdata(struct s3c_platform_fb *pd) 2 { 3 struct s3c_platform_fb *npd; // 定义一个 struct s3c_platform_fb 类型的指针 4 int i; 5 6 if (!pd) // 如果没有传入 s3c_platform_fb 结构体变量指针,则使用默认的 7 pd = &default_fb_data; 8 9 npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL); 10 if (!npd) 11 printk(KERN_ERR "%s: no memory for platform data\n", __func__); 12 else { 13 for (i = 0; i < npd->nr_wins; i++) 14 npd->nr_buffers[i] = 1; 15 16 npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS; // 再进一步对数据结构进行填充 17 18 s3cfb_get_clk_name(npd->clk_name); 19 npd->clk_on = s3cfb_clk_on; 20 npd->clk_off = s3cfb_clk_off; 21 22 /* starting physical address of memory region */ 23 npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1); 24 /* size of memory region */ 25 npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1); 26 27 s3c_device_fb.dev.platform_data = npd; // 把传进来的 s3c_platform_fb 结构体变量挂载到 s3c_device_fb变量中 28 } 29 }
总结: 由上可知s3cfb_set_platdata函数设置平台设备的私有数据,就是定义一个struct s3c_platform_fb类型的指针,然后给他申请分配内存然后进行一系列的填充,
最后将这个结构体挂接到平台设备的私有数据中去。