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
static struct platform_driver s3cfb_driver = {.probe = s3cfb_probe, // 平臺的probe函數.remove = __devexit_p(s3cfb_remove),.driver = {.name = S3CFB_NAME, // 平臺設備驅動的名字 s3cfb.owner = THIS_MODULE,}, };?
2、相關的數據結構
struct s3c_platform_fb {int hw_ver;char clk_name[16];int nr_wins; // 這個表示虛擬窗口的數量int nr_buffers[5];int default_win; // 這個表示當前默認的窗口int swap;phys_addr_t pmem_start; /* starting physical address of memory region */ // 顯存的物理起始地址size_t pmem_size; /* size of memory region */ // 顯存的字節大小void *lcd;void (*cfg_gpio)(struct platform_device *dev); // LCD相關gpio的配置int (*backlight_on)(struct platform_device *dev); // 打開LCD的背光int (*backlight_onoff)(struct platform_device *dev, int onoff); // 關閉LCD的背光int (*reset_lcd)(struct platform_device *dev); // 復位LCDint (*clk_on)(struct platform_device *pdev, struct clk **s3cfb_clk); // LCD相關的時鐘打開int (*clk_off)(struct platform_device *pdev, struct clk **clk); // LCD相關的時鐘關閉};?
struct s3cfb_global {/* general */void __iomem *regs; // SoC中LCD控制器部分相關的寄存器地址的基地址(虛擬地址) Display Controller (FIMD)模塊struct mutex lock; // 互斥鎖struct device *dev; // 表示本fb設備的device指針struct clk *clock;struct regulator *regulator;int irq; // 本LCD使用到的中斷號struct fb_info **fb; // fb_info 的二重指針 用來指向一個 fb_info 指針數組 struct completion fb_complete;/* fimd */int enabled;int dsi;int interlace;enum s3cfb_output_t output; // LCD的輸出模式enum s3cfb_rgb_mode_t rgb_mode; // RGB色彩模式 struct s3cfb_lcd *lcd; // 用來描述一個LCD的硬件信息#ifdef CONFIG_HAS_WAKELOCKstruct early_suspend early_suspend;struct wake_lock idle_lock;#endif#ifdef CONFIG_CPU_FREQstruct notifier_block freq_transition;struct notifier_block freq_policy;#endif};?
struct s3cfb_lcd {int width; // 水平像素int height; // 垂直像素int p_width; // 物理寬度 mmint p_height; // 物理高度mmint bpp; // 像素深度int freq; // LCD的刷新率struct s3cfb_lcd_timing timing; // LCD時序相關的參數struct s3cfb_lcd_polarity polarity; // 這個是用來表示LCD的各種電平信號是否需要進行翻轉void (*init_ldi)(void); // 用來初始化 LDI 我不知道LDI是什么東西void (*deinit_ldi)(void);};?
3、函數詳解
(1)s3cfb_probe函數分析:
static int __devinit s3cfb_probe(struct platform_device *pdev){struct s3c_platform_fb *pdata; // 這個是三星封裝的一個用來表示平臺設備層的私有數據的結構體struct s3cfb_global *fbdev; // 設備驅動部分封裝的一個全局的結構體,這個結構體主要作用是在驅動部分的2個文件(s3cfb.c和s3cfb_fimd6x.c)的函數中做數據傳遞用的struct resource *res; // 定義一個資源結構體指針int i, j, ret = 0;fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); // 給 fpdev 申請分配內存if (!fbdev) {dev_err(&pdev->dev, "failed to allocate for ""global fb structure\n");ret = -ENOMEM;goto err_global;}fbdev->dev = &pdev->dev; // 通過 fbdev->dev 指向 pdev->dev /sys/devices/platform/s3cfb/ 這個目錄作為fb設備的父設備目錄fbdev->regulator = regulator_get(&pdev->dev, "pd"); // 調整器 : 動態電流和電壓控制,具體的我也不清楚if (!fbdev->regulator) {dev_err(fbdev->dev, "failed to get regulator\n");ret = -EINVAL;goto err_regulator;}ret = regulator_enable(fbdev->regulator);if (ret < 0) {dev_err(fbdev->dev, "failed to enable regulator\n");ret = -EINVAL;goto err_regulator;}pdata = to_fb_plat(&pdev->dev); // 獲取平臺設備層的私有數據 pdev->dev-> platform_data 存放在 pdata中 if (!pdata) {dev_err(fbdev->dev, "failed to get platform data\n");ret = -EINVAL;goto err_pdata;}fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd; // 通過fbdev->lcd 指向 pdata->lcd if (pdata->cfg_gpio) // 如果平臺設備的私有數據中的cfg_gpio指向了一個有效的配置LCD相關的gpio的方法pdata->cfg_gpio(pdev); // 則調用這個函數if (pdata->clk_on) // 打開LCD相關的時鐘設置pdata->clk_on(pdev, &fbdev->clock);res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 獲取平臺設備的IO資源if (!res) {dev_err(fbdev->dev, "failed to get io memory region\n");ret = -EINVAL;goto err_io;}res = request_mem_region(res->start, // 請求進行物理地址到虛擬地址的映射res->end - res->start + 1, pdev->name);if (!res) {dev_err(fbdev->dev, "failed to request io memory region\n");ret = -EINVAL;goto err_io;}fbdev->regs = ioremap(res->start, res->end - res->start + 1); // 申請物理地址到虛擬地址的映射,將映射得到的虛擬地址存放在 fbdev->regsif (!fbdev->regs) {dev_err(fbdev->dev, "failed to remap io region\n");ret = -EINVAL;goto err_mem;}s3cfb_set_vsync_interrupt(fbdev, 1); // 使能vsync中斷(場同步信號中斷)s3cfb_set_global_interrupt(fbdev, 1); // 全局中斷使能: 使能視頻幀中斷 和 使能視頻中斷s3cfb_init_global(fbdev); // 全局初始化if (s3cfb_alloc_framebuffer(fbdev)) { // 給fb_info 申請分配內存 并構建fb_info結構體ret = -ENOMEM;goto err_alloc;}if (s3cfb_register_framebuffer(fbdev)) { // 注冊fb設備 內部其實就是調用了FB驅動框架層中 register_framebuffer 函數進行注冊ret = -EINVAL;goto err_register;}s3cfb_set_clock(fbdev); // 時鐘設置s3cfb_set_window(fbdev, pdata->default_win, 1); // 虛擬窗口相關的設置s3cfb_display_on(fbdev); // 打開LCD顯示fbdev->irq = platform_get_irq(pdev, 0); // 獲取平臺設備私有數據中的 中斷號資源if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED, // 申請中斷pdev->name, fbdev)) {dev_err(fbdev->dev, "request_irq failed\n");ret = -EINVAL;goto err_irq;}#ifdef CONFIG_FB_S3C_LCD_INITif (pdata->backlight_on)pdata->backlight_on(pdev); if (!bootloaderfb && pdata->reset_lcd) pdata->reset_lcd(pdev);#endif#ifdef CONFIG_HAS_EARLYSUSPENDfbdev->early_suspend.suspend = s3cfb_early_suspend;fbdev->early_suspend.resume = s3cfb_late_resume;fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;register_early_suspend(&fbdev->early_suspend);#endifret = device_create_file(&(pdev->dev), &dev_attr_win_power); // 在平臺設備下 /sys/devices/platform/pdev_dev/dev_attr_win_power 屬性文件if (ret < 0) // pdev_dev表示的就是我們的平臺設備的名字dev_err(fbdev->dev, "failed to add sysfs entries\n");dev_info(fbdev->dev, "registered successfully\n");#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) // 下面這個是處理Linux啟動logo 相關的代碼if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {printk("Start display and show logo\n");/* Start display and show logo on boot */fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);}#endifmdelay(100);if (pdata->backlight_on) // 打開背光pdata->backlight_on(pdev);return 0;err_irq:s3cfb_display_off(fbdev);s3cfb_set_window(fbdev, pdata->default_win, 0);for (i = pdata->default_win;i < pdata->nr_wins + pdata->default_win; i++) {j = i % pdata->nr_wins;unregister_framebuffer(fbdev->fb[j]);}err_register:for (i = 0; i < pdata->nr_wins; i++) {if (i == pdata->default_win)s3cfb_unmap_default_video_memory(fbdev->fb[i]);framebuffer_release(fbdev->fb[i]);}kfree(fbdev->fb);err_alloc:iounmap(fbdev->regs);err_mem:release_mem_region(res->start,res->end - res->start + 1);err_io:pdata->clk_off(pdev, &fbdev->clock);err_pdata:regulator_disable(fbdev->regulator);err_regulator:kfree(fbdev);err_global:return ret;}?
(2)s3cfb_init_global
static int s3cfb_init_global(struct s3cfb_global *ctrl){ctrl->output = OUTPUT_RGB; // 設置初始模式ctrl->rgb_mode = MODE_RGB_P; // 設置RGB色彩模式init_completion(&ctrl->fb_complete); // 初始化完成量(注: 完成量也是一種內核提供的同步機制)mutex_init(&ctrl->lock);s3cfb_set_output(ctrl); // 寄存器配置LCD的輸出模式s3cfb_set_display_mode(ctrl); // 寄存器配置LCD的顯示模式s3cfb_set_polarity(ctrl); // 寄存器配置信號電平翻轉s3cfb_set_timing(ctrl); // 寄存器配置LCD時序參數s3cfb_set_lcd_size(ctrl); // 寄存器配置LCD的水平、垂直像素大小return 0;}?
(3)s3cfb_alloc_framebuffer
static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl){struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); // 通過 ctrl->dev 去獲取平臺設備的私有數據int ret, i;ctrl->fb = kmalloc(pdata->nr_wins * // 給ctrl->fb 的這個fb_info指針數組分配內存sizeof(*(ctrl->fb)), GFP_KERNEL); // 數量 nr_winsif (!ctrl->fb) {dev_err(ctrl->dev, "not enough memory\n");ret = -ENOMEM;goto err_alloc;}for (i = 0; i < pdata->nr_wins; i++) { // 給fb_info 指針數組中的每一個指針申請分配內存ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb),ctrl->dev);if (!ctrl->fb[i]) {dev_err(ctrl->dev, "not enough memory\n");ret = -ENOMEM;goto err_alloc_fb;}s3cfb_init_fbinfo(ctrl, i); // 初始化fb_info 這個結構體 就是去構建fb_infoif (i == pdata->default_win) {if (s3cfb_map_video_memory(ctrl->fb[i])) { // 給FB顯存確定內存地址和分配空間(注意只是對默認的fb設備分配了,一個虛擬的顯示窗口其實就是抽象為一個fb設備,多個窗口其實是會進行疊加的)dev_err(ctrl->dev,"failed to map video memory ""for default window (%d)\n", i);ret = -ENOMEM;goto err_map_video_mem;}}}return 0;err_alloc_fb:while (--i >= 0) {if (i == pdata->default_win)s3cfb_unmap_default_video_memory(ctrl->fb[i]);err_map_video_mem:framebuffer_release(ctrl->fb[i]);}kfree(ctrl->fb);err_alloc:return ret;}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); // 獲取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; // 指定我們的 fb 設備的父類設備是平臺設備 /sys/devices/platform/plat_xxxdev/ 這個目錄,也就是我們將來創建的設備就在這個目錄下 #ifdef CONFIG_FB_BACKLIGHTmutex_init(&info->bl_curve_mutex);#endifreturn info;#undef PADDING#undef BYTES_PER_LONG}static int s3cfb_map_video_memory(struct fb_info *fb){struct fb_fix_screeninfo *fix = &fb->fix;struct s3cfb_window *win = fb->par;struct s3cfb_global *fbdev =platform_get_drvdata(to_platform_device(fb->device));struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);if (win->owner == DMA_MEM_OTHER) {fix->smem_start = win->other_mem_addr;fix->smem_len = win->other_mem_size;return 0;}if (fb->screen_base) // 如果我們之前就已經確定了FB的顯存地址的虛擬地址,那么就直接退出,因為這個函數的作用就是給顯存確定虛擬內存地址并分配內存空間return 0;if (pdata && pdata->pmem_start && (pdata->pmem_size >= fix->smem_len)) { // 如果我們的平臺設備中的私有數據中已經確定了顯存的物理地址和大小fix->smem_start = pdata->pmem_start; // 那么就使用平臺設備私有數據中定義的fb->screen_base = ioremap_wc(fix->smem_start, pdata->pmem_size);} elsefb->screen_base = dma_alloc_writecombine(fbdev->dev, // 否則的話我們就自己申請分配顯存空間PAGE_ALIGN(fix->smem_len),(unsigned int *)&fix->smem_start, GFP_KERNEL);if (!fb->screen_base)return -ENOMEM;dev_info(fbdev->dev, "[fb%d] dma: 0x%08x, cpu: 0x%08x, ""size: 0x%08x\n", win->id,(unsigned int)fix->smem_start,(unsigned int)fb->screen_base, fix->smem_len);memset(fb->screen_base, 0, fix->smem_len); // 將FB顯存清零win->owner = DMA_MEM_FIMD;return 0;}?
三、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 文件中
?
struct platform_device s3c_device_fb = {.name = "s3cfb", // 平臺設備的名字.id = -1, .num_resources = ARRAY_SIZE(s3cfb_resource), // 平臺設備的資源數量.resource = s3cfb_resource, // 平臺設備的資源.dev = {.dma_mask = &fb_dma_mask,.coherent_dma_mask = 0xffffffffUL}};?
?
(1)從定義的變量中可以看出來,并沒有掛接設備的私有數據到s3c_device_fb變量中,因為platform_device結構體中device結構體下的platform_data指針并沒有被賦值
那么是不是這個平臺設備沒有私有數據呢?
答案是肯定有的,因為前面在分析平臺驅動部分時都使用了平臺設備的私有數據,那么之前說過,數據有使用的地方,肯定是有產生數據的地方,一定要弄清楚這么一個關系。
那么數據的產生地在那呢? ?其實就是在smdkc110_machine_init函數中,這個函數中通過調用另一個函數(s3cfb_set_platdata)來掛接fb平臺設備的私有數據。
s3cfb_set_platdata(&ek070tn93_fb_data);
?
static struct s3c_platform_fb ek070tn93_fb_data __initdata = {.hw_ver = 0x62,.nr_wins = 5,.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, // 默認開啟的虛擬窗口.swap = FB_SWAP_WORD | FB_SWAP_HWORD,.lcd = &ek070tn93, // 描述LCD硬件信息的結構體.cfg_gpio = ek070tn93_cfg_gpio, // 配置LCD相關的gpio的方法.backlight_on = ek070tn93_backlight_on, // 使能LCD背光.backlight_onoff = ek070tn93_backlight_off, // 關閉LCD背光.reset_lcd = ek070tn93_reset_lcd, // 復位LCD};?
當我們要去移植一款LCD時,一般只需要對這個結構體里面的內容進行的更改,例如 gpio、LCD的硬件信息等等。
?
?
1):s3cfb_set_platdata函數分析:
void __init s3cfb_set_platdata(struct s3c_platform_fb *pd){struct s3c_platform_fb *npd; // 定義一個 struct s3c_platform_fb 類型的指針 int i;if (!pd) // 如果沒有傳入 s3c_platform_fb 結構體變量指針,則使用默認的pd = &default_fb_data;npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);if (!npd)printk(KERN_ERR "%s: no memory for platform data\n", __func__);else {for (i = 0; i < npd->nr_wins; i++)npd->nr_buffers[i] = 1;npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS; // 再進一步對數據結構進行填充s3cfb_get_clk_name(npd->clk_name);npd->clk_on = s3cfb_clk_on;npd->clk_off = s3cfb_clk_off;/* starting physical address of memory region */npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);/* size of memory region */npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1);s3c_device_fb.dev.platform_data = npd; // 把傳進來的 s3c_platform_fb 結構體變量掛載到 s3c_device_fb變量中}}總結: ?由上可知s3cfb_set_platdata函數設置平臺設備的私有數據,就是定義一個struct?s3c_platform_fb類型的指針,然后給他申請分配內存然后進行一系列的填充,
最后將這個結構體掛接到平臺設備的私有數據中去。
總結
以上是生活随笔為你收集整理的framebuffer驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Makefile之变量(10)
- 下一篇: FreeModbus ASCII传输