在“linux lcd设备驱动剖析二”文章中,我们详细分析了s3c24xxfb_probe函数。
文章链接:http://blog.csdn.net/lwj103862095/article/details/18189765
s3c2410fb.c中s3c24xxfb_probe是非常重要的函数之一,但仅仅分析probe函数,貌似感觉有点不够过瘾,貌似缺少分析了一个非常重要的成员。在probe函数中有一句:fbinfo->fbops = &s3c2410fb_ops;
static struct fb_ops s3c2410fb_ops = { .owner = THIS_MODULE, .fb_check_var = s3c2410fb_check_var, //设置可变参数 .fb_set_par = s3c2410fb_set_par, //设置固定参数及lcdcon寄存器 .fb_blank = s3c2410fb_blank, //设置是否使能LCD控制器 .fb_setcolreg = s3c2410fb_setcolreg, //设置RGB颜色,实现伪颜色表 .fb_fillrect = cfb_fillrect, //画一个矩形 .fb_copyarea = cfb_copyarea, //Copy data from area to another .fb_imageblit = cfb_imageblit, //Draws a image to the display };
一、s3c2410fb_check_var函数主要根据tq2440_lcd_cfg实例来设置fb_info结构体的可变参数
fb_var_screeninfo结构体各个成员,如xres、yres、bits_per_pixel、height、width 等等,具体分析如下:
/* 此函数的主要功能是设置可变参数var */ static int s3c2410fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct s3c2410fb_info *fbi = info->par; /* platform_data就是tq2440_fb_info结构体实例 */ struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data; struct s3c2410fb_display *display = NULL; /* 在tq2440_fb_info实例里,displays = tq2440_lcd_cfg,default_display = 0 */ struct s3c2410fb_display *default_display = mach_info->displays + mach_info->default_display; /* 在tq2440_fb_info实例里,type = S3C2410_LCDCON1_TFT */ int type = default_display->type; unsigned i; dprintk("check_var(var=%p, info=%p)\n", var, info); /* validate x/y resolution */ /* choose default mode if possible */ /* 如果参数都等于tq2440_fb_info实例里的参数 * 那么赋值给display,此时display指向tq2440_fb_info实例 */ if (var->yres == default_display->yres && var->xres == default_display->xres && var->bits_per_pixel == default_display->bpp) display = default_display; /* 否则从tq2440_fb_info结构体实例中循环匹配,num_displays = 1 */ else for (i = 0; i < mach_info->num_displays; i++) if (type == mach_info->displays[i].type && var->yres == mach_info->displays[i].yres && var->xres == mach_info->displays[i].xres && var->bits_per_pixel == mach_info->displays[i].bpp) { display = mach_info->displays + i; break; } /* 如果匹配不成功,display = NULL 则错误 */ if (!display) { dprintk("wrong resolution or depth %dx%d at %d bpp\n", var->xres, var->yres, var->bits_per_pixel); return -EINVAL; } /* it is always the size as the display */ /* 找到匹配的display后,将实例中的可变参数赋值 */ var->xres_virtual = display->xres; var->yres_virtual = display->yres; var->height = display->height; var->width = display->width; /* copy lcd settings */ var->pixclock = display->pixclock; var->left_margin = display->left_margin; var->right_margin = display->right_margin; var->upper_margin = display->upper_margin; var->lower_margin = display->lower_margin; var->vsync_len = display->vsync_len; var->hsync_len = display->hsync_len; fbi->regs.lcdcon5 = display->lcdcon5; /* set display type */ fbi->regs.lcdcon1 = display->type; var->transp.offset = 0; var->transp.length = 0; /* set r/g/b positions */ switch (var->bits_per_pixel) { case 1: case 2: case 4: var->red.offset = 0; var->red.length = var->bits_per_pixel; var->green = var->red; var->blue = var->red; break; ...... /* TQ2440的LCD就是采用这种模式 */ case 32: /* 24 bpp 888 and 8 dummy */ var->red.length = 8; var->red.offset = 16; var->green.length = 8; var->green.offset = 8; var->blue.length = 8; var->blue.offset = 0; break; } return 0; }tq2440_lcd_cfg实例在arch/arm/mach-s3c2440/mach-tq2440.c中定义
/* LCD driver info */ /* tq2440_lcd_cfg在tq2440_fb_info中被设置 */ static struct s3c2410fb_display tq2440_lcd_cfg __initdata = { .lcdcon5 = S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSWP, .type = S3C2410_LCDCON1_TFT, ...... /* TQ(LCD W4.3)的配置,config_EmbedSky_W43:CONFIG_FB_S3C24X0_TFT480272=y */ #elif defined(CONFIG_FB_S3C24X0_TFT480272) .width = 480, .height = 272, .pixclock = 40000, /* HCLK 100 MHz, divisor 1 */ /* VCLK=HCLK/[(CLKVAL+1)x2],HCLK = 100MHz * 根据LCD手册"WXCAT43-TG6#001_V1.0.pdf"的第22页可得,VCLK = 10MHz * 即10 = 100/[(CLKVAL+1)x2],可得CLKVAL = 4 */ .setclkval = 0x4, .xres = 480, .yres = 272, .bpp = 16, /* 为什么是这样?参考linux/Documentation/fb/framebuffer.txt */ .left_margin = 19, /* 左边沿 : for HFPD*/ .right_margin = 10, /* 右边沿 : for HBPD*/ .hsync_len = 30, /* 水平同步: for HSPW*/ .upper_margin = 4, /* 上边沿 : for VFPD*/ .lower_margin = 2, /* 下边沿 : for VBPD*/ .vsync_len = 8, /* 垂直同步: for VSPW*/ ...... };
二、s3c2410fb_set_par函数先根据var->bits_per_pixel来选择fix.visual,这里bits_per_pixel = 32,
故fix.visual = FB_VISUAL_TRUECOLOR,然后计算一行的字节数,最后调用s3c2410fb_activate_var
函数来激活LCD控制器,即设置各个lcdcon寄存器。
static int s3c2410fb_set_par(struct fb_info *info) { /* 获得刚被s3c2410fb_check_var函数设置过的var */ struct fb_var_screeninfo *var = &info->var; switch (var->bits_per_pixel) { case 32: case 16: case 12: info->fix.visual = FB_VISUAL_TRUECOLOR; /* 真彩色 */ break; case 1: info->fix.visual = FB_VISUAL_MONO01; break; default: info->fix.visual = FB_VISUAL_PSEUDOCOLOR; break; } /* 一行的字节数 = x*bpp/8 = 480*32/8 = 480*4 */ info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; /* activate this new configuration */ s3c2410fb_activate_var(info); return 0; }s3c2410fb_activate_var函数先调用s3c2410fb_calc_pixclk函数来计算LCD时钟频率,然后调用s3c2410fb_calculate_tft_lcd_regs函数来设置lcdcon1~lcdcon5,然后调用writel函数将前面s3c2410fb_calculate_tft_lcd_regs函数设置好的lcdconx写入对应寄存器,接着调用s3c2410fb_set_lcdaddr函数来设置LCDSADDR1、LCDSADDR2、LCDSADDR3寄存器,也就是将之前在probe函数通过s3c2410fb_map_video_memory-->dma_alloc_writecombine函数分配好的“显存”告诉LCD控制器,最后使能LCD控制器。
static void s3c2410fb_activate_var(struct fb_info *info) { /* 在framebuffer_alloc函数里info->par指向了额外多申请 * 内存空间的首地址,即info->par指向s3c2410fb_info结构体 */ struct s3c2410fb_info *fbi = info->par; void __iomem *regs = fbi->io; /* IO基地址 */ /* 设置显示模式为: TFT LCD panel */ int type = fbi->regs.lcdcon1 & S3C2410_LCDCON1_TFT; /* 通过probe函数后platform_data指向tq2440_fb_info结构体实例 */ struct s3c2410fb_mach_info *mach_info = fbi->dev->platform_data; struct s3c2410fb_display *default_display = mach_info->displays + mach_info->default_display; struct fb_var_screeninfo *var = &info->var; /* 计算LCD时钟频率, 在mach_tq2440.c里 pixclock = 40000 */ int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock) / 2; dprintk("%s: var->xres = %d\n", __func__, var->xres); dprintk("%s: var->yres = %d\n", __func__, var->yres); dprintk("%s: var->bpp = %d\n", __func__, var->bits_per_pixel); if (type == S3C2410_LCDCON1_TFT) { s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs); --clkdiv; if (clkdiv < 0) clkdiv = 0; } else { s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs); if (clkdiv < 2) clkdiv = 2; } // fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv); fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(default_display->setclkval); /* write new registers */ dprintk("new register set:\n"); dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1); dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2); dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3); dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4); dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5); /* 禁止视频输出,禁止LCD控制信号 */ writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, regs + S3C2410_LCDCON1); /* 将前面s3c2410fb_calculate_tft_lcd_regs函数设置好的lcdconx写入对应寄存器 */ writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2); writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3); writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4); writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5); /* set lcd address pointers */ s3c2410fb_set_lcdaddr(info); /* 最后使能LCD控制器,即使能视频输出 */ fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID, writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1); }s3c2410fb_calculate_tft_lcd_regs函数比较简单这里就不分析了,这里只分析s3c2410fb_set_lcdaddr函数
static void s3c2410fb_set_lcdaddr(struct fb_info *info) { unsigned long saddr1, saddr2, saddr3; struct s3c2410fb_info *fbi = info->par; void __iomem *regs = fbi->io; /* LCDSADDR1 = 帧缓冲区起始地址再右移1位 */ saddr1 = info->fix.smem_start >> 1; /* LCDSADDR2 = 帧缓冲区结束地址再右移1位 */ saddr2 = info->fix.smem_start; saddr2 += info->fix.line_length * info->var.yres; /* 帧缓冲区大小 */ saddr2 >>= 1; /* LCDSADDR3 = 一行的长度,单位为2字节 */ saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH((info->fix.line_length / 2) & 0x3ff); dprintk("LCDSADDR1 = 0x%08lx\n", saddr1); dprintk("LCDSADDR2 = 0x%08lx\n", saddr2); dprintk("LCDSADDR3 = 0x%08lx\n", saddr3); writel(saddr1, regs + S3C2410_LCDSADDR1); writel(saddr2, regs + S3C2410_LCDSADDR2); writel(saddr3, regs + S3C2410_LCDSADDR3); }三、s3c2410fb_setcolreg函数主要通过red,green,blue三原色构造出val,然后再将val写入pseudo_palette假调色板中。
static int s3c2410fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { struct s3c2410fb_info *fbi = info->par; void __iomem *regs = fbi->io; unsigned int val; /* TQ2440的LCD是FB_VISUAL_TRUECOLOR,即TFT */ switch (info->fix.visual) { case FB_VISUAL_TRUECOLOR: /* true-colour, use pseudo-palette */ if (regno < 16) { u32 *pal = info->pseudo_palette; /* 用red,green,blue三原色构造出val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); pal[regno] = val; } break; ...... default: return 1; /* unknown type */ } return 0; }chan_to_field 函数如下,将具体的RGB数据代入就比较容易理解这个函数了,相应的var.red、var.green、var.blue在s3c2410fb_check_var函数的最后面有设置。
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; }
s3c2410fb_check_var函数设置rgb的部分代码,这里省略了大部分源码,为的是方便参考。
static int s3c2410fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { ....... /* set r/g/b positions */ switch (var->bits_per_pixel) { ....... /* TQ2440的LCD就是采用这种模式 */ case 32: /* 24 bpp 888 and 8 dummy */ var->red.length = 8; var->red.offset = 16; var->green.length = 8; var->green.offset = 8; var->blue.length = 8; var->blue.offset = 0; break; } return 0; }而cfb_fillrect、cfb_copyarea、cfb_imageblit是通用的函数,不用驱动工程师去理会,只需要在加载lcd驱动时,将其对应的模块加载,而要加载模块,必须在编译内核后,再执行make modules,这样就可以得到相应的cfb*.ko了。
到这里s3c2410fb.c内核自带的lcd驱动基本剖析完毕,这里总结一下难点:
一、内核自带的lcd驱动是以平台驱动设备模型来编写的,难点不在框架,框架其实很简单,
1、分配一个fb_info结构体;
2、设置 fb_info结构体;
3、注册;
4、硬件相关的设置
二、好啦,难点就是如何设置fb_info结构体,而fb_info结构体成员那么多,是不每个成员都要一一设置呢?当然不是,
主要设置fb_info结构体的固定参数 fb_fix_screeninfo结构体和可变参数fb_var_screeninfo结构体,还有就是硬件相关
的设置,比如lcd时序参数的设置,也就是要设置lcdcon1~lcdcon5,lcdaddr1~lcdaddr3各个寄存器。