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

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

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

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

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

https://files.cnblogs.com/files/Lioker/11_fb.zip

注意:有的开发板的驱动层可能是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=y和CONFIG_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[];
int nr_wins; /* 虚拟窗口的个数 */
int nr_buffers[];
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()函数:

 static int s3cfb_probe(struct platform_device *pdev)
{
struct s3c_platform_fb *pdata = NULL;
struct resource *res = NULL;
struct s3cfb_global *fbdev[];
int ret = ;
int i = ;
...
/* 分配包含平台设备s3cfb_global指针的结构体 */
fbfimd = kzalloc(sizeof(struct s3cfb_fimd_desc), GFP_KERNEL);
...
for (i = ; i < FIMD_MAX; i++) {
/* 分配平台设备s3cfb_global,里面包含LCD各种属性 */
fbfimd->fbdev[i] = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
fbdev[i] = fbfimd->fbdev[i];
...
fbdev[i]->dev = &pdev->dev;
s3cfb_set_lcd_info(fbdev[i]); /* 设置s3cfb_global的lcd成员为&wa101:struct s3cfb_global *ctrl; ctrl->lcd = &wa101; */ /* 配置platform_device的引脚和时钟参数 */
pdata = to_fb_plat(&pdev->dev);
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev); if (pdata->clk_on)
pdata->clk_on(pdev, &fbdev[i]->clock); /* 内存映射 */
res = platform_get_resource(pdev, IORESOURCE_MEM, i);
...
res = request_mem_region(res->start,
res->end - res->start + , pdev->name);
...
fbdev[i]->regs = ioremap(res->start, res->end - res->start + );
...
/* irq */
fbdev[i]->irq = platform_get_irq(pdev, );
...
/* fb寄存器设置 */
s3cfb_init_global(fbdev[i]); fbdev[i]->system_state = POWER_ON; /* 分配 fb_info */
if (s3cfb_alloc_framebuffer(fbdev[i], i)) {
...
} /* 注册 fb_info */
if (s3cfb_register_framebuffer(fbdev[i])) {
...
} /* 使能显示 */
s3cfb_set_clock(fbdev[i]);
s3cfb_enable_window(fbdev[], pdata->default_win);
...
s3cfb_update_power_state(fbdev[i], pdata->default_win, FB_BLANK_UNBLANK);
s3cfb_display_on(fbdev[i]);
...
}
/* 使能背光 */
#ifdef CONFIG_FB_S5P_LCD_INIT
/* panel control */
if (pdata->backlight_on)
pdata->backlight_on(pdev); if (pdata->lcd_on)
pdata->lcd_on(pdev);
#endif ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
...
dev_info(fbdev[]->dev, "registered successfully\n");
...
return ;
}

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

1. 分配单板对应的s3cfb_fimd_desc

2. 分配平台对应的s3cfb_global

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

 void s3cfb_setup_lcd()
{
int type = get_lcd_type(); /* 获取LCD类型 */
...
if(0x1 == type) //7.0
{
wa101.width = ;
wa101.height = ;
wa101.bpp = ;
wa101.freq = ; //70;
}
...
} void s3cfb_set_lcd_info(struct s3cfb_global *ctrl)
{
s3cfb_setup_lcd(); wa101.init_ldi = NULL;
ctrl->lcd = &wa101;
}

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

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

 int s3cfb_init_global(struct s3cfb_global *fbdev)
{
fbdev->output = OUTPUT_RGB;
fbdev->rgb_mode = MODE_RGB_P; fbdev->wq_count = ;
init_waitqueue_head(&fbdev->wq);
mutex_init(&fbdev->lock); /* 写寄存器操作 */
s3cfb_set_output(fbdev); /* 设置输出格式为RGB */
s3cfb_set_display_mode(fbdev); /* 设置RGB数据格式 */
s3cfb_set_polarity(fbdev); /* 设置极性是否反转 */
s3cfb_set_timing(fbdev); /* 设置时序 */
s3cfb_set_lcd_size(fbdev); /* 设置LCD分辨率 */ return ;
}

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

