本篇博客LCD驱动开发环境,内核基于Linux2.6.22,soc基于s3c2440
我们先来看下内核中相关代码,其实我们会发现LCD驱动和前面输入子系统在理念上非常相似(有趣的是,LCD驱动可以基于输入子系统来开发),我个人认为,内核在驱动中最重要的一个思想就是机制和策略分离,将代表着硬件设备(机制)的代码和代表着驱动(策略)的代码尽量剥离开来,然后从大体相同而在细节上不同的驱动之间找出它们的相同代码,将相同的代码构建成一个类似于平台一样的东西,这个平台尽量具有包容性,这样就可以让两端的驱动和设备的代码量减少,有效了减少了内核驱动的冗余代码,也给我们这样的普通开发者一个更友好的开发环境。
类型与输入子系统,LCD驱动也有一个相当于核心层
/driver/video/fbmem.c
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
这个函数向内核申请了主设备号,注册了一个fb_fops,同时还向创建了一个class,但是没有在这个class基础上创建一个设备节点,这是由原因的,稍后我们就会知道
看下这个fb_fops,当我们调用注册在这个平台下的fb(frambuffer)read、write等函数时,对应的系统调用就是fb_fops中的函数
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.ioctl = fb_ioctl,
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
};
我们首先看下open函数
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
//判断次设备号是否大于FB_MAX,这个是由限制的
if (fbidx >= FB_MAX)
return -ENODEV;
//这个是最为核心的部分,从registered_fb这个全局数组中根据次设备号来获取一个info
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
//把info存放在file->private_data中
file->private_data = info;
if (info->fbops->fb_open) {
res = info->fbops->fb_open(info,1);
if (res)
module_put(info->fbops->owner);
}
return res;
}
这个open函数和输入子系统中的open函数其实没有太大区别,都是根据次设备号从一个全局数组中获取一个成员(info)。然后把info保存在file->private_data中。所以我们可能想知道到底是哪个函数对这个registered_fb全局数组进行初始化,这个稍后分析,继续看fb_fops
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
struct inode *inode = file->f_path.dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
u32 *buffer, *dst;
u32 __iomem *src;
int c, i, cnt = 0, err = 0;
unsigned long total_size;
...
//判断info->fbops里有没有fb_read函数,如果有,就直接执行info->fbops函数
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
total_size = info->screen_size;
...
//申请一个buffer,这个buffer等下是作为向用户空间传递信息的载体
//有趣的是这里对buffer的空间大小有要求,所以可以猜测一下,等下信息传递肯定会有一个循序
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
//读操作的src地址是显存的基地址加上传进来的文件指针偏移
src = (u32 __iomem *) (info->screen_base + p);
//如果fbops中有定义fb_sync就执行
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
//因为src和dst都是32位数,所以fb_readl每次读的是四个字节,所以需要对c除以4,
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);
//那如果除以4有余数呢,显然就不可以直接32位一起读了,所以只能以一个字节为单位来读了。
if (c & 3) {
u8 *dst8 = (u8 *) dst;
u8 __iomem *src8 = (u8 __iomem *) src;
for (i = c & 3; i--;)
*dst8++ = fb_readb(src8++);
src = (u32 __iomem *) src8;
}
if (copy_to_user(buf, buffer, c)) {
err = -EFAULT;
break;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
这里对缓冲区的读操作写得非常地巧妙,值得我们好好学习一下。读懂了read函数,write函数就很好懂了,我们来看下之前提到的那个问题,到底是哪个函数初始化了registered_fb这个全局数组
int
register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
if (num_registered_fb == FB_MAX)
return -ENXIO;
//其实我有疑问,在一开始就对num_registered_fb++全局变量加一计数,
//如果最终没有申请成功,也没有看到对这个全局变量进行恢复,所以是不是有问题?
num_registered_fb++;
//找出第一个没有被使用的次设备号
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
fb_info->node = i;
//在fb_class基础上创建一个设备节点(device),在入口函数中我们发现只有register_chrdev和class_create,但是没有device_create。
//原来是把device_create放在了register_framebuffer中。首先这可以说明,一个主设备号可以对应多个次设备号(废话),一个fb_class仅仅只是对应
//着一个主设备号,可以依据不同的次设备号在同一个class基础上创建不同的device
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
//对fb_info进行基本的初始化,代码比较多我给删掉了
...
fb_info->pixmap.offset = 0;
if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;
if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
//将初始化好的fb_info注册到全局数组register_fb中,用次设备号来管理
registered_fb[i] = fb_info;
event.info = fb_info;
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
return 0;
}
那是谁调用了register_framebuffer?这里我们以s3c2410fb.c为例
可以看到是s3c2410fb_probe调用了register_framebuffer,这个文件是建立在平台总线之上
static int __init s3c2410fb_probe(struct platform_device *pdev)
{
struct s3c2410fb_info *info;
struct fb_info *fbinfo;
struct s3c2410fb_hw *mregs;
int ret;
int irq;
int i;
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;
}
mregs = &mach_info->regs;
//从pdev中获取irq
irq = platform_get_irq(pdev, 0);
//申请一个fb_info,为什么要在这里用sizeof(struct s3c2410fb_info)?
//可以进入framebuffer_alloc看一下,size_t size参数表示的是额外申请空间,就是说如果我们把这个参数设置为0
//那么将会申请到sizeof(struct fb_info)大小的空间。那为什么要申请额外空间呢?这个额外空间是给private_data使用的
//在核心层fbmem.c的fb_open函数中,将会把fb_info保存在其中
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo) {
return -ENOMEM;
}
info = fbinfo->par;
info->fb = fbinfo;
info->dev = &pdev->dev;
platform_set_drvdata(pdev, fbinfo);
dprintk("devinit\n");
strcpy(fbinfo->fix.id, driver_name);
memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));
/* Stop the video and unset ENVID if set */
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
info->mach_info = pdev->dev.platform_data;
/*fix对应的是fb_fix_screeninfo,代表着固定参数*/
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;
/*var 代表着可变参数的设置*/
fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.height = mach_info->height;
fbinfo->var.width = mach_info->width;
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;
fbinfo->var.xres = mach_info->xres.defval;
fbinfo->var.xres_virtual = mach_info->xres.defval;
fbinfo->var.yres = mach_info->yres.defval;
fbinfo->var.yres_virtual = mach_info->yres.defval;
fbinfo->var.bits_per_pixel = mach_info->bpp.defval;
fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
fbinfo->var.red.offset = 11; //三种颜色的offset和length是和他的bpp mode有关,我们这里选择的是565
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
fbinfo->var.transp.length = 0;
fbinfo->fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;
for (i = 0; i < 256; i++)
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
ret = -EBUSY;
goto dealloc_fb;
dprintk("got LCD region\n");
ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
info->clk = clk_get(NULL, "lcd");
clk_enable(info->clk);
dprintk("got and enabled clock\n");
msleep(1);
/* Initialize video memory */
ret = s3c2410fb_map_video_memory(info);
dprintk("got video memory\n");
ret = s3c2410fb_init_registers(info);
ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
//初始化fbinfo完毕,将其注册到全局数组中
ret = register_framebuffer(fbinfo);
/* create device files */
device_create_file(&pdev->dev, &dev_attr_debug);
printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);
return 0;
}
如下是s3c2410-lcd对应的resource
/* LCD Controller */
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};
static u64 s3c_device_lcd_dmamask = 0xffffffffUL;
struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};