Linux的LCD驅動分析及移植


測試平台

宿主機平台:Ubuntu 12.04.4 LTS

目標機:Easy-ARM IMX283

目標機內核:Linux 2.6.35.3

 

LCD驅動分析

LCD屏的驅動總體上分成兩塊,一塊是GUI顯示輸出驅動;一塊是觸摸驅動(該部分單獨一節另外描述)。

LCD驅動概念

LCD是Liquid Crystal Display的簡稱,也就是經常所說的液晶顯示器。LCD能夠支持彩色圖像的顯示和視頻的播放,是一種非常重要的輸出設備。如果我們的系統要用GUI(圖形界面接口),比如minigui,MicroWindows。這時LCD設備驅動程序就應該編寫成frambuffer接口,而不是編寫成僅僅操作底層的LCD控制器接口。

framebuffer是Linux系統為顯示設備提供的一個接口,它將顯示緩沖區抽象,屏蔽圖像硬件的底層差異,允許上層應用程序在圖形模式下直接對顯示緩沖區進行操作。framebuffer又叫幀緩沖,是Linux為操作顯示設備提供的一個用戶接口。用戶應用程序可以通過framebuffer透明地訪問不同類型的顯示設備。從這個方面來說,framebuffer是硬件設備顯示緩沖區的抽象。Linux抽象出framebuffer這個幀緩沖區可以供用戶應用程序直接讀寫,通過更改framebuffer中的內容,就可以立刻顯示在LCD顯示屏上。

framebuffer是一個標准的字符設備,主設備號是29,次設備號根據緩沖區的數目而定。framebuffer對應/dev/fb%d設備文件。根據顯卡的多少,設備文件可能是/dev/fb0、/dev/fb1等。緩沖區設備也是一種普通的內存設備,可以直接對其進行讀寫。對用戶程序而言,它和/dev下面的其他設備沒有什么區別,用戶可以把frameBuffer看成一塊內存,既可以寫,又可以讀。顯示器將根據內存數據顯示對應的圖像界面。這一切都由LCD控制器和響應的驅動程序來完成。

 

LCD驅動框架分析

總體上是一個平台設備驅動與字符驅動的組合;

先從LCD的設備 dev/fb* 是怎么實現的來進行追溯;

1.開發板啟動,進行設備注冊;

在  arch/arm/mach-mx28/device.c  設備注冊文件中,注冊LCD的fd平台設備 “mxs-fb”

// mxs-fb平台設備資源定義
static
struct resource framebuffer_resource[] = { { .flags = IORESOURCE_MEM, .start = LCDIF_PHYS_ADDR, .end = LCDIF_PHYS_ADDR + 0x2000 - 1, }, { .flags = IORESOURCE_IRQ, .start = IRQ_LCDIF, .end = IRQ_LCDIF, }, };
// mxs-fb平台設備私有數據,包含顯示屏名稱、分辨率、位寬、時鍾、面板操作等,在drivers/video/mxs/lcd_43wvf1g.c 中通過subsys_initcall 接口將設備私有數據添加到鏈表中
static struct mxs_platform_fb_data mxs_framebuffer_pdata = {
    .list = LIST_HEAD_INIT(mxs_framebuffer_pdata.list),
};

// lcd設備啟動初始化,在 m28evk.c 中
mx28_device_init()會調用
static void __init mx28_init_lcdif(void) { struct platform_device *pdev; pdev = mxs_get_device("mxs-fb", 0);  //獲取匹配的設備結構體,定義在 arch/arm/plat-mxs/device.c 中
if (pdev == NULL || IS_ERR(pdev)) return; pdev->resource = framebuffer_resource; pdev->num_resources = ARRAY_SIZE(framebuffer_resource); pdev->dev.platform_data = &mxs_framebuffer_pdata; mxs_add_device(pdev, 3);  // 添加到設備注冊列表,設備注冊在 arch/arm/plat-mxs/device.c 中實現 通過 device_initcall(mxs_device_init);遍歷設備列表並進行平台設備注冊
}

2.接下來是 platform_driver   mxsfb_driver 的注冊,匹配之后觸發 mxsfb_probe 函數執行以下操作:

進行相關硬件初始化和 framebuffer 設置;

register_framebuffer()  注冊 LCD 屏的 fd 設備;
 
