十一、三星平台framebuffer驱动

时间:2021-07-04 23:37:36

 

和总线设备驱动模型类似,framebuffer分为核心层、驱动层和设备层。

核心层:就是上一章分析的fbmem.c文件

驱动层(控制器层):一般由芯片原厂提供,实现了LCD控制器通用的操作接口和配置接口,本章用到的是三星提供的s3cfb_main.cs3cfb_ops.c

设备层:一般由单板厂商提供,本章用到的是arch/arm/plat-s5p/dev-fimd-s5p.c文件,后续会分析为什么是它。

考虑到我是用的并不是之前的TINY4412,在此给出上述分析的文件:

https://pan.baidu.com/s/1rjM7_v2DZDgRNUErGxZrQQ

提取码为:v6ed

 

注意:有的开发板的驱动层可能是s3c-fb.c,这要取决于:

1. drivers/video/Makefile中的约束条件,比如我的Makefile是obj-$(CONFIG_FB_S3C) += s3c-fb.o

2. 内核根目录下的.config是否配置了CONFIG_FB_S3C这个宏,比如我的.config是# CONFIG_FB_S3C is not set,未设置

进一步分析.config,我发现了CONFIG_FB_S5P=yCONFIG_FB_S5P_WA101S=y,在drivers/video/Makefile中有obj-$(CONFIG_FB_S5P) += samsung/,故需要查看drivers/video/samsung/Makefile。

经过.config排除drivers/video/samsung/Makefile的宏定义后,Makefile文件最终如下:

ifeq ($(CONFIG_FB_S5P),y)

obj-y               += s3cfb.o
s3cfb-y             := s3cfb_main.o s3cfb_ops.o
obj-$(CONFIG_ARCH_EXYNOS4)  += s3cfb_fimd6x.o

obj-$(CONFIG_FB_S5P_WA101S) += s3cfb_wa101s.o

endif

 

除此之外,我们需要完成的是设备层下的参数层,参数层存储LCD相关的一些时序,如分辨率、BPP等参数。单板厂商提供的是drivers/video/samsung/s3cfb_wa101s.c文件。

把控制器层和设备层分开,是因为芯片的LCD控制器只有一个,而开发板配套LCD选择较多。LCD控制器确定了LCD的操作方式,因此可以被普适化。

对于LCD,我们可以选择4.3寸、7寸等,不同LCD会有一定的差异,此时就可以采取数据总线的形式。我们根据LCD各自的物理特性,把相关参数添加到构建好的平台总线的设备层即可。

 

本章仿照总线设备驱动模型进行分析,首先介绍需要使用的结构体,之后分析platform_driver和platform_device,最后分析参数层。

 

 

一、平台驱动使用的结构体 

1. s3c_platform_fb:总线对应的fb结构体,定义有LCD操作函数

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;  /* 显存的物理起始地址 */
    size_t        pmem_size;    /* 显存的字节大小 */
    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);                       /*  重置LCD */
    int        (*clk_on)(struct platform_device *pdev, struct clk **s3cfb_clk); /*  打开LCD时钟 */
    int        (*clk_off)(struct platform_device *pdev, struct clk **clk);      /*  关闭LCD时钟 */
};

 

2. s3cfb_fimd_desc:单板对应的fb结构体

struct s3cfb_fimd_desc {
    int            state;
    int            dual;
    struct s3cfb_global    *fbdev[FIMD_MAX];    /* 通用的平台fb结构体 */
};

 

3. s3cfb_global:平台通用的fb结构体,定义有LCD参数

struct s3cfb_global {
    void __iomem        *regs;   /* LCD对应的寄存器 */
    struct mutex        lock;    /* 互斥量 */
    struct device        *dev;   /* LCD设备 */
    struct clk        *clock;    /* LCD时钟 */
    int            irq;
    wait_queue_head_t    wq;
    unsigned int        wq_count;
    struct fb_info        **fb;  /* 通用的fb属性,指针数组 */

    atomic_t        enabled_win;
    enum s3cfb_output_t    output;
    enum s3cfb_rgb_mode_t    rgb_mode;
    struct s3cfb_lcd    *lcd;    /* 用于描述LCD物理参数 */
    int             system_state;
#ifdef CONFIG_HAS_WAKELOCK
    struct early_suspend    early_suspend;
    struct wake_lock    idle_lock;
#endif
};

 

