驅動程序實例(五):LCD驅動程序分析(Samsung LCD)

/************************************************************************************html

*本文爲我的學習記錄,若有錯誤,歡迎指正。linux

*本文參考資料: 
數組

*        朱有鵬嵌入式課程框架

*        https://blog.csdn.net/ultraman_hs/article/details/54987874
ide

************************************************************************************/函數

結合以前對Linux的framebuffer驅動框架的分析(詳見Linux字符設備驅動框架(五):Linux內核的framebuffer驅動框架),本文對LCD的驅動程序進行了分析。學習

本文基於九鼎科技的x210開發板的BSP進行分析,涉及到如下文件: spa

(1)drivers/video/samsung/s3cfb.c:驅動主體 ;
(2)drivers/video/samsung/s3cfb_fimd6x.c:其中包含不少LCD硬件操做的函數;
(2)arch/arm/mach-s5pv210/mach-x210.c:負責提供platform_device的 ;
(3)arch/arm/plat-s5p/devs.c:爲platform_device提供一些硬件描述信息的。.net

Samsung編寫的LCD設備驅動程序是經過Linux內核的platform總線驅動框架實現的,本文將分兩大部分device和driver對其進行分析。code

1. device部分分析

(1) struct platform_device s3c_device_lcd

LCD的設備信息被嵌入在struct platform_device *smdkc110_devices[]數組中,在內核初始化時,內核將調用platform_add_devices()函數將smdkc110_devices[]數組中的全部設備註冊至內核。

//所在文件:/kernel/arch/arm/mach-s5pv210/mach-x210.c

//smdkc110_devices[]數組中包含了開發板的全部設備的設備信息
static struct platform_device *smdkc110_devices[] __initdata = 
{
    ... ...
    &s3c_device_lcd,  //LCD設備信息
    ... ...
}

//開發板初始化函數
static void __init smdkc110_machine_init(void)
{
    ... ...
    platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices)); //向內核註冊smdkc110_devices[]中的全部設備
    ... ...
#ifdef CONFIG_FB_S3C_LTE480WV
    s3cfb_set_platdata(&lte480wv_fb_data); //設置platform_device s3c_device_lcd的platform_data
#endif  

#ifdef CONFIG_FB_S3C_EK070TN93
    smdkv210_backlight_off();
    s3cfb_set_platdata(&ek070tn93_fb_data); //設置platform_device s3c_device_lcd的platform_data
#endif
}

LCD的設備信息以下:

//所在文件:/kernel/arch/arm/plat-s3c24xx/devs.c

//LCD控制器的資源信息
static struct resource s3c_lcd_resource[] = {
    [0] = {
        .start = S3C24XX_PA_LCD,                     //控制器IO端口開始地址(0xf8000000)
        .end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,//控制器IO端口結束地址(1M)
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_LCD,//LCD中斷
        .end   = IRQ_LCD,
        .flags = IORESOURCE_IRQ,
    }

};

struct platform_device s3c_device_lcd = 
{
    .name          = "s3c2410-lcd",    //設備名稱
    .id            = -1,
    .num_resources = ARRAY_SIZE(s3c_lcd_resource),
    .resource      = s3c_lcd_resource, //LCD設備須要的資源
    .dev           = {
        .dma_mask  = &s3c_device_lcd_dmamask,
        .coherent_dma_mask    = 0xffffffffUL
    }
};

(2)struct s3c_platform_fb與s3cfb_set_platdata

在struct platform_device s3c_device_fb裏並無設置platdata的內容,而是經過s3cfb_set_platdata這個函數來單獨設置platdata。由於內核中實現了多個LCD設備的platdata,用戶可經過相應的宏定義來選擇設置。它在啓動時經過smdkc110_machine_init函數加載,保證platdata在內核啓動時就已經設置好。

在移植LCD驅動過程當中,LCD的配置信息在其platdata中進行修改。

經過內核源碼及.config文件的分析,此處LCD的platdata以下:

//所在文件:/kernel/arch/arm/mach-s5pv210/mach-x210.c
//LCD的參數信息
static struct s3cfb_lcd ek070tn93 = {
    .width = S5PV210_LCD_WIDTH,  //LCD分辨率參數,可修改LCD分辨率
    .height = S5PV210_LCD_HEIGHT,
    .bpp = 32,
    .freq = 60,

    .timing = {
        .h_fp    = 210,
        .h_bp    = 38,
        .h_sw    = 10,
        .v_fp    = 22,
        .v_fpe    = 1,
        .v_bp    = 18,
        .v_bpe    = 1,
        .v_sw    = 7,
    },
    .polarity = {
        .rise_vclk = 0,
        .inv_hsync = 1,
        .inv_vsync = 1,
        .inv_vden = 0,
    },
};

//LCD的platdata
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,    //背光點亮函數
    .backlight_onoff    = ek070tn93_backlight_off,//背光熄滅函數
    .reset_lcd    = ek070tn93_reset_lcd,          //LCD復位函數
};

2. driver部分分析

(1)驅動註冊

//所在文件:/kernel/drivers/video/samsung/s3cfb.c
static struct platform_driver s3cfb_driver = {
    .probe = s3cfb_probe,
    .remove = __devexit_p(s3cfb_remove),
    .driver = {
           .name = S3CFB_NAME,
           .owner = THIS_MODULE,
    },
};

