一是对LCD及其相关部件的初始化,包括画面缓冲区的创建和对DMA通道的设置;二是对画面缓冲区的读写,具体到代码为read,write等系统调用接口。
这些都是由帧缓冲设备驱动来完成的。帧缓冲设备对应的设备文件通常为/dev/fb031,Linux的帧缓冲设备的驱动主要基于两个文件:
1)linux/include/linux/fb.h;
2)linux/drivers/video/fbmem.c。
帧缓冲设备属于字符设备,采用“文件层-驱动层”的接口方式。
文件fbmem.c中定义了帧缓冲设备的文件层接口file_operations结构体,它对应用程序可见,该结构体的定义如下:
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,
};
在这个结构体中功能函数open()和release()不需要底层的支持,而read(),write(),mmap()则需要调用fb-get-fix(),fb-get-var(),fb-set-var()(这些函数位于结构体fb-info中指针fbops指向的结构体变量中)等与底层LCD硬件相关的函数的支持。另一个功能函数是ioctl(),ioctl()是设备驱动程序中对设备的I/O通道进行管理的函数,应用程序应用ioctl()系统调用来调用fb-get-fix(),fb-get-var(),fb-set-var()等方法来获得和设置结构体fb-info中var,fix和cmap等变量的信息。
帧缓冲设备在驱动层所要做的工作仅仅是对Linux为帧缓冲的驱动层接口fb_info进行初始化,然后调用这两个函数对其注册或注销。
帧缓冲设备驱动层接口直接对LCD设备硬件进行操作,而fbmem.c可以记录和管理多个底层设备驱动。
LCD控制器经常被集成到SOC上作为一个独立硬件模块而存在(成为platform_device),因此,模块加载函数中完成的只是注册平台驱动,而初始化FBI结构体中固定参数和可变参数,申请帧缓冲设备的显示缓冲区,注册缓冲区则交给探测函数完成。
int __init s3c2410fb_init(void)
{
intret = platform_driver_register(&s3c2410fb_driver);
if(ret == 0)
ret= platform_driver_register(&s3c2412fb_driver);
returnret;
}
static void __exit s3c2410fb_cleanup(void)
{
platform_driver_unregister(&s3c2410fb_driver);
platform_driver_unregister(&s3c2412fb_driver);
}
module_init(s3c2410fb_init); 使用insmod 或者modprobe 命令加载时自动调用
module_exit(s3c2410fb_cleanup); 使用rmmod 卸载时自动调用
MODULE_AUTHOR("ArnaudPatard <arnaud.patard@rtp-net.org>, "
"Ben Dooks<ben-linux@fluff.org>");
MODULE_DESCRIPTION("Framebuffer driverfor the s3c2410");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:s3c2410-lcd");
MODULE_ALIAS("platform:s3c2412-lcd");
对应的platform_driver结构体为:
static struct platform_drivers3c2410fb_driver = {
.probe = s3c2410fb_probe,
.remove = __devexit_p(s3c2410fb_remove),
.suspend = s3c2410fb_suspend,
.resume = s3c2410fb_resume,
.driver = {
.name = "s3c2410-lcd", //驱动名
.owner = THIS_MODULE,
},
};
struct platform_device {
constchar * name;
int id;
struct device dev;
u32 num_resources;
structresource * resource;
conststruct platform_device_id *id_entry;
/*arch specific additions */
structpdev_archdata archdata;
};
static struct fb_ops s3c2410fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c2410fb_check_var,
.fb_set_par = s3c2410fb_set_par,
.fb_blank = s3c2410fb_blank,
.fb_setcolreg = s3c2410fb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
那么平台设备的资源呢?
/* LCD Controller */
static u64 s3c_device_lcd_dmamask =0xffffffffUL;
struct platform_device s3c_device_lcd = {
.name = "s3c-lcd",
.id = -1,设备编号,-1表示只有这样一个设备
.num_resources =ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask =&s3c_device_lcd_dmamask,
.coherent_dma_mask= 0xffffffffUL
}
};
/*
* Probe平台驱动探测函数
*/
static int __devinit s3c2410fb_probe(structplatform_device *pdev)
{
returns3c24xxfb_probe(pdev, DRV_S3C2410);
}
static int __devinit s3c24xxfb_probe(structplatform_device *pdev,
enum s3c_drv_type drv_type)
{
structs3c2410fb_info *info;
structs3c2410fb_display *display;
structfb_info *fbinfo;
structs3c2410fb_mach_info *mach_info;
structresource *res;
intret;
intirq;
inti;
intsize;
u32lcdcon1;
mach_info= pdev->dev.platform_data;
if(mach_info == NULL) {
dev_err(&pdev->dev,
"noplatform 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;
}
display= mach_info->displays + mach_info->default_display;
irq= platform_get_irq(pdev, 0);
if(irq < 0) {
dev_err(&pdev->dev,"no irq for device\n");
return-ENOENT;
}
//分配fb_info空间
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;
res= platform_get_resource(pdev, IORESOURCE_MEM, 0);
if(res == NULL) {
dev_err(&pdev->dev,"failed to get memory registers\n");
ret= -ENXIO;
gotodealloc_fb;
}
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;
gotodealloc_fb;
}
info->io= ioremap(res->start, size);
if(info->io == NULL) {
dev_err(&pdev->dev,"ioremap() of registers failed\n");
ret= -ENXIO;
gotorelease_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;
gotorelease_regs;
}
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);
gotorelease_irq;
}
clk_enable(info->clk);
dprintk("gotand 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++) {
unsignedlong 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 */
ret= s3c2410fb_map_video_memory(fbinfo);
if(ret) {
printk(KERN_ERR"Failed to allocate video RAM: %d\n", ret);
ret= -ENOMEM;
gotorelease_clock;
}
dprintk("gotvideo memory\n");
fbinfo->var.xres= display->xres;
fbinfo->var.yres= display->yres;
fbinfo->var.bits_per_pixel= display->bpp;
s3c2410fb_init_registers(fbinfo);
//检查可变参数
s3c2410fb_check_var(&fbinfo->var,fbinfo);
ret= s3c2410fb_cpufreq_register(info);
if(ret < 0) {
dev_err(&pdev->dev,"Failed to register cpufreq\n");
gotofree_video_memory;
}
//注册FBI
ret= register_framebuffer(fbinfo);
if(ret < 0) {
printk(KERN_ERR"Failed to register framebuffer device: %d\n",
ret);
gotofree_cpufreq;
}
/*create device files */
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);
return0;
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);
returnret;
}
static int __devexits3c2410fb_remove(struct platform_device *pdev)
{
structfb_info *fbinfo = platform_get_drvdata(pdev);
structs3c2410fb_info *info = fbinfo->par;
intirq;
unregister_framebuffer(fbinfo);//注销fb_info
s3c2410fb_cpufreq_deregister(info);
s3c2410fb_lcd_enable(info,0);
msleep(1);
s3c2410fb_unmap_video_memory(fbinfo);
if(info->clk) {
clk_disable(info->clk);
clk_put(info->clk);
info->clk= NULL;
}
irq= platform_get_irq(pdev, 0);
free_irq(irq,info);
iounmap(info->io);
release_resource(info->mem);
kfree(info->mem);
platform_set_drvdata(pdev,NULL);
framebuffer_release(fbinfo);
return0;
}