4. s3cfb_lcd:描述LCD物理参数,这个结构体是需要我们修改的

struct s3cfb_lcd {
    int    width;        /* 水平像素个数 */
    int    height;       /* 垂直像素个数 */
    int    bpp;          /* 每个像素位数 */
    int    freq;         /* LCD刷新率 */
    struct    s3cfb_lcd_timing timing;      /* LCD时序相关参数 */
    struct    s3cfb_lcd_polarity polarity;  /* 电平是否反转 */
    void    (*init_ldi)(void);
    void    (*deinit_ldi)(void);
};

 

 

二、platform_driver

platform_driver定义在s3cfb_main.c中,其定义如下:

static struct platform_driver s3cfb_driver = {
    .probe        = s3cfb_probe,
    .remove        = s3cfb_remove,
...
    .driver        = {
        .name    = S3CFB_NAME,    /* #define S3CFB_NAME "s3cfb" */
...
    },
};

 

我们首先查看s3cfb_probe()函数:

 1 static int s3cfb_probe(struct platform_device *pdev)
 2 {
 3     struct s3c_platform_fb *pdata = NULL;
 4     struct resource *res = NULL;
 5     struct s3cfb_global *fbdev[2];
 6     int ret = 0;
 7     int i = 0;
 8 ...
 9     /* 分配包含平台设备s3cfb_global指针的结构体 */
10     fbfimd = kzalloc(sizeof(struct s3cfb_fimd_desc), GFP_KERNEL);
11 ...
12     for (i = 0; i < FIMD_MAX; i++) {
13         /* 分配平台设备s3cfb_global,里面包含LCD各种属性 */
14         fbfimd->fbdev[i] = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
15         fbdev[i] = fbfimd->fbdev[i];
16 ...
17         fbdev[i]->dev = &pdev->dev;
18         s3cfb_set_lcd_info(fbdev[i]);    /* 设置s3cfb_global的lcd成员为&wa101:struct s3cfb_global *ctrl; ctrl->lcd = &wa101;  */
19 
20         /* 配置platform_device的引脚和时钟参数 */
21         pdata = to_fb_plat(&pdev->dev);
22         if (pdata->cfg_gpio)
23             pdata->cfg_gpio(pdev);
24 
25         if (pdata->clk_on)
26             pdata->clk_on(pdev, &fbdev[i]->clock);
27 
28         /* 内存映射 */
29         res = platform_get_resource(pdev, IORESOURCE_MEM, i);
30 ...
31         res = request_mem_region(res->start,
32                     res->end - res->start + 1, pdev->name);
33 ...
34         fbdev[i]->regs = ioremap(res->start, res->end - res->start + 1);
35 ...
36         /* irq */
37         fbdev[i]->irq = platform_get_irq(pdev, 0);
38 ...
39         /* fb寄存器设置 */
40         s3cfb_init_global(fbdev[i]);
41 
42         fbdev[i]->system_state = POWER_ON;
43 
44         /* 分配 fb_info */
45         if (s3cfb_alloc_framebuffer(fbdev[i], i)) {
46 ...
47         }
48 
49         /* 注册 fb_info */
50         if (s3cfb_register_framebuffer(fbdev[i])) {
51 ...
52         }
53 
54         /* 使能显示 */
55         s3cfb_set_clock(fbdev[i]);
56         s3cfb_enable_window(fbdev[0], pdata->default_win);
57 ...
58         s3cfb_update_power_state(fbdev[i], pdata->default_win, FB_BLANK_UNBLANK);
59         s3cfb_display_on(fbdev[i]);
60 ...
61     }
62     /* 使能背光 */
63 #ifdef CONFIG_FB_S5P_LCD_INIT
64     /* panel control */
65     if (pdata->backlight_on)
66         pdata->backlight_on(pdev);
67 
68     if (pdata->lcd_on)
69         pdata->lcd_on(pdev);
70 #endif
71 
72     ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
73 ...
74     dev_info(fbdev[0]->dev, "registered successfully\n");
75 ...
76     return 0;
77 }

 

probe()函数所做的事情有:

1. 分配单板对应的s3cfb_fimd_desc

2. 分配平台对应的s3cfb_global

3. 调用s3cfb_set_lcd_info()设置s3cfb_global的lcd成员

十一、三星平台framebuffer驱动十一、三星平台framebuffer驱动
 1 void s3cfb_setup_lcd()
 2 {
 3     int type = get_lcd_type();    /* 获取LCD类型 */
 4 ...
 5     if(0x1 == type)        //7.0
 6     {
 7         wa101.width  = 800;
 8         wa101.height = 1280;
 9         wa101.bpp     = 24;
10         wa101.freq   = 50;    //70;
11     }
12 ...
13 }
14 
15 void s3cfb_set_lcd_info(struct s3cfb_global *ctrl)
16 {
17     s3cfb_setup_lcd();
18 
19     wa101.init_ldi = NULL;
20     ctrl->lcd = &wa101;
21 }
View Code

4. 配置引脚,打开LCD时钟,设置中断

5. 映射寄存器,调用s3cfb_init_global()设置寄存器,有些寄存器的值是通过s3cfb_lcd传入的

十一、三星平台framebuffer驱动十一、三星平台framebuffer驱动
 1 int s3cfb_init_global(struct s3cfb_global *fbdev)
 2 {
 3     fbdev->output = OUTPUT_RGB;
 4     fbdev->rgb_mode = MODE_RGB_P;
 5 
 6     fbdev->wq_count = 0;
 7     init_waitqueue_head(&fbdev->wq);
 8     mutex_init(&fbdev->lock);
 9 
10     /* 写寄存器操作 */
11     s3cfb_set_output(fbdev);        /* 设置输出格式为RGB */
12     s3cfb_set_display_mode(fbdev);    /* 设置RGB数据格式 */
13     s3cfb_set_polarity(fbdev);        /* 设置极性是否反转 */
14     s3cfb_set_timing(fbdev);        /* 设置时序 */
15     s3cfb_set_lcd_size(fbdev);        /* 设置LCD分辨率 */
16 
17     return 0;
18 }
View Code

6. 分配、注册s3cfb_global的fb[i]成员

7. 使能背光和显示

 

综合上一章,我们可以知道probe()函数分配和注册了fb_info,但两函数之间并没有设置fb_info,因此我们需要进一步分析。

分配函数如下:

 1 int s3cfb_alloc_framebuffer(struct s3cfb_global *fbdev, int fimd_id)
 2 {
 3     struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);
 4     int ret = 0;
 5     int i;
 6 
 7     fbdev->fb = kmalloc(pdata->nr_wins * sizeof(struct fb_info *), GFP_KERNEL);
 8 ...
 9     for (i = 0; i < pdata->nr_wins; i++) {
10         fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window), fbdev->dev);
11 ...
12         ret = s3cfb_init_fbinfo(fbdev, i);
13 ...
14         if (i == pdata->default_win)
15             if (s3cfb_map_default_video_memory(fbdev, fbdev->fb[i], fimd_id)) {
16                 ret = -ENOMEM;
17 ...
18             }
19         }
20     }
21 ...
22     return ret;
23 }

 