7. 使能背光和显示

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

分配函数如下:

 int s3cfb_alloc_framebuffer(struct s3cfb_global *fbdev, int fimd_id)
{
struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);
int ret = ;
int i; fbdev->fb = kmalloc(pdata->nr_wins * sizeof(struct fb_info *), GFP_KERNEL);
...
for (i = ; i < pdata->nr_wins; i++) {
fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window), fbdev->dev);
...
ret = s3cfb_init_fbinfo(fbdev, i);
...
if (i == pdata->default_win)
if (s3cfb_map_default_video_memory(fbdev, fbdev->fb[i], fimd_id)) {
ret = -ENOMEM;
...
}
}
}
...
return ret;
}

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

 int s3cfb_init_fbinfo(struct s3cfb_global *fbdev, int id)
{
struct fb_info *fb = fbdev->fb[id];
struct fb_fix_screeninfo *fix = &fb->fix;
struct fb_var_screeninfo *var = &fb->var;
struct s3cfb_window *win = fb->par;
struct s3cfb_alpha *alpha = &win->alpha;
struct s3cfb_lcd *lcd = fbdev->lcd;
struct s3cfb_lcd_timing *timing = &lcd->timing; memset(win, , sizeof(struct s3cfb_window));
platform_set_drvdata(to_platform_device(fbdev->dev), fb);
strcpy(fix->id, S3CFB_NAME); /* fimd specific */
win->id = id;
win->path = DATA_PATH_DMA;
win->dma_burst = ;
s3cfb_update_power_state(fbdev, win->id, FB_BLANK_POWERDOWN);
alpha->mode = PLANE_BLENDING; /* 设置fbinfo */
fb->fbops = &s3cfb_ops;
fb->flags = FBINFO_FLAG_DEFAULT;
fb->pseudo_palette = &win->pseudo_pal;
#if (CONFIG_FB_S5P_NR_BUFFERS != 1)
fix->xpanstep = ;
fix->ypanstep = ;
#else
fix->xpanstep = ;
fix->ypanstep = ;
#endif
fix->type = FB_TYPE_PACKED_PIXELS;
fix->accel = FB_ACCEL_NONE;
fix->visual = FB_VISUAL_TRUECOLOR;
var->xres = lcd->width;
var->yres = lcd->height; #if defined(CONFIG_FB_S5P_VIRTUAL)
var->xres_virtual = CONFIG_FB_S5P_X_VRES;
var->yres_virtual = CONFIG_FB_S5P_Y_VRES * CONFIG_FB_S5P_NR_BUFFERS;
#else
var->xres_virtual = var->xres;
var->yres_virtual = var->yres * CONFIG_FB_S5P_NR_BUFFERS;
#endif
var->bits_per_pixel = ;
var->xoffset = ;
var->yoffset = ;
var->width = ;
var->height = ;
var->transp.length = ; fix->line_length = var->xres_virtual * var->bits_per_pixel / ;
fix->smem_len = fix->line_length * var->yres_virtual; var->nonstd = ;
var->activate = FB_ACTIVATE_NOW;
var->vmode = FB_VMODE_NONINTERLACED;
var->hsync_len = timing->h_sw;
var->vsync_len = timing->v_sw;
var->left_margin = timing->h_bp;
var->right_margin = timing->h_fp;
var->upper_margin = timing->v_bp;
var->lower_margin = timing->v_fp;
var->pixclock = (lcd->freq *
(var->left_margin + var->right_margin
+ var->hsync_len + var->xres) *
(var->upper_margin + var->lower_margin
+ var->vsync_len + var->yres));
var->pixclock = KHZ2PICOS(var->pixclock/); s3cfb_set_bitfield(var);
s3cfb_set_alpha_info(var, win); return ;
}

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

三、platform_device

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

platform_device定义如下:

struct platform_device s3c_device_fb = {
.name = "s3cfb",
#if defined(CONFIG_ARCH_EXYNOS4)
.id = ,
#else
.id = -,
#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:

代码如下:

