在imx7的开发板上使用LCD液晶屏,现在跟踪一下lcd的驱动。
1.注册framebuffer设备,这是一个字符设备。
内核驱动对应的文件为kernel-source/drivers/video/fbdev/core/fbmem.c
module_init(fbmem_init);
static int __init
fbmem_init(void)
{
int ret;
if (!proc_create("fb", 0, NULL, &fb_proc_fops))
return -ENOMEM;
ret = register_chrdev(FB_MAJOR, "fb", &fb_fops);
if (ret) {
printk("unable to get major %d for fb devs\n", FB_MAJOR);
goto err_chrdev;
}
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
ret = PTR_ERR(fb_class);
pr_warn("Unable to create fb class; errno = %d\n", ret);
fb_class = NULL;
goto err_class;
}
return 0;
err_class:
unregister_chrdev(FB_MAJOR, "fb");
err_chrdev:
remove_proc_entry("fb", NULL);
return ret;
}
//The name of this device has nothing to do with the name of the device in /dev.2
register_chrdev(FB_MAJOR, "fb", &fb_fops);
把这个设备注册到系统中,加入到全局变量chrdevs[CHRDEV_MAJOR_HASH_SIZE],这个fb名字跟dev目录下面的名字没有关系。
注册成功后可以在/proc/devices中看到
# cat /proc/devices
Character devices:
29 fb
fb_fops这个是操作函数,当应用操作这类设备的时候会调用这个函数。(后面分析应用调用的时候会用到) 。在linux设备驱动中,所有的显示缓存设备均由framebuffer子系统内部管理,即linux设备驱动框架只认识一个主设备号为29的framebuffer设备。应用层所有针对显示缓存(最多32个)的访问均会推送给fb_fops进行进一步分发操作。
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,
};
新建一个全局的fb_class,用于生成设备文件,后面生成/dev/fb0的时候会用到。
fb_class = class_create(THIS_MODULE, "graphics");
2.lcd驱动分析
在dts文件中查找lcdif的compatible 为"fsl,imx7d-lcdif", "fsl,imx28-lcdif";
内核驱动对应的文件为kernel-source/drivers/video/fbdev/mxsfb.c
lcdif: lcdif@30730000 {
compatible = "fsl,imx7d-lcdif", "fsl,imx28-lcdif";
reg = <0x30730000 0x10000>;
display = <&display0>;
status = "okay";
display0: display {
bits-per-pixel = <32>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: ETM0700G0DH6 {
clock-frequency = <33000000>;
hactive = <800>;
vactive = <480>;
hfront-porch = <40>;
hback-porch = <216>;
hsync-len = <128>;
vback-porch = <35>;
vfront-porch = <10>;
vsync-len = <2>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
};
mxsfb_probe()
{
struct mxsfb_info *host;
struct fb_info *fb_info;
//申请一块内存存放struct mxsfb_info
host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
if (!host) {
dev_err(&pdev->dev, "Failed to allocate IO resource\n");
return -ENOMEM;
}
//申请一块内存存放struct fb_info fb_info->device = &pdev->dev;
//0是fb_info中private data数据的大小
fb_info = framebuffer_alloc(0, &pdev->dev);
if (!fb_info) {
dev_err(&pdev->dev, "Failed to allocate fbdev\n");
devm_kfree(&pdev->dev, host);
return -ENOMEM;
}
host->fb_info = fb_info; //here
fb_info->par = host;
//ioremap映射寄存器地址 lcdif@30730000
//reg = <0x30730000 0x10000>;
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = PTR_ERR(host->base);
goto fb_release;
}
//设备树文件里面读取数据用于初始化
ret = mxsfb_init_fbinfo(host);
//注册fb设备
ret = register_framebuffer(fb_info);
}
主要是两个结构体 struct mxsfb_info, struct fb_info。mxsfb_info包含fb_info,fb_info的par指针指向mxsfb_info。
在分配内存保存struct mxsfb_info, struct fb_info后,ioremap映射寄存器,进入mxsfb_init_fbinfo()对fb_info进行初始化并设置lcd寄存器。
static int mxsfb_init_fbinfo(struct mxsfb_info *host)
{
int ret;
struct fb_info *fb_info = host->fb_info;
struct fb_var_screeninfo *var = &fb_info->var;
struct fb_modelist *modelist;
//操作fb0 通过系统调用,调用fb_fops后最终会执行到这里
fb_info->fbops = &mxsfb_ops; //fb0的操作函数
fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST;
fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
fb_info->fix.ypanstep = 1;
fb_info->fix.ywrapstep = 1;
fb_info->fix.visual = FB_VISUAL_TRUECOLOR,
fb_info->fix.accel = FB_ACCEL_NONE;
//struct videomode -->struct fb_videomode
//mxsfb_init_fbinfo_dt()->fb_videomode_from_videomode(&vm, &fb_vm);计算刷新频率
ret = mxsfb_init_fbinfo_dt(host);//解析device-tree,计算刷新频率
if (host->id < 0)
sprintf(fb_info->fix.id, "mxs-lcdif");
else
sprintf(fb_info->fix.id, "mxs-lcdif%d", host->id);
//注意这个list,fb_info->modelist可以找到fb_videomode
if (!list_empty(&fb_info->modelist)) {
/* first video mode in the modelist as default video mode */
modelist = list_first_entry(&fb_info->modelist,
struct fb_modelist, list);
fb_videomode_to_var(var, &modelist->mode); //初始化struct fb_var_screeninfo
}
/* save the sync value getting from dtb */
host->sync = fb_info->var.sync;
printk("host->id is %d,host->sync is %d\n",host->id,host->sync); //-19
var->nonstd = 0;
var->activate = FB_ACTIVATE_NOW;
var->accel_flags = 0;
var->vmode = FB_VMODE_NONINTERLACED;
/* init the color fields */
mxsfb_check_var(var, fb_info);
fb_info->fix.line_length =
fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3);
//framebuffer缓冲大小
fb_info->fix.smem_len = SZ_32M;
//为framebuffer分配内存空间地址,存放到fbi->fix.smem_start
//fbi->screen_size = fbi->fix.smem_len;
//这个地址会在mmap中用到,应用程序和驱动操作同一块内存
/* Memory allocation for framebuffer */
if (mxsfb_map_videomem(fb_info) < 0)
return -ENOMEM;
//设置LCD控制器信息 设置寄存器
if (mxsfb_restore_mode(host))
memset((char *)fb_info->screen_base, 0, fb_info->fix.smem_len);
return 0;
}
进行完上面的操作后,register_framebuffer(fb_info) 注册这个fb,在dev/目录下生成fb0。
应用程序 可以操作这个文件节点。
register_framebuffer(struct fb_info *fb_info)
{
do_register_framebuffer(fb_info);
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
if (num_registered_fb == FB_MAX)//num_registered_fb 全局变量,最多32个framebuffer
return -ENXIO;
num_registered_fb++;
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i; //这个是次设备号
//fb_class在前面注册fb设备的时候创建
//device_create这里会通过mdev最终在/dev/下面生成fb0
fb_info->dev = device_create(fb_class, fb_info->device,MKDEV(FB_MAJOR, i),
NULL,"fb%d",i); //fb次设备号 -->FB0
fb_init_device(fb_info)
{
//在sys中创建相关的节点
for (i = 0; i < ARRAY_SIZE(device_attrs); i++) {
error = device_create_file(fb_info->dev, &device_attrs[i]);}
}
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info; //最终把这个fb_info加入到全局的registered_fb中
}