在framebuffer_alloc()之后,我们需要关注第12行代码:ret = s3cfb_init_fbinfo(fbdev, i);,这个函数就是我们要找的设置fb_info函数。

 1 int s3cfb_init_fbinfo(struct s3cfb_global *fbdev, int id)
 2 {
 3     struct fb_info *fb = fbdev->fb[id];
 4     struct fb_fix_screeninfo *fix = &fb->fix;
 5     struct fb_var_screeninfo *var = &fb->var;
 6     struct s3cfb_window *win = fb->par;
 7     struct s3cfb_alpha *alpha = &win->alpha;
 8     struct s3cfb_lcd *lcd = fbdev->lcd;
 9     struct s3cfb_lcd_timing *timing = &lcd->timing;
10 
11     memset(win, 0, sizeof(struct s3cfb_window));
12     platform_set_drvdata(to_platform_device(fbdev->dev), fb);
13     strcpy(fix->id, S3CFB_NAME);
14 
15     /* fimd specific */
16     win->id = id;
17     win->path = DATA_PATH_DMA;
18     win->dma_burst = 16;
19     s3cfb_update_power_state(fbdev, win->id, FB_BLANK_POWERDOWN);
20     alpha->mode = PLANE_BLENDING;
21 
22     /* 设置fbinfo */
23     fb->fbops = &s3cfb_ops;
24     fb->flags = FBINFO_FLAG_DEFAULT;
25     fb->pseudo_palette = &win->pseudo_pal;
26 #if (CONFIG_FB_S5P_NR_BUFFERS != 1)
27     fix->xpanstep = 2;
28     fix->ypanstep = 1;
29 #else
30     fix->xpanstep = 0;
31     fix->ypanstep = 0;
32 #endif
33     fix->type = FB_TYPE_PACKED_PIXELS;
34     fix->accel = FB_ACCEL_NONE;
35     fix->visual = FB_VISUAL_TRUECOLOR;
36     var->xres = lcd->width;
37     var->yres = lcd->height;
38 
39 #if defined(CONFIG_FB_S5P_VIRTUAL)
40     var->xres_virtual = CONFIG_FB_S5P_X_VRES;
41     var->yres_virtual = CONFIG_FB_S5P_Y_VRES * CONFIG_FB_S5P_NR_BUFFERS;
42 #else
43     var->xres_virtual = var->xres;
44     var->yres_virtual = var->yres * CONFIG_FB_S5P_NR_BUFFERS;
45 #endif
46     var->bits_per_pixel = 32;
47     var->xoffset = 0;
48     var->yoffset = 0;
49     var->width = 0;
50     var->height = 0;
51     var->transp.length = 0;
52 
53     fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
54     fix->smem_len = fix->line_length * var->yres_virtual;
55 
56     var->nonstd = 0;
57     var->activate = FB_ACTIVATE_NOW;
58     var->vmode = FB_VMODE_NONINTERLACED;
59     var->hsync_len = timing->h_sw;
60     var->vsync_len = timing->v_sw;
61     var->left_margin = timing->h_bp;
62     var->right_margin = timing->h_fp;
63     var->upper_margin = timing->v_bp;
64     var->lower_margin = timing->v_fp;
65     var->pixclock = (lcd->freq *
66         (var->left_margin + var->right_margin
67         + var->hsync_len + var->xres) *
68         (var->upper_margin + var->lower_margin
69         + var->vsync_len + var->yres));
70     var->pixclock = KHZ2PICOS(var->pixclock/1000);
71 
72     s3cfb_set_bitfield(var);
73     s3cfb_set_alpha_info(var, win);
74 
75     return 0;
76 }

读者如果熟悉LCD裸机操作,想更接近底层修改代码,可以修改此函数,在修改前注意备份。

 

 

三、platform_device

之前platform_driver定义的匹配方式是name匹配,搜索后确定platform_device定义在arch/arm/dev-fimd-s5p.c中

十一、三星平台framebuffer驱动

 

platform_device定义如下:

struct platform_device s3c_device_fb = {
    .name        = "s3cfb",
#if defined(CONFIG_ARCH_EXYNOS4)
    .id        = 0,
#else
    .id        = -1,
#endif
    .num_resources    = ARRAY_SIZE(s3cfb_resource),
    .resource    = s3cfb_resource,
    .dev        = {
        .dma_mask        = &fb_dma_mask,
        .coherent_dma_mask    = 0xffffffffUL
    }
};

从定义的变量来看,并没有挂接设备的私有数据到s3c_device_fb变量中,因为platform_device结构体中device结构体的platform_data指针并没有被赋值。

但是前面平台驱动中使用了平台设备的私有数据。我们可以搜索s3c_device_fb.dev.platform_data:

十一、三星平台framebuffer驱动