static int __init s3cfb_register(void)
{
    platform_driver_register(&s3cfb_driver);

    return 0;
}
static void __exit s3cfb_unregister(void)
{
    platform_driver_unregister(&s3cfb_driver);
}

module_init(s3cfb_register);
module_exit(s3cfb_unregister);

(2)probe函數分析

    static int __devinit s3cfb_probe(struct platform_device *pdev)
    {
        struct s3c_platform_fb *pdata;/*LCD的platdata,LCD屏配置信息結構體*/
        struct s3cfb_global *fbdev;   /*驅動程序全局變量結構體,主要做用是在驅動部分的2個文件(s3cfb.c和s3cfb_fimd6x.c)的函數中作數據傳遞用的*/
        struct resource *res;         /*用來保存從LCD平臺設備中獲取的LCD資源*/
        int i, j, ret = 0;

        fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
        
        fbdev->dev = &pdev->dev;
     ... ...
      /*LCD電源功耗管理*/
        fbdev->regulator = regulator_get(&pdev->dev, "pd");
        ret = regulator_enable(fbdev->regulator);
        
        /*1)獲取LCD配置信息*/
     pdata = to_fb_plat(&pdev->dev);             //獲取LCD的platdata
        fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;//獲取LCD參數信息

     ... ...
        /*配置GPIO端口*/
        if (pdata->cfg_gpio)
            pdata->cfg_gpio(pdev);

        /*設置時鐘參數*/
        if (pdata->clk_on)
            pdata->clk_on(pdev, &fbdev->clock);

        /*獲取LCD平臺設備所使用的IO端口資源,注意這個IORESOURCE_MEM標誌和LCD平臺設備定義中的一致*/
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

        /*申請LCD IO端口所佔用的IO空間(注意理解IO空間和內存空間的區別),request_mem_region定義在ioport.h中*/
        res = request_mem_region(res->start, res->end - res->start + 1, pdev->name);
      

        /*將LCD的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中
         * 注意:IO空間要映射後才能使用,之後對虛擬地址的操做就是對IO空間的操做*/
        fbdev->regs = ioremap(res->start, res->end - res->start + 1);
        
    #ifdef CONFIG_FB_S3C_LTE480WV
        /*設置寄存器初始狀態*/
        s3cfb_pre_init_para(fbdev);
    #endif

        /*設置gamma 值*/
        s3cfb_set_gamma(fbdev);
        /*設置VSYNC中斷*/
        s3cfb_set_vsync_interrupt(fbdev, 1);
        /*設置全局中斷*/
        s3cfb_set_global_interrupt(fbdev, 1);
        /*fb設備參數信息初始化*/
        s3cfb_init_global(fbdev);

        /*爲framebuffer分配空間,進行內存映射,填充fb_info*/
        if (s3cfb_alloc_framebuffer(fbdev)) {
            ret = -ENOMEM;
            goto err_alloc;
        }

        /*註冊fb設備到系統中*/
        if (s3cfb_register_framebuffer(fbdev)) //建立framebuffer設備
     {
            ret = -EINVAL;
            goto err_register;
        }

        s3cfb_set_clock(fbdev);
        s3cfb_set_window(fbdev, pdata->default_win, 1);

        s3cfb_display_on(fbdev);

        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_INIT
        if (pdata->backlight_on)  //打開LCD的背光
            pdata->backlight_on(pdev);

        if (!bootloaderfb && pdata->reset_lcd)
            pdata->reset_lcd(pdev);

        if (pdata->lcd_on)
            pdata->lcd_on(pdev);
    #endif


        /*對設備文件系統的支持,建立fb設備文件*/
        ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
        if (ret < 0)
            dev_err(fbdev->dev, "failed to add sysfs entries\n");

        dev_info(fbdev->dev, "registered successfully\n");

        /*顯示開機logo*/
    #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
        if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) //準備logo
     {
            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);//顯示logo
        }
    #endif

  ... ...
        return 0;
    }

1)s3cfb_register_framebuffer

s3cfb_register_framebuffer()函數中調用register_framebuffer()註冊多個framebuffer設備,對應/dev/fb*,從而應用層能夠設置多個虛擬屏幕達到更好的顯示效果。

2)logo顯示

Linux內核啓動成功後,會在LCD中顯示logo圖標。Linux內核中提供了多個logo文件(/kernel/kernel/drivers/video/logo),內核調用fb_prepare_logo()函數來肯定須要顯示的logo,再調用fb_show_logo()函數顯示logo。用戶可經過宏定義來設置顯示相應的logo圖標。

int fb_prepare_logo(struct fb_info *info, int rotate)
{
    ... ...
    fb_logo.logo = fb_find_logo(depth);//查找須要顯示的logo
    ... ... 
}

const struct linux_logo * __init_refok fb_find_logo(int depth)
{
    const struct linux_logo *logo = NULL;

    if (nologo)
        return NULL;

    if (depth >= 1) {
#ifdef CONFIG_LOGO_LINUX_MONO
        /* Generic Linux logo */
        logo = &logo_linux_mono;
#endif
#ifdef CONFIG_LOGO_SUPERH_MONO
        /* SuperH Linux logo */
        logo = &logo_superh_mono;
#endif
    }
    
    if (depth >= 4) {
#ifdef CONFIG_LOGO_LINUX_VGA16
        /* Generic Linux logo */
        logo = &logo_linux_vga16;
#endif
    ... ...
    return logo;
}
相關文章
相關標籤/搜索