目录
///////////////////////////////////////////////////////////////////////
注意LCD相关关键词
MDP: Mobile display processor:移动显示处理器
DSI: Display Serial Interface : 显示器串行接口
MDSS : Multimedia Display sub system : 多媒体显示子系统
DCS (DisplayCommandSet):DCS是一个标准化的命令集,用于命令模式的显示模组
一般情况下,固定电话、对讲机、带实体按键的手机(老人机,户外手机)等等都会使用RGB接口的LCD,而对屏幕要求较高的设备比如目前流行的不带实体按键智能手机,平板电脑,广告机等都会使用HDMI、MIPI、LVDS等接口的LCD屏。
由上图可知,不同接口的LCD有不同的硬件结构,自然而然就有不同的驱动框架:
RGB接口LCD驱动框架:分为两部分
1 核心层驱动
2 LCD控制器驱动
这两部分 核心层驱动已经有内核统一提供了,我们需要完成的是 LCD控制器驱动,大致思路通过platform总线挂在LCD控制器,在
prob中完成 fb_info的创建,初始化,注册等工作。
LVDS、HDMI(DSI)、MIPI(DSI)等接口的LCD驱动框架:分为两部分
1 核心驱动
2 LVDS、HDMI、MIPI接口控制器驱动
本文驱动是基于高通平台msm8909,msm8909LCD接口是MIPI接口,而我们的户外手机对屏幕要求不高,有实体按键,所以用RGB接口的LCD即可,所以CPU与LCD之间需要一个 MIPI转RGB的转换芯片,本文使用ICN6211转换芯片,框图如下:
在Linux设备中,LCD是一个很重要的外设,LCD显示采用了帧缓冲(framebuffer)技术,所以LCD驱动也叫Framebuffer驱动,所以LCD驱动框架就是围绕 帧缓冲 展开工作,下面解释一下什么是帧缓冲,帧缓冲(framebuffer)是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象出来,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关系物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域写入颜色值,对应的颜色会自动在屏幕上显示。帧缓冲为标准字符设备。
帧缓冲设备最关键的一个数据结构体是fb_info结构,。包括了关于帧缓冲设备属性和操作的完整描述,这个结构体的定义如下所示。
/kenrel/include/linux/fb.h
struct fb_info {
...
struct fb_var_screeninfo var;//记录用户可变的显示控制参数
struct fb_fix_screeninfo fix;//记录用户固定的显示控制器的参数
...
struct fb_ops *fbops;//操作函数指针,类似于 file_oparetions
...
};
struct fb_ops {
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
//用于上层用户应用对 framebuffer 的读写
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,size_t count, loff_t *ppos);
//用于应用层对framebuffer进行的控制
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,unsigned long arg);
...
};
注意以下解析中,会经常将LCD设备称呼为FB设备
FB设备驱动大致分为三层
第一层:核心层
第二层:XXX平台驱动,FB驱动层
第三层:具体LCD驱动
第一层:核心层
功能:1为底层设备驱动提供注册接口,为上层用户访问提供访问控制接口
kernel\drivers\video\fbmem.c
第二层:FB驱动层
功能: 1 填充 struct fb_info
2 调用 fbmem.c的 register_framebuffer注册 fb设备
kernel\video\msm\mdss\mdss_fb.c //FB驱动 xxx平台(msm高通平台设备驱动)
第三层:具体LCD驱动
功能:1 实际LCD 初始化信息
kernel\video\msm\mdss\mdss_spi_panel.c
kernel\drivers\video\fbmem.c //核心层
LCD驱动核心层:fbmem.c (input.c):内核为所有LCD及显示器抽象出来的统一的驱动模型,即LCD模型或者称为Framebuffer模型,抽象出来的模型屏蔽掉了硬件的差异,为用户体统一提供LCD显示接口,上层应用可以直接操作读写该接口用来操作LCD设备。
作用:
1 为底层设备驱动提供注册接口
2 为上层用户访问提供访问控制接口
分析fbmem.c
//注册fb_info结构体
int register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
//调用do_register_framebuffer
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
...
//创建设备节点
fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
...
//注册fb_info的时候赋值给registered_fb数组
registered_fb[i] = fb_info;
...
return 0;
}
static struct fb_info *file_fb_info(struct file *file)
{
struct inode *inode = file_inode(file);
int fbidx = iminor(inode);
//registered_fb存放已经注册的fb_info的数组,是在注册fb_info的时候赋值给registered_fb数组的,这里是从已经的注册的 fb_info中选择对应的LCD设备
struct fb_info *info = registered_fb[fbidx];
if (info != file->private_data)
info = NULL;
return info;
}
static ssize_t fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
//定义 fb_info结构体,代表一个特定的LCD设备
struct fb_info *info = file_fb_info(file);
u8 *buffer, *dst;
u8 __iomem *src;
int c, cnt = 0, err = 0;
unsigned long total_size;
...
//如果fb_info结构体中定义了fops成员变量,并且fops中定义了read成员函数,那么就调用info->fbops->fb_read
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
...
while (count) {
...
//否则就执行 file_operations的默认read函数,直接copy_to_user
if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
...
}
return (err) ? err : cnt;
}
//要特别说明一下.read = fb_read,函数
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
fbmem_init(void)
{
proc_create("fb", 0, NULL, &fb_proc_fops);
//创建字符设备,绑定 struct file_operations fb_fops ,证明LCD驱动是字符设备
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
//创建设备类
fb_class = class_create(THIS_MODULE, "graphics");
return 0;
}
kernel\video\msm\mdss\mdss_fb.c //FB驱动 xxx平台(msm高通平台设备驱动)
功能:
1 填充 struct fb_info
2 调用 fbmem.c的 register_framebuffer注册 fb设备
分析mdss_fb.c
static int mdss_fb_register(struct msm_fb_data_type *mfd)
{
struct fb_info *fbi = mfd->fbi;
struct fb_fix_screeninfo *fix;
struct fb_var_screeninfo *var;
//framebuffer fb_info 信息初始化
...
fix->type_aux = 0;
...
var->xoffset = 0
...
fbi->fbops = &mdss_fb_ops;
...
//registers a frame buffer device (fbmem.c中定义)
register_framebuffer(fbi)
}
static int mdss_fb_probe(struct platform_device *pdev)
{
pdata = dev_get_platdata(&pdev->dev);
//注册一个 FB设备
rc = mdss_fb_register(mfd);
}
static const struct dev_pm_ops mdss_fb_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mdss_fb_pm_suspend, mdss_fb_pm_resume)
};
//对应msm8909-mdss.dtsi mdss_fb0: qcom,mdss_fb_primary {
static const struct of_device_id mdss_fb_dt_match[] = {
{ .compatible = "qcom,mdss-fb",},
{}
};
EXPORT_COMPAT("qcom,mdss-fb");
static struct platform_driver mdss_fb_driver = {
.probe = mdss_fb_probe,
.remove = mdss_fb_remove,
.suspend = mdss_fb_suspend,
.resume = mdss_fb_resume,
.shutdown = mdss_fb_shutdown,
.driver = {
.name = "mdss_fb", //对应设备数 msm8909-mdss.dtsi : mdss_mdp: qcom,mdss_mdp@1a00000
.of_match_table = mdss_fb_dt_match,
.pm = &mdss_fb_pm_ops,
},
};
static struct fb_ops mdss_fb_ops = {
.owner = THIS_MODULE,
.fb_open = mdss_fb_open,
.fb_release = mdss_fb_release,
.fb_check_var = mdss_fb_check_var, /* vinfo check */
.fb_set_par = mdss_fb_set_par, /* set the video mode */
.fb_blank = mdss_fb_blank, /* blank display */
.fb_pan_display = mdss_fb_pan_display, /* pan display */
.fb_ioctl = mdss_fb_ioctl, /* perform fb specific ioctl */
#ifdef CONFIG_COMPAT
.fb_compat_ioctl = mdss_fb_compat_ioctl,
#endif
.fb_mmap = mdss_fb_mmap,
};
int __init mdss_fb_init(void)
{
int rc = -ENODEV;
if (platform_driver_register(&mdss_fb_driver))
return rc;
return 0;
}
module_init(mdss_fb_init);
kernel\video\msm\mdss\mdss_spi_panel.c
功能:实际LCD 初始化信息 : 遍历设备树,寻找fb设备节点。初始化lcd屏幕,注册LCD屏幕
int mdss_spi_panel_init(struct device_node *node,struct spi_panel_data *ctrl_pdata,bool cmd_cfg_cont_splash)
{
//遍历设备树 寻找 spi-panel-gc9305-qvga-cmd-dtsi: spi_gc9305_qvga_cmd: qcom,mdss_spi_gc9305_qvga_cmd
panel_name = of_get_property(node, "qcom,mdss-spi-panel-name", NULL);
}
//find device node of spi panel
static struct device_node *mdss_spi_find_panel_of_node(struct platform_device *pdev, char *panel_cfg)
{
...
mdss_node = of_parse_phandle(pdev->dev.of_node,"qcom,mdss-mdp", 0);
}
static int mdss_spi_panel_probe(struct platform_device *pdev)
{
//寻找设备节点
spi_pan_node = mdss_spi_find_panel_of_node(pdev, panel_cfg);
//屏初始化
rc = mdss_spi_panel_init(spi_pan_node, ctrl_pdata, cmd_cfg_cont_splash);
//注册 spi lcd屏
rc = spi_panel_device_register(spi_pan_node, ctrl_pdata);
{
//mdss_fb.c中提供
rc = mdss_register_panel(ctrl_pdev, &(ctrl_pdata->panel_data));
}
}
//msm8909-mdss.dtsi
static const struct of_device_id mdss_spi_panel_match[] = {
{ .compatible = "qcom,mdss-spi-display" },
{},
};
static struct platform_driver this_driver = {
.probe = mdss_spi_panel_probe,
.driver = {
.name = "spi_panel",//设备树中节点之前的名称 : mdss_spi: qcom,mdss_spi {
.owner = THIS_MODULE,
.of_match_table = mdss_spi_panel_match,
},
};
static int __init mdss_spi_display_init(void)
{
int ret;
ret = platform_driver_register(&this_driver);
return 0;
}
module_init(mdss_spi_display_init);
本文只讲框架,调试过程有机会补充,以上。