 static struct s3c_platform_fb default_fb_data __initdata = {
#if defined(CONFIG_ARCH_EXYNOS4)
.hw_ver = 0x70,
#else
.hw_ver = 0x62,
#endif
.nr_wins = ,
#if defined(CONFIG_FB_S5P_DEFAULT_WINDOW)
.default_win = CONFIG_FB_S5P_DEFAULT_WINDOW,
#else
.default_win = ,
#endif
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
}; void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)
{
struct s3c_platform_fb *npd;
int i; if (!pd)
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 = ; i < npd->nr_wins; i++)
npd->nr_buffers[i] = ; #if defined(CONFIG_FB_S5P_NR_BUFFERS)
npd->nr_buffers[npd->default_win] = CONFIG_FB_S5P_NR_BUFFERS;
#else
npd->nr_buffers[npd->default_win] = ;
#endif s3cfb_get_clk_name(npd->clk_name);
npd->cfg_gpio = s3cfb_cfg_gpio;
npd->backlight_on = s3cfb_backlight_on; /* 最终调用的背光打开函数 */
npd->backlight_off = s3cfb_backlight_off; /* 最终调用的背光关闭函数 */
npd->lcd_on = s3cfb_lcd_on; /* 最终调用的LCD打开函数 */
npd->lcd_off = s3cfb_lcd_off; /* 最终调用的LCD关闭函数 */
npd->clk_on = s3cfb_clk_on; /* 最终调用的时钟使能函数 */
npd->clk_off = s3cfb_clk_off; s3c_device_fb.dev.platform_data = npd;
}
}

四、关系总结

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

上一章的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文件中,其定义如下:

 #include "s3cfb.h"

 static struct s3cfb_lcd wa101 = {
...
/* CONFIG_TOUCHSCREEN_TSC2007=y */
#ifdef CONFIG_TOUCHSCREEN_TSC2007
.width = ,
.height = ,
#endif
.bpp = ,
.freq = , // 70, .polarity = {
.rise_vclk = ,
.inv_hsync = ,
.inv_vsync = ,
.inv_vden = ,
}, }; extern int get_lcd_type(); void s3cfb_setup_lcd()
{
int type = get_lcd_type();
...
if(0x1 == type) //7.0
{
wa101.width = ;
wa101.height = ;
wa101.bpp = ;
wa101.freq = ; //70;
}
...
} /* name should be fixed as 's3cfb_set_lcd_info' */
void s3cfb_set_lcd_info(struct s3cfb_global *ctrl)
{
s3cfb_setup_lcd(); wa101.init_ldi = NULL;
ctrl->lcd = &wa101;
}

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

 static struct s3cfb_lcd wa101 = {
.width = ;
.height = ;
.bpp = ;
.freq = ; .polarity = {
.rise_vclk = ,
.inv_hsync = ,
.inv_vsync = ,
.inv_vden = ,
},
};

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