代码如下:

 1 static struct s3c_platform_fb default_fb_data __initdata = {
 2 #if defined(CONFIG_ARCH_EXYNOS4)
 3     .hw_ver    = 0x70,
 4 #else
 5     .hw_ver    = 0x62,
 6 #endif
 7     .nr_wins    = 5,
 8 #if defined(CONFIG_FB_S5P_DEFAULT_WINDOW)
 9     .default_win    = CONFIG_FB_S5P_DEFAULT_WINDOW,
10 #else
11     .default_win    = 0,
12 #endif
13     .swap        = FB_SWAP_WORD | FB_SWAP_HWORD,
14 };
15 
16 void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)
17 {
18     struct s3c_platform_fb *npd;
19     int i;
20 
21     if (!pd)
22         pd = &default_fb_data;
23 
24     npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
25     if (!npd)
26         printk(KERN_ERR "%s: no memory for platform data\n", __func__);
27     else {
28         for (i = 0; i < npd->nr_wins; i++)
29             npd->nr_buffers[i] = 1;
30 
31 #if defined(CONFIG_FB_S5P_NR_BUFFERS)
32         npd->nr_buffers[npd->default_win] = CONFIG_FB_S5P_NR_BUFFERS;
33 #else
34         npd->nr_buffers[npd->default_win] = 1;
35 #endif
36 
37         s3cfb_get_clk_name(npd->clk_name);
38         npd->cfg_gpio = s3cfb_cfg_gpio;
39         npd->backlight_on = s3cfb_backlight_on;        /* 最终调用的背光打开函数 */
40         npd->backlight_off = s3cfb_backlight_off;    /* 最终调用的背光关闭函数 */
41         npd->lcd_on = s3cfb_lcd_on;        /* 最终调用的LCD打开函数 */
42         npd->lcd_off = s3cfb_lcd_off;    /* 最终调用的LCD关闭函数 */
43         npd->clk_on = s3cfb_clk_on;        /* 最终调用的时钟使能函数 */
44         npd->clk_off = s3cfb_clk_off;
45 
46         s3c_device_fb.dev.platform_data = npd;
47     }
48 }

 

 

四、关系总结

各个结构体关系如下图所示:

十一、三星平台framebuffer驱动

 

上一章的framebuffer设备驱动分为核心、信息(fb_info)和操作(fb_ops)。

本章的在上一章的基础上完成了适配平台的工作,在总线下分为信息(s3c_fb_global)和操作(s3c_platform_fb)。由于同一CPU可以适配多个单板,单板和LCD选择项过多,因此提取单板结构体(s3cfb_fimd_desc)和LCD结构体(s3cfb_lcd)为信息(s3c_fb_global)提供数据。

 

 

五、s3cfb_lcd

此结构体定义在drivers/video/samsung/s3cfb_wa101s.c文件中,其定义如下:

 1 #include "s3cfb.h"
 2 
 3 static struct s3cfb_lcd wa101 = {
 4 ...
 5 /* CONFIG_TOUCHSCREEN_TSC2007=y */
 6 #ifdef CONFIG_TOUCHSCREEN_TSC2007
 7     .width    = 800,
 8     .height = 480,
 9 #endif
10     .bpp    = 24,
11     .freq    = 70,    // 70,
12 
13     .polarity = {
14         .rise_vclk    = 1,
15         .inv_hsync    = 0,
16         .inv_vsync    = 1,
17         .inv_vden    = 0,
18     },
19 
20 };
21 
22 extern int get_lcd_type();
23 
24 void s3cfb_setup_lcd()
25 {
26     int type = get_lcd_type();
27 ...
28     if(0x1 == type)   //7.0
29     {
30         wa101.width     = 800;
31         wa101.height = 1280;
32         wa101.bpp     = 24;
33         wa101.freq     = 50;    //70;
34     }
35 ...
36 }
37 
38 /* name should be fixed as 's3cfb_set_lcd_info' */
39 void s3cfb_set_lcd_info(struct s3cfb_global *ctrl)
40 {
41     s3cfb_setup_lcd();
42 
43     wa101.init_ldi = NULL;
44     ctrl->lcd = &wa101;
45 }

之前分析过,probe()会调用s3cfb_set_lcd_info()函数。故最终LCD参数为:

 1 static struct s3cfb_lcd wa101 = {
 2     .width     = 800;
 3     .height     = 1280;
 4     .bpp     = 24;
 5     .freq     = 50;
 6 
 7     .polarity = {
 8         .rise_vclk    = 1,
 9         .inv_hsync    = 0,
10         .inv_vsync    = 1,
11         .inv_vden    = 0,
12     },
13 };

 

 

下一章  十二、使用PWM调整LCD背光亮度