LCD的驅動包含:
drivers/video/mxs/lcd_43wvf1g.c  // LCD設備私有數據,包含名稱、分辨率、位寬、時鍾、面板操作
drivers/video/mxs/lcdif.c       // lcd的一些接口操作
drivers/video/mxs/mxsfb.c      // 

平台設備驅動 platform_driver 注冊  drivers/video/mxs/mxsfb.c 

會編譯成 mxsfb.ko

static struct platform_driver mxsfb_driver = {
    .probe = mxsfb_probe,
    .remove = mxsfb_remove,
    .suspend = mxsfb_suspend,
    .resume = mxsfb_resume,
    .driver = {
           .name = "mxs-fb",    // 與啟動時的平台設備注冊的 platform_device 名稱相同
           .owner = THIS_MODULE,
           },
};

static int __init mxsfb_init(void)
{
    return platform_driver_register(&mxsfb_driver);  // 顯示屏平台設備驅動注冊
}

驅動安裝時與平台設備匹配之后觸發 mxsfb_probe 函數,這個時核心。

static int __devinit mxsfb_probe(struct platform_device *pdev)
{
    int ret = 0;
    struct mxs_fb_data *data;
    struct resource *res;
    struct fb_info *info;
    struct mxs_platform_fb_data *pdata = pdev->dev.platform_data;
    struct mxs_platform_fb_entry *pentry = NULL;

    mydbg("\n");
    if (pdata == NULL) {
        ret = -ENODEV;
        goto out;
    }

    if (default_panel_name) {
        mydbg("default_panel_name=%s\n",default_panel_name);
     // 通過LCD面板名稱匹配獲取面板參數及設置句柄(平台設備私有數據傳遞過來) pentry
= (void *)mxs_lcd_iterate_pdata(pdata, get_matching_pentry_by_name, default_panel_name); if (pentry) { mxs_lcd_move_pentry_up(pentry, pdata); pdata->cur = pentry; } } if (!default_panel_name || !pentry) { mydbg("\n"); pentry = pdata->cur; } if (!pentry || !pentry->init_panel || !pentry->run_panel || !pentry->release_panel) { mydbg("\n"); ret = -EINVAL; goto out; } data = (struct mxs_fb_data *)framebuffer_alloc(sizeof(struct mxs_fb_data) + sizeof(u32) * 256 - sizeof(struct fb_info), &pdev->dev); if (data == NULL) { ret = -ENOMEM; goto out; } cdata = data; data->dev = &pdev->dev; data->pdata = pdata; platform_set_drvdata(pdev, data); info = &data->info; dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n", pentry->x_res, pentry->y_res, pentry->bpp); mxs_lcd_iterate_pdata(pdata, get_max_memsize, data); data->map_size = PAGE_ALIGN(data->mem_size) * NUM_SCREENS; dev_dbg(&pdev->dev, "memory to allocate: %d\n", data->map_size); data->virt_start = dma_alloc_writecombine(&pdev->dev, data->map_size, &data->phys_start, GFP_KERNEL); if (data->virt_start == NULL) { ret = -ENOMEM; goto out_dma; } dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", data->virt_start, data->phys_start); mutex_init(&data->blank_mutex); INIT_WORK(&data->work, mxsfb_task); data->state = F_ENABLE; mxsfb_default.bits_per_pixel = pentry->bpp; /* NB: rotated */ mxsfb_default.xres = pentry->y_res; mxsfb_default.yres = pentry->x_res; mxsfb_default.xres_virtual = pentry->y_res; mxsfb_default.yres_virtual = data->map_size / (pentry->y_res * pentry->bpp / 8); if (mxsfb_default.yres_virtual >= mxsfb_default.yres * 2) mxsfb_default.yres_virtual = mxsfb_default.yres * 2; else mxsfb_default.yres_virtual = mxsfb_default.yres; mxsfb_fix.smem_start = data->phys_start; mxsfb_fix.smem_len = pentry->y_res * pentry->x_res * pentry->bpp / 8; mxsfb_fix.ypanstep = 1; switch (pentry->bpp) { case 32: case 24: mxsfb_default.red.offset = 16; mxsfb_default.red.length = 8; mxsfb_default.green.offset = 8; mxsfb_default.green.length = 8; mxsfb_default.blue.offset = 0; mxsfb_default.blue.length = 8; break; case 16: #if 0 mxsfb_default.red.offset = 11; mxsfb_default.red.length = 5; mxsfb_default.green.offset = 5; mxsfb_default.green.length = 6; mxsfb_default.blue.offset = 0; mxsfb_default.blue.length = 5; break; #else mxsfb_default.red.offset = 0 ; mxsfb_default.red.length = 5; mxsfb_default.green.offset = 5; mxsfb_default.green.length = 6; mxsfb_default.blue.offset = 11; mxsfb_default.blue.length = 5; break; #endif default: dev_err(&pdev->dev, "unsupported bitwidth %d\n", pentry->bpp); ret = -EINVAL; goto out_dma; } info->screen_base = data->virt_start; info->fbops = &mxsfb_ops; info->var = mxsfb_default; info->fix = mxsfb_fix; info->pseudo_palette = &data->par; data->par = NULL; info->flags = FBINFO_FLAG_DEFAULT; init_waitqueue_head(&data->vsync_wait_q); data->vsync_count = 0; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { dev_err(&pdev->dev, "cannot get IRQ resource\n"); ret = -ENODEV; goto out_dma; } data->regbase = (unsigned long)IO_ADDRESS(res->start); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (res == NULL) { dev_err(&pdev->dev, "cannot get IRQ resource\n"); ret = -ENODEV; goto out_dma; } data->irq = res->start; mxsfb_check_var(&info->var, info); ret = fb_alloc_cmap(&info->cmap, 256, 0); if (ret) goto out_cmap; mxsfb_set_par(info); mxs_init_lcdif(); ret = pentry->init_panel(data->dev, data->phys_start, mxsfb_fix.smem_len, pentry); if (ret) { dev_err(&pdev->dev, "cannot initialize LCD panel\n"); goto out_panel; } dev_dbg(&pdev->dev, "LCD panel initialized\n"); init_timings(data); // not effect dotclk mode ret = request_irq(data->irq, lcd_irq_handler, 0, "fb_irq", data); if (ret) { dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", data->irq, ret); goto out_panel; } ret = register_framebuffer(info); // 注冊fb_info if (ret) goto out_irq; pentry->run_panel(); /* REVISIT: temporary workaround for MX23EVK */ mxsfb_disable_controller(data); mxsfb_enable_controller(data); data->cur_phys = data->phys_start; dev_dbg(&pdev->dev, "LCD running now\n"); #ifdef CONFIG_CPU_FREQ mxsfb_nb.fb_data = data; cpufreq_register_notifier(&mxsfb_nb.nb, CPUFREQ_TRANSITION_NOTIFIER); #endif /* CONFIG_CPU_FREQ */ goto out; out_irq: free_irq(data->irq, data); out_panel: fb_dealloc_cmap(&info->cmap); out_cmap: dma_free_writecombine(&pdev->dev, data->map_size, data->virt_start, data->phys_start); out_dma: kfree(data); out: return ret; }

 

待續.....

 

LCD驅動移植總結

1.LCD引腳配置及初始化

上述原理圖包含了數據、時鍾、背光控制、觸摸、復位等引腳的分配,具體有機會在深入理解LCD硬件驅動原理有在進行說明。

在 arch/arm/mach-mx28/mx28evk_pins.c 的 mx28evk_fixed_pins[ ] 引腳列表中添加 LCD 的驅動引腳,相關引腳轉義在  mx28_pins.h 結合 arch/arm/mach/pinctrl.h 實現

#if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
    {
     .name  = "LCD_D00",
     .id    = PINID_LCD_D00,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D01",
     .id    = PINID_LCD_D01,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D02",
     .id    = PINID_LCD_D02,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D03",
     .id    = PINID_LCD_D03,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D04",
     .id    = PINID_LCD_D04,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D05",
     .id    = PINID_LCD_D05,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D06",
     .id    = PINID_LCD_D06,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D07",
     .id    = PINID_LCD_D07,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D08",
     .id    = PINID_LCD_D08,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D09",
     .id    = PINID_LCD_D09,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D10",
     .id    = PINID_LCD_D10,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D11",
     .id    = PINID_LCD_D11,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D12",
     .id    = PINID_LCD_D12,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D13",
     .id    = PINID_LCD_D13,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D14",
     .id    = PINID_LCD_D14,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D15",
     .id    = PINID_LCD_D15,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
/*
    {
     .name  = "LCD_D16",
     .id    = PINID_LCD_D16,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D17",
     .id    = PINID_LCD_D17,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D18",
     .id    = PINID_LCD_D18,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D19",
     .id    = PINID_LCD_D19,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D20",
     .id    = PINID_LCD_D20,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D21",
     .id    = PINID_LCD_D21,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D22",
     .id    = PINID_LCD_D22,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name  = "LCD_D23",
     .id    = PINID_LCD_D23,
     .fun    = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage    = PAD_3_3V,
     .drive    = 1,
     },
*/
    {
     .name = "LCD_RESET",
     .id = PINID_LCD_RESET,
     .fun = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name = "LCD_VSYNC",
     .id   = PINID_LCD_RD_E,
     .fun  = PIN_FUN2,
     .strength = PAD_8MA,
     .voltage = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name = "LCD_HSYNC",
     .id = PINID_LCD_WR_RWN,
     .fun = PIN_FUN2,
     .strength = PAD_8MA,
     .voltage = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name = "LCD_ENABLE",
     .id = PINID_LCD_CS,
     .fun = PIN_FUN2,
     .strength = PAD_8MA,
     .voltage = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name = "LCD_DOTCLK",
     .id = PINID_LCD_RS,
     .fun = PIN_FUN2,
     .strength = PAD_8MA,
     .voltage = PAD_3_3V,
     .drive    = 1,
     },
    {
     .name = "LCD_BACKLIGHT",
     .id = PINID_PWM3,
     .fun = PIN_FUN1,
     .strength = PAD_8MA,
     .voltage = PAD_3_3V,
     .drive    = 1,
     },
#endif

然后通過類似 LCDIF_PHYS_ADDR + BM_LCDIF_CTRL1_RESET 組合就可以操作相關引腳寄存器了

 

2. 驅動移植到drivers 目錄

1).將LCD驅動放到 drivers/video/ 目錄下,本例為 mxs

2).修改 drivers/video/Kconfig,添加如下配置,表示會提取 mxs 驅動的 Kconfig 配置

