Linux设备驱动--FrameBuffer的创建方法

时间:2022-07-28 17:54:03

1 环境与简介

    Host:Ubuntu14.04(64bit)

    Target:smdk2440

    Kernel:linux-2.6.39.4

    《Linux设备驱动--LCD平台设备与驱动(smdk2440)》介绍了如何添加LCD平台设备平台驱动的主要流程,但是仅仅是停留在平台设备和驱动的添加方法上,并没有对真正LCD的具体操作进行分析。本文在此基础上,对framebuffer的实现进行进一步的探索。

2 framebuffer简介

    framebuffer其实对显存的一个抽象,操作这个显存就可以直接控制LCD的显示。因此,framebuffer是LCD设备的HAL(硬件抽象层)。关于framebuffer的更多介绍,详见参考资料,尤其是参考资料[3],写的比较有条例,并且比较详细。

    本文不再对framebuffer做更详细的介绍,而重点在于阐述framebuffer是如何被创建的。

3 创建FrameBuffer

    内核匹配平台设备和驱动后,就会调用s3c24xxfb_probe()函数,framebuffer就是在该函数中被创建的,如下第166行所示。

static int __devinit s3c24xxfb_probe(struct platform_device *pdev,
enum s3c_drv_type drv_type)
{
/* 先定义一些局部变量供探测函数使用. */
struct s3c2410fb_info *info;
struct s3c2410fb_display *display;
struct fb_info *fbinfo;
struct s3c2410fb_mach_info *mach_info;
struct resource *res;
int ret;
int irq;
int i;
int size;
u32 lcdcon1;

mach_info = pdev->dev.platform_data;
if (mach_info == NULL) {
dev_err(&pdev->dev,
"no platform data for lcd, cannot attach\n");
return -EINVAL;
}

if (mach_info->default_display >= mach_info->num_displays) {
dev_err(&pdev->dev, "default is %d but only %d displays\n",
mach_info->default_display, mach_info->num_displays);
return -EINVAL;
}

/* 获得在内核中定义的FrameBuffer平台设备的LCD配置信息结构体数据. */
display = mach_info->displays + mach_info->default_display;

/* 在系统定义的LCD平台设备资源中获取LCD中断号[2]P292. */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}

/* 申请s3c2410fb_info结构体空间以存储FrameBuffer设备相关的数据. */
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo)
return -ENOMEM;

platform_set_drvdata(pdev, fbinfo);

info = fbinfo->par;
info->dev = &pdev->dev;
info->drv_type = drv_type;

/* 获取LCD平台设备所使用的I/O端口资源. */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}

/* 申请LCD设备的IO端口所占用的IO空间. */
size = (res->end - res->start) + 1;
info->mem = request_mem_region(res->start, size, pdev->name);
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}

/* 将LCD的IO端口占用的这段IO空间映射到内存虚拟地址. */
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}

info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);

dprintk("devinit\n");

strcpy(fbinfo->fix.id, driver_name);

/* Stop the video */
lcdcon1 = readl(info->io + S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);

fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;

fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;

fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;

for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_regs;
}

/* 从平台时钟队列中获取LCD的时钟. */
info->clk = clk_get(NULL, "lcd");
if (IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = PTR_ERR(info->clk);
goto release_irq;
}

/* 获取时钟后, 使能才可以使用. */
clk_enable(info->clk);
dprintk("got and enabled clock\n");

msleep(1);

info->clk_rate = clk_get_rate(info->clk);

/* find maximum required memory size for display */
for (i = 0; i < mach_info->num_displays; i++) {
unsigned long smem_len = mach_info->displays[i].xres;

smem_len *= mach_info->displays[i].yres;
smem_len *= mach_info->displays[i].bpp;
smem_len >>= 3;
if (fbinfo->fix.smem_len < smem_len)
fbinfo->fix.smem_len = smem_len;
}

/* Initialize video memory
* 分配DRAM内存给FrameBuffer, 并且初始化这段内存. */
ret = s3c2410fb_map_video_memory(fbinfo);
if (ret) {
printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}

dprintk("got video memory\n");

fbinfo->var.xres = display->xres;
fbinfo->var.yres = display->yres;
fbinfo->var.bits_per_pixel = display->bpp;

/* 初始化LCD控制器的相关寄存器. */
s3c2410fb_init_registers(fbinfo);

/* 检查FrameBuffer的相关参数, 如果传递的参数不合法则进行修改. */
s3c2410fb_check_var(&fbinfo->var, fbinfo);

ret = s3c2410fb_cpufreq_register(info);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register cpufreq\n");
goto free_video_memory;
}

/* 注册帧缓冲设备到系统中. */
ret = register_framebuffer(fbinfo);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n",
ret);
goto free_cpufreq;
}

/* create device files
* 对设备文件系统的支持, 创建framebuffer的设备文件. */
ret = device_create_file(&pdev->dev, &dev_attr_debug);
if (ret) {
printk(KERN_ERR "failed to add debug attribute\n");
}

printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);

return 0;

/* 以下是错误处理的跳转点. */
free_cpufreq:
s3c2410fb_cpufreq_deregister(info);
free_video_memory:
s3c2410fb_unmap_video_memory(fbinfo);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq, info);
release_regs:
iounmap(info->io);
release_mem:
release_resource(info->mem);
kfree(info->mem);
dealloc_fb:
platform_set_drvdata(pdev, NULL);
framebuffer_release(fbinfo);
return ret;
}
源文件:drivers/video/s3c2410fb.c

4 FrameBuffer参数来源

    上述构建framebuffer所需要的参数来自哪里呢?下面对此进行特别分析:

(1)定义一个结构体变量smdk2440_lcd_cfg,里面包含了具体的LCD参数,例如widthheight等:

static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {

.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,

.type = S3C2410_LCDCON1_TFT,

.width = 240,
.height = 320,

.pixclock = 166667, /* HCLK 60 MHz, divisor 10 */
.xres = 240,
.yres = 320,
.bpp = 16,
.left_margin = 20,
.right_margin = 8,
.hsync_len = 4,
.upper_margin = 8,
.lower_margin = 7,
.vsync_len = 4,
};

源文件:arch/arm/mach-s3c2440/mach-smdk2440.c

(2)定义一个结构体变量smdk2440_fb_info,里面包含上smdk2440_lcd_cfg

static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,

#if 0
/* currently setup by downloader */
.gpccon = 0xaa940659,
.gpccon_mask = 0xffffffff,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa84aaa0,
.gpdcon_mask = 0xffffffff,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
#endif

.lpcsel = ((0xCE6) & ~7) | 1<<4,
};
源文件:arch/arm/mach-s3c2440/mach-smdk2440.c

(3)将上述smdk2440_fb_info添加到平台设备中:

static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);

platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
源文件:arch/arm/mach-s3c2440/mach-smdk2440.c
(4)在上述 s3c24xxfb_probe()中就可以从平台设备中将 smdk2440_lcd_cfg拿出来配置 framebuffer

5 总结

    每个显示设备都有自己的framebuffer(而不是所有设备共用一个framebuffer),系统中最多可以支持32个显示设备,也就是最多可以有32framebuffer,对应的设备名名称为/dev/fb0/dev/fb31

参考资料

[1]fb_fix_screeninfo fb_var_screeninfo结构体测试 四)

[2]FrameBuffer之fb_fix_screeninfo和fb_var_screeninfo1

[3]LCD 驱动

[4]Frambuffer介绍 

[5]小结 frambuffer 原理及流程 (五)

[6]Linux framebuffer显示bmp图片

[7]全面的framebuffer详解

[8]Linux驱动修炼之道-framebuffer