三星framebuffer驱动代码分析
一、驅動總體概述
本次的驅動代碼是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類型的指針,然后給他申請分配內存然后進行一系列的填充,
最后將這個結構體掛接到平臺設備的私有數據中去。
?
轉載于:https://www.cnblogs.com/deng-tao/p/6078072.html
總結
以上是生活随笔為你收集整理的三星framebuffer驱动代码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx负载均衡的简单实现
- 下一篇: Android开发7:简单的数据存储(使