驱动程序实例(五):LCD驱动程序分析(Samsung LCD)

/************************************************************************************

*本文为个人学习记录,如有错误,欢迎指正。

*本文参考资料: 

*        朱有鹏嵌入式课程

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

************************************************************************************/

结合之前对Linux的framebuffer驱动框架的分析(详见Linux字符设备驱动框架(五):Linux内核的framebuffer驱动框架),本文对LCD的驱动程序进行了分析。

本文基于九鼎科技的x210开发板的BSP进行分析,涉及到以下文件: 

(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提供一些硬件描述信息的。

Samsung编写的LCD设备驱动程序是通过Linux内核的platform总线驱动框架实现的,本文将分两大部分device和driver对其进行分析。

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;
}
posted @ 2018-08-16 13:52  LinFeng-Learning  阅读(1081)  评论(0编辑  收藏  举报