十一、三星平台framebuffer驱动的更多相关文章

  1. 三星framebuffer驱动代码分析

    一.驱动总体概述 本次的驱动代码是Samsung公司为s5pv210这款SoC编写的framebuffer驱动,对应于s5pv210中的内部外设Display Controller (FIMD)模块. ...

  2. android系统平台显示驱动开发简要:Samsung LCD接口篇『三』

    平台信息: 内核:linux3.4.39系统:android4.4 平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) 欢迎指正错误,共同学习.共同进步!! 关注博主新浪博 ...

  3. Linux Framebuffer驱动剖析之一—软件需求

    嵌入式企鹅圈将以本文作为2015年的终结篇,以回应第一篇<Linux字符设备驱动剖析>.嵌入式企鹅圈一直专注于嵌入式Linux和物联网IOT两方面的原创技术分享,稍后会发布嵌入式企鹅圈的2 ...

  4. Linux Framebuffer驱动剖析之中的一个—软件需求

    嵌入式企鹅圈将以本文作为2015年的终结篇,以回应第一篇<Linux字符设备驱动剖析>.嵌入式企鹅圈一直专注于嵌入式Linux和物联网IOT双方面的原创技术分享,稍后会公布嵌入式企鹅圈的2 ...

  5. android系统平台显示驱动开发简要:LCD驱动调试篇『四』

    平台信息: 内核:linux3.4.39系统:android4.4 平台:S5P4418(cortex a9) 作者:瘋耔(欢迎转载,请注明作者) 欢迎指正错误,共同学习.共同进步!! 关注博主新浪博 ...

  6. LCD framebuffer驱动设计文档

    内容提要:1. android display相关的名词2. 调试LCD驱动需要注意的步骤3. 关于帧缓冲区及I/O内存---------------------------------------- ...

  7. Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用

    深入分析LinuxFramebuffer子系统的驱动框架.接口实现和使用. 一.LinuxFramebuffer的软件需求 上一篇文章详细阐述了LinuxFramebuffer的软件需求(请先理解第一 ...

  8. Framebuffer 驱动学习总结(一) ---- 总体架构及关键结构体

    一.Framebuffer 设备驱动总体架构 帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数 ...

  9. 嵌入式Linux驱动学习之路(十七)驱动程序分层分离概念-平台设备驱动

    平台设备驱动: 包含BUS(总线).DEVICE.DRIVER. DEVICE:硬件相关的代码 DRIVER:比较稳定的代码 BUS有一个driver链表和device链表. ①把device放入bu ...

随机推荐

  1. 关于long_query_time的设置,可不可以说是mysql的一个小小bug呢

    我们知道对对于MySQL的日志功能,我们可以完全自己控制到底写还是不写.一般来说,binlog我们一般会开启,而对于慢查询我们一般会在开发的时候调试和观察SQL语句的执行速度.但今天发现一个问题.在使 ...

  2. 2018-2019-2 20165234 《网络对抗技术》 Exp7 网络欺诈防范

    Exp7  网络欺诈防范 实验内容 1. 简单应用SET工具建立冒名网站 2. ettercap DNS spoof 3. 结合应用两种技术,用DNS spoof引导特定访问到冒名网站 4. 请勿使用 ...

  3. nginx中获取真实的客户端访问IP

    date : 2019-06-28 16:54:50 author: headsen chen notice: 个人原创 1,必需要先搞清楚的基本概念 1.1   什么是remote_addr     ...

  4. selenium元素input的value值设置【node.js版本】

    driver.executeScript(‘document.getElementById(“id”).value=“value”’); 这个操作就类似于//$("#id").va ...

  5. 一百四十六:CMS系统之帖子按照发布时间和评论数量排序

    按照不同选项进行排序 视图 @bp.route('/')def index(): board_id = request.args.get('board_id', type=int, default=N ...

  6. 华为OpenStack开源团队人才招募中

    职位要求: 1. 三年以上软件开发经验,编程技能良好. 2. 熟练使用Python.Java.Go或其他语言开发. 3. 有OpenStack经验或者存储经验优先考虑. 4. 良好的学习和沟通能力,责 ...

  7. asp.net怎样实现批量下载文件(非打包形式下载)

    问题: 我想实现的是一个一个的下载. 比如我有一个文件列表.通过checkbox选择.通过单击下载按钮下载选中文件. 百度到都是用打包形式实现批量下载. 这是我自己写的代码,但是点击下载后只能下载一个 ...

  8. jQuery补充之jQuery扩展/form表单提交/滚动菜单

    jQuery扩展 为了避免重复造轮子,能高效使用别人的代码,所以有了扩展. jQuery扩展有两种方式: 自执行函数方式 定义函数,并执行函数. 自执行函数: (function(jq){ jq.ex ...

  9. vue-cli 引入stylus报错

    在App.vue页面添加以下代码报错: <style lang="stylus" rel="stylesheet/stylus"> </sty ...

  10. Spring的AOP原理

    转自 https://www.tianmaying.com/tutorial/spring-aop AOP是什么? 软件工程有一个基本原则叫做“关注点分离”(Concern Separation),通 ...