基于frame buffer架构的lcd驱动
来源:互联网 发布:印度撤军知乎 编辑:程序博客网 时间:2024/05/29 08:42
硬件:LQ035HC111 ,3.45” TFT LCD Module ,s3c2440
内核版本2.6
(一)frame buffer架构
在driver/video/fbmem.c 中完成对对framebuffer的初始化,初始化函数为fbmem_init如下:
static int __init
fbmem_init(void)
{
create_proc_read_entry("fb", 0, NULL, fbmem_read_proc, NULL);
devfs_mk_dir("fb");
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字符设备好的申请,添加和注册,主设备好为29 ,次设备号为0-255,该函数并没有创建设备节点,设备节点是在
in
register_framebuffer(struct fb_info *fb_info)函数里创建的一个节点对应一个struct fb_info 结构,该函数也位于driver/video/fbmem.c下
函数为fb设备提供了统一的ops egister_chrdev(FB_MAJOR,"fb",&fb_fops))函数实现了cdev和fb_fops的绑定
static struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.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
};
该函数实现了对打开节点的对应操作,应用层上的open函数最终打开对应的fb_open
static int
fb_open(struct inode *inode, struct file *file)
{
int fbidx = iminor(inode);
struct fb_info *info;
int res = 0;
if (fbidx >= FB_MAX)
return -ENODEV;
#ifdef CONFIG_KMOD
if (!(info = registered_fb[fbidx])) //找到对应节点的struct fb_info 结构,该结构在驱动程序s3c2410fb_probe函数中完成填充和注册.
try_to_load(fbidx);
#endif /* CONFIG_KMOD */
if (!(info = registered_fb[fbidx]))
return -ENODEV;
if (!try_module_get(info->fbops->owner))
return -ENODEV;
if (info->fbops->fb_open) { //如果fbops中实现了自己的fb_open,则调用
res = info->fbops->fb_open(info,1) //最终调用struct fb_info 中添加的fbops操作函数中的fb_open函数
if (res)
module_put(info->fbops->owner);
}
return res;
}
应用层write()调用fbmem.c中的fb_write():
static ssize_t
fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) //想分配lcd dma内存偏移ppos的地方写入buf中的count字节
{
unsigned long p = *ppos;
struct inode *inode = file->f_dentry->d_inode;
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
u32 *buffer, *src;
u32 __iomem *dst;
int c, i, cnt = 0, err;
unsigned long total_size;
if (!info || !info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_write) //如果fb_info->fbops 实现了fb_write则调用,s3c2410fb.c中没有实现该函数
return info->fbops->fb_write(file, buf, count, ppos);
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p > total_size)
return -ENOSPC;
if (count >= total_size)
count = total_size;
err = 0;
if (count + p > total_size) {
count = total_size - p;
err = -ENOSPC;
}
cnt = 0;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
dst = (u32 __iomem *) (info->screen_base + p); 要写入数据的地址
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
src = buffer;
if (copy_from_user(src, buf, c)) {
err = -EFAULT;
break;
}
for (i = c >> 2; i--; )
fb_writel(*src++, dst++);
if (c & 3) {
u8 *src8 = (u8 *) src;
u8 __iomem *dst8 = (u8 __iomem *) dst;
for (i = c & 3; i--; )
fb_writeb(*src8++, dst8++);
dst = (u32 __iomem *) dst8;
}
*ppos += c;
buf += c;
cnt += c;
count -= c;
}
kfree(buffer);
return (err) ? err : cnt;
}
fb_read 被用户层的read()函数调用
static ssize_t
fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) //从lcd dma 缓冲起始位置偏移ppos读取count字节到用户空间的 buf中
{
unsigned long p = *ppos;
struct inode *inode = file->f_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;
if (!info || ! info->screen_base)
return -ENODEV;
if (info->state != FBINFO_STATE_RUNNING)
return -EPERM;
if (info->fbops->fb_read) //同上面的一样如果fb_info->fbops实现了fb_read则调用,否则进行默认的操作
return info->fbops->fb_read(file, buf, count, ppos);
total_size = info->screen_size;
if (total_size == 0)
total_size = info->fix.smem_len;
if (p >= total_size)
return 0;
if (count >= total_size)
count = total_size;
if (count + p > total_size)
count = total_size - p;
cnt = 0;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
if (!buffer)
return -ENOMEM;
src = (u32 __iomem *) (info->screen_base + p);
if (info->fbops->fb_sync)
info->fbops->fb_sync(info);
while (count) {
c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
dst = buffer;
for (i = c >> 2; i--; )
*dst++ = fb_readl(src++);
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;
}
static int
fb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, //同样由用户层的ioctl()调用,通过传递不同的命令实现对底层的操作
unsigned long arg)
{
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
struct fb_ops *fb = info->fbops;
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
struct fb_con2fbmap con2fb;
struct fb_cmap_user cmap;
struct fb_event event;
void __user *argp = (void __user *)arg;
int i;
if (!fb)
return -ENODEV;
switch (cmd) {
case FBIOGET_VSCREENINFO:
return copy_to_user(argp, &info->var, //获取fb_var_screeninfo 的信息到用户空间
sizeof(var)) ? -EFAULT : 0;
case FBIOPUT_VSCREENINFO:
if (copy_from_user(&var, argp, sizeof(var))) //将用户空间的var填充到fb_info中
return -EFAULT;
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
i = fb_set_var(info, &var);
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
if (i) return i;
if (copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
return 0;
case FBIOGET_FSCREENINFO: //获取固定的fix信息
return copy_to_user(argp, &info->fix,
sizeof(fix)) ? -EFAULT : 0;
case FBIOPUTCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap))) //从用户空间获取cmap 到内核
return -EFAULT;
return (fb_set_user_cmap(&cmap, info));
case FBIOGETCMAP:
if (copy_from_user(&cmap, argp, sizeof(cmap))) //从内核获取cmap到用户空间
return -EFAULT;
return fb_cmap_to_user(&info->cmap, &cmap);
case FBIOPAN_DISPLAY:
if (copy_from_user(&var, argp, sizeof(var)))
return -EFAULT;
acquire_console_sem();
i = fb_pan_display(info, &var); //显示打印屏幕信息,如果info->fbops->fb_pan_display中有对应的实现的话,就调用,没有执行默认操作
release_console_sem();
if (i)
return i;
if (copy_to_user(argp, &var, sizeof(var)))
return -EFAULT;
return 0;
case FBIO_CURSOR:
return -EINVAL;
case FBIOGET_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return -EFAULT;
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
con2fb.framebuffer = -1;
event.info = info;
event.data = &con2fb;
notifier_call_chain(&fb_notifier_list,
FB_EVENT_GET_CONSOLE_MAP, &event);
return copy_to_user(argp, &con2fb,
sizeof(con2fb)) ? -EFAULT : 0;
case FBIOPUT_CON2FBMAP:
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
return - EFAULT;
if (con2fb.console < 0 || con2fb.console > MAX_NR_CONSOLES)
return -EINVAL;
if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX)
return -EINVAL;
#ifdef CONFIG_KMOD
if (!registered_fb[con2fb.framebuffer])
try_to_load(con2fb.framebuffer);
#endif /* CONFIG_KMOD */
if (!registered_fb[con2fb.framebuffer])
return -EINVAL;
event.info = info;
event.data = &con2fb;
return notifier_call_chain(&fb_notifier_list,
FB_EVENT_SET_CONSOLE_MAP,
&event);
case FBIOBLANK:
acquire_console_sem();
info->flags |= FBINFO_MISC_USEREVENT;
i = fb_blank(info, arg); //如果info->fbops->fb_blank实现的话就调用,或则执行默认操作
info->flags &= ~FBINFO_MISC_USEREVENT;
release_console_sem();
return i;
default:
if (fb->fb_ioctl == NULL)
return -EINVAL;
return fb->fb_ioctl(inode, file, cmd, arg, info); //自己之定义的一些操作
}
}
LQ035HC111驱动
/* LCD Controller */
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C2410_PA_LCD //lcd 的物理内存地址,在驱动中要申请映射,
.end = S3C2410_PA_LCD + S3C24XX_SZ_LCD,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD, //对应的中断
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
}
};
在arch/arm/mach-s2c32410/devs.c 中添加设备
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
}
};
EXPORT_SYMBOL(s3c_device_lcd);
在arch/arm/mach-s3c2410/mach-utu2440.c中添加:
//*****************************************************************
289 //lq035nc111 LCD Parameter:add by pan <panpingsheng@sina.com>
290 //******************************************************************
291 #if defined(CONFIG_FB_LQ035NC111_320X240)
292 static struct s3c2410fb_mach_info utu2440_lcdcfg __initdata = {
293 .regs={
294 .lcdcon1 = S3C2410_LCDCON1_TFT16BPP |\
295 S3C2410_LCDCON1_TFT | \ //TFT 面板
296 S3C2410_LCDCON1_CLKVAL(0x07), //频率6.32 mhz
297
298 .lcdcon2 = S3C2410_LCDCON2_VBPD(15) |\
299 S3C2410_LCDCON2_LINEVAL(239) |\
300 S3C2410_LCDCON2_VFPD(4) | \
301 S3C2410_LCDCON2_VSPW(3),
302
303 .lcdcon3 = S3C2410_LCDCON3_HBPD(38) | \
304 S3C2410_LCDCON3_HOZVAL(319) | \
305 S3C2410_LCDCON3_HFPD(20),
306
307 .lcdcon4 = S3C2410_LCDCON4_HSPW(30),
308
309 .lcdcon5 = S3C2410_LCDCON5_FRM565 |
310 S3C2410_LCDCON5_INVVLINE |
311 S3C2410_LCDCON5_INVVFRAME |
312 S3C2410_LCDCON5_PWREN |
313 S3C2410_LCDCON5_HWSWP,
314
315 },
316
317 .gpccon=0xaa9556a9, //设置GPIO状态
318 .gpccon_mask = 0xffc003fc,
319 .gpcup=0x0000ffff,
320 .gpcup_mask=0xffffffff,
321
322 .gpdcon = 0xaa95aaa1,
323 .gpdcon_mask = 0xffc0fff0,
324 .gpdup = 0x0000faff,
325 .gpdup_mask = 0xffffffff,
326
327 .fixed_syncs = 1,
328 .width = 320,
329 .height = 240,
333 .xres = {
334 .min = 320,
335 .max = 320,
336 .defval = 320,
337 },
338
339 .yres = {
340 .min = 240,
341 .max = 240,
342 .defval = 240,
343 },
344 .bpp = {
345 .min = 16,
346 .max = 16,
347 .defval = 16,
348 },
349 };
350 #endif
注册平台驱动最后进入probe函数中:
该函数主要完成工作填充fb结构,并完成注册,初始化硬件,配置寄存器
int __init s3c2410fb_probe(struct device *dev)
{
struct s3c2410fb_hw *mregs;
int ret;
int i;
mach_info = dev->platform_data;
if (mach_info == NULL) {
dev_err(dev,"no platform data for lcd, cannot attach\n");
return -EINVAL;
}
mregs = &mach_info->regs;
strcpy(info.fb.fix.id, driver_name);
memcpy(&info.regs, &mach_info->regs, sizeof(info.regs));
info.mach_info = dev->platform_data;
info.fb.fix.type = FB_TYPE_PACKED_PIXELS;
info.fb.fix.type_aux = 0;
info.fb.fix.xpanstep = 0;
info.fb.fix.ypanstep = 0;
info.fb.fix.ywrapstep = 0;
info.fb.fix.accel = FB_ACCEL_NONE;
info.fb.var.nonstd = 0;
info.fb.var.activate = FB_ACTIVATE_NOW;
#if 1
info.fb.var.height = mach_info->height;
info.fb.var.width = mach_info->width;
#else
info.fb.var.height = -1;
info.fb.var.width = -1;
#endif
info.fb.var.accel_flags = 0;
info.fb.var.vmode = FB_VMODE_NONINTERLACED;
info.fb.fbops = &s3c2410fb_ops;
info.fb.flags = FBINFO_FLAG_DEFAULT;
info.fb.monspecs = monspecs;
#if 1
info.fb.pseudo_palette = &info.pseudo_pal;
#else
addr = &info;
addr = addr + sizeof(struct s3c2410fb_info);
info.fb.pseudo_palette = addr;
#endif
info.fb.var.xres = mach_info->xres.defval;
info.fb.var.xres_virtual = mach_info->xres.defval;
info.fb.var.yres = mach_info->yres.defval;
info.fb.var.yres_virtual = mach_info->yres.defval;
info.fb.var.bits_per_pixel = mach_info->bpp.defval;
info.fb.var.pixclock = mach_info->pixclock;
info.fb.var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) +1;
info.fb.var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) +1;
info.fb.var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
info.fb.var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
info.fb.var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
info.fb.var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
info.fb.var.red.offset = 11;
info.fb.var.green.offset = 5;
info.fb.var.blue.offset = 0;
info.fb.var.transp.offset = 0;
info.fb.var.red.length = 5;
info.fb.var.green.length = 6;
info.fb.var.blue.length = 5;
info.fb.var.transp.length = 0;
info.fb.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;
info.clk = clk_get(NULL, "lcd");
if (!info.clk || IS_ERR(info.clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
return -ENOENT;
}
clk_use(info.clk);
clk_enable(info.clk);
gprintk("got and enabled clock\n");
msleep(1);
/* Initialize video memory */
ret = s3c2410fb_map_video_memory(&info);
if (ret) {
printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto failed;
}
gprintk("got video memory\n");
ret = s3c2410fb_init_registers(&info);
ret = s3c2410fb_check_var(&info.fb.var, &info.fb);
ret = register_framebuffer(&info.fb);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto failed;
}
/* create device files */
device_create_file(dev, &dev_attr_debug);
printk(KERN_INFO "S3C24X0 fb%d: %s frame buffer device initialize done\n",
info.fb.node, info.fb.fix.id);
return 0;
failed:
return ret;
}
调试中遇到问题
1.调试中出现的问题,会出现滚屏,原因是初始化时没有设置info->fix.line_length 固定参数
2.LPCSEL寄存器一定要清零,否则使能的话屏幕的中间会出现总一条黑带。
3.屏幕不能过低,会出现抖动
4.图像错位
症状
常见的症状如图像左右位移几个像素,上方或下方有一条彩色条纹,或黑色条纹等等
分析
毫无疑问,这种现象肯定是初始化参数设置不对,位置错位,和场同步型号或行同步信号有关,不外乎就是LCD模组和CPU上的LCD控制器的行场同步信号的宽度,前后延迟时间,极性等属性的匹配。这其中,对于图像错位,又以行场前后延迟时间的不匹配可能性最高。
另外,这种情况,通常错位的像素数不会太大,如果出现错位了1/2屏之类的情况,通常就是由别的原因照成的了。
解决
精确适配行场信号,有时候,有些LCD的行场信号的设置还和LCD驱动芯片的部分电压参数的取值设置相关。要协同修改。
- 基于frame buffer架构的lcd驱动
- uboot: lcd frame buffer的保留机理
- LCD Driver 筆記 - Frame Buffer
- LCD frame buffer 白屏 (熄灭)
- LCD Driver 筆記 - Frame Buffer
- Linux Frame Buffer 驱动
- frame buffer驱动
- 基于S3c2440的LCD驱动
- s3c2410_lcd & frame buffer 驱动分析
- s3c2410_lcd & frame buffer 驱动分析
- frame buffer驱动(1)
- 编写基于linux的lcd驱动
- 编写基于Linux的lcd驱动
- 编写基于linux的lcd驱动
- Linux的帧缓冲(Frame Buffer)之三:LCD上显示摄像头
- Linux的帧缓冲(Frame Buffer)之三:LCD上显示摄像头
- Linux的帧缓冲(Frame Buffer)之三:LCD上显示摄像头
- Linux的帧缓冲(Frame Buffer)之三:LCD上显示摄像头
- Android单元测试初探——Instrumentation
- 百度贴吧一键签到
- 使用mallopt调整malloc/new行为
- struts2 国际化
- OJ 2013-1-8第一个版本
- 基于frame buffer架构的lcd驱动
- 在JSP页面获取当前项目名称的方法
- java连接jdbc读取oracle数据库的内容
- reinterpret_cast在编程中的应用
- 应该知道的Linux技巧
- Linux 00
- ASP.NET 中的 ViewState <转>
- 淘宝网的技术发展史(一)——个人网站时代
- JQuery Mobile入门——创建页面pagecreate事件