if ARCH_MXS
source "drivers/video/mxs/Kconfig"
endif

3. 修改板級文件

板級文件有兩個

mach-mx28   // mx28系列特有的

plat-mxs     // fsl通用共有的功能

1)在設備注冊 arch/arm/mach-mx28/device.c 中添加 mxs LCD 平台設備注冊

#if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
// LCD平台設備資源 resource static struct resource framebuffer_resource[] = { { .flags = IORESOURCE_MEM,    //尋址地址空間資源

     .start = LCDIF_PHYS_ADDR,
     .end   = LCDIF_PHYS_ADDR + 0x2000 - 1,
     },
    {
     .flags = IORESOURCE_IRQ,   //中斷資源
     .start = IRQ_LCDIF,
     .end   = IRQ_LCDIF,
     },
};

static struct mxs_platform_fb_data mxs_framebuffer_pdata = {
    .list = LIST_HEAD_INIT(mxs_framebuffer_pdata.list),
};

static void __init mx28_init_lcdif(void)
{
    struct platform_device *pdev;
    pdev = mxs_get_device("mxs-fb", 0);  //獲取匹配的設備結構體,定義在 arch/arm/plat-mxs/device.c 中
    if (pdev == NULL || IS_ERR(pdev))
        return;
    pdev->resource = framebuffer_resource;
    pdev->num_resources = ARRAY_SIZE(framebuffer_resource);
    pdev->dev.platform_data = &mxs_framebuffer_pdata; //設備私有數據鏈表,包含名稱、分辨率、位寬、時鍾、面板操作,在drivers/video/mxs/lcd_43wvf1g.c 中通過subsys_initcall 接口將設備私有數據添加到鏈表中
    mxs_add_device(pdev, 3);  //添加到設備注冊列表
}
#else
static void __init mx28_init_lcdif(void)
{
    ;
}
#endif

 2)在設備列表注冊 arch/arm/plat-mxs/device.c 中添加 mxs LCD 平台設備結構及設備列表匹配信息

#if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE)
// LCD面板平台設備結構體
static struct platform_device mxs_fb = { .name = "mxs-fb",  //平台設備名稱,后面的平台設備驅動名稱要與這個一致 .id = 0, .dev = { .dma_mask = &common_dmamask, .coherent_dma_mask = DMA_BIT_MASK(32), .release = mxs_nop_release, }, }; #endif static struct mxs_dev_lookup dev_lookup[] = { ......
#if defined(CONFIG_FB_MXS) || defined(CONFIG_FB_MXS_MODULE) { .name = "mxs-fb", .size = 1, .pdev = &mxs_fb, }, #endif

...... }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM