Linux-2.6.38的LCD驱动分析(四)

来源:互联网 发布:加拿大荷兰学院知乎 编辑:程序博客网 时间:2024/05/22 00:54

Linux-2.6.38的LCD驱动分析(四)

参考自:http://blog.chinaunix.net/uid-11114210-id-2907026.html


四、s3cfb_ops变量详解

        在上面的文字中,较为详细的解释了platform device相关的代码,通过上面的代码的执行,一个platform设备(framebuffer被当作了platform设备)就加载到内核中去了。就像一个PCI的网卡被加入到内核一样,不同的是PCI的网卡占用的是PCI总线,内核会直接支持它。而对于platform设备需要用上面软件的方法加载到内核,同PCI网卡一样,设备需要驱动程序,刚才只是将platform设备注册到内核中,现在它还需要驱动程序,本节中就来看看这些驱动。

4.1  struct fb_ops and s3cfb_ops

/*
 * Frame buffer operations
 *
 * LOCKING NOTE: those functions must _ALL_ be called with the console
 * semaphore held, this is the only suitable locking mechanism we have
 * in 2.6. Some may be called at interrupt time at this point though.
 *
 * The exception to this is the debug related hooks.  Putting the fb
 * into a debug state (e.g. flipping to the kernel console) and restoring
 * it must be done in a lock-free manner, so low level drivers should
 * keep track of the initial console (if applicable) and may need to
 * perform direct, unlocked hardware writes in these hooks.
 */
struct fb_ops {
/* open/release and usage marking */
struct module *owner;
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);


/* For framebuffers with strange non linear layouts or that do not
* work with normal memory mapped access
*/
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
  size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
   size_t count, loff_t *ppos);

/* checks var and eventually tweaks it to something supported,
* DO NOT MODIFY PAR */
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

/* set the video mode according to info->var */
int (*fb_set_par)(struct fb_info *info);

/* set color register */
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
   unsigned blue, unsigned transp, struct fb_info *info);


/* set color registers in batch */
int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

/* blank display */
int (*fb_blank)(int blank, struct fb_info *info);

/* pan display */
int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

/* Draws a rectangle */
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
/* Copy data from area to another */
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
/* Draws a image to the display */
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

/* Draws cursor */
int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

/* Rotates the display */
void (*fb_rotate)(struct fb_info *info, int angle);

/* wait for blit idle, optional */
int (*fb_sync)(struct fb_info *info);

/* perform fb specific ioctl (optional) */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);

/* Handle 32bit compat ioctl (optional) */
int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
unsigned long arg);

/* perform fb specific mmap */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

/* get capability given var */
void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
   struct fb_var_screeninfo *var);

/* teardown any resources to do with this framebuffer */
void (*fb_destroy)(struct fb_info *info);

/* called at KDB enter and leave time to prepare the console */
int (*fb_debug_enter)(struct fb_info *info);
int (*fb_debug_leave)(struct fb_info *info);
};

对于s3c6410的framebuffer驱动支持的操作主要有s3cfb_ops变量中定义,该变量类型为struct fb_ops,如上,该类型的定义在include/linux/fb.h文件中。它的相关解释可以http://www.91linux.com/html/article/kernel/20071204/8805.html页面中找到,当然在fb.h中也有很详细的说明。下面看看对于s3c6410的驱动为该framebuffer提供了哪些操作。

struct fb_ops s3cfb_ops = {
.owner  = THIS_MODULE,
.fb_check_var  = s3cfb_check_var,
.fb_set_par  = s3cfb_set_par,
.fb_blank          = s3cfb_blank,
.fb_pan_display= s3cfb_pan_display,
.fb_setcolreg  = s3cfb_setcolreg,
.fb_fillrect          = cfb_fillrect,
.fb_copyarea  = cfb_copyarea,
.fb_imageblit  = cfb_imageblit,
.fb_cursor  = soft_cursor,
.fb_ioctl          = s3cfb_ioctl,
};

上面的代码描述了支持的相关操作,下面主要会解释s3c6410****的函数。这里还有一个问题要说明一下,就是s3cfb_ops是在什么时候被注册的,这个问题的答案可以在s3cfb_probe函数中找到,请查看s3cfb_probe分析的那一小节。

4.2.1 s3cfb_check_var
       在上面的小节中提到对于一个LCD屏来说内核提供了两组数据结构来描述它,一组是可变属(fb_var_screeninfo描述),另一组是不变属性(fb_fix_screeninfo描述)。对于可变属性,应该防止在操作的过程中出现超出法定范围的情况,因此内核应该可以调用相关函数来检测、并将这些属性固定在法定的范围内,完成这个操作的函数就是s3cfb_check_var。
下面简单说明一下该函数要做的事情,在这里最好看着fb_var_screeninfo和fb_info的定义。
/*
 * s3cfb_check_var():
 * Get the video params out of 'var'. If a value doesn't fit, round it up,
 * if it's too big, return -EINVAL.
 *
 */
static int s3cfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
s3cfb_info_t *fbi = (s3cfb_info_t *) info;


DPRINTK("check_var(var=%p, info=%p)\n", var, info);

switch (var->bits_per_pixel) {
case 8:
var->red = s3cfb_rgb_8.red;
var->green = s3cfb_rgb_8.green;
var->blue = s3cfb_rgb_8.blue;
var->transp = s3cfb_rgb_8.transp;
s3cfb_fimd.bytes_per_pixel = 1;
break;

case 16:
var->red = s3cfb_rgb_16.red;
var->green = s3cfb_rgb_16.green;
var->blue = s3cfb_rgb_16.blue;
var->transp = s3cfb_rgb_16.transp;
s3cfb_fimd.bytes_per_pixel = 2;
break;

case 24:
var->red = s3cfb_rgb_24.red;
var->green = s3cfb_rgb_24.green;
var->blue = s3cfb_rgb_24.blue;
var->transp = s3cfb_rgb_24.transp;
s3cfb_fimd.bytes_per_pixel = 4;
break;

case 28:
var->red = s3cfb_rgb_28.red;
var->green = s3cfb_rgb_28.green;
var->blue = s3cfb_rgb_28.blue;
var->transp = s3cfb_rgb_28.transp;
s3cfb_fimd.bytes_per_pixel = 4;
break;

case 32:
var->red = s3cfb_rgb_32.red;
var->green = s3cfb_rgb_32.green;
var->blue = s3cfb_rgb_32.blue;
var->transp = s3cfb_rgb_32.transp;
s3cfb_fimd.bytes_per_pixel = 4;
break;
}

/* WIN0 cannot support alpha channel. */
if( (fbi->win_id == 0) && (var->bits_per_pixel == 28) ){
var->transp.length = 0;
}

return 0;
}

4.2.2 s3cfb_set_par
        该函数的主要工作是重新设置驱动的私有数据信息主要改变的属性有bpp和行的长度(以字节为单位)。这些属性值其实是存放在fb_fix_screeninfo结构中的,前面说过这些值在运行基本是不会改变的,这些不可改变的值又可分为绝对不能改变和允许改变的两种类型,前一种的例子就是帧缓冲区的起始地址,后一种的例子就是在s3cfb_set_par函数中提到的属性。假如应用程序需要修改硬件的显示状态之类的操作,这个函数就显得十分重要。
/*
 *      s3cfb_set_par - Optional function. Alters the hardware state.
 *      @info: frame buffer structure that represents a single frame buffer
 *
 */
static int s3cfb_set_par(struct fb_info *info)
{
struct fb_var_screeninfo *var = &info->var;//可变的数据属性
s3cfb_info_t *fbi = (s3cfb_info_t *) info;


        if (var->bits_per_pixel == 16 || var->bits_per_pixel == 24 || var->bits_per_pixel == 28)
fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR;真彩色
else
fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;伪彩色

fbi->fb.fix.line_length = var->width * s3cfb_fimd.bytes_per_pixel;
修改行长度信息(以字节为单位),计算方法是一行中的(像素总数 * 表达每个像素的位数)/8。
/* activate this new configuration */
s3cfb_activate_var(fbi, var);该函数实际是设置硬件寄存器,解释略
return 0;
}

4.2.3 s3cfb_blank和s3cfb_setcolreg

       对于s3cfb_blank函数实现的功能非常简单,而且也有较详细的说明,因此对它的说明就省略了。s3cfb_setcolreg函数的功能是设置颜色寄存器。它需要6个参数,分别代表寄存器编号,红色,绿色,蓝色,透明和fb_info结构。

/**
 *      s3cfb_blank
 * @blank_mode: the blank mode we want.
 * @info: frame buffer structure that represents a single frame buffer
 *
 * Blank the screen if blank_mode != 0, else unblank. Return 0 if
 * blanking succeeded, != 0 if un-/blanking failed due to e.g. a
 * video mode which doesn't support it. Implements VESA suspend
 * and powerdown modes on hardware that supports disabling hsync/vsync:
 * blank_mode == 2: suspend vsync
 * blank_mode == 3: suspend hsync
 * blank_mode == 4: powerdown
 *
 * Returns negative errno on error, or zero on success.
 *
 */
static int s3cfb_blank(int blank_mode, struct fb_info *info)
{
DPRINTK("blank(mode=%d, info=%p)\n", blank_mode, info);

switch (blank_mode) {
case VESA_NO_BLANKING:/* lcd on, backlight on */
s3cfb_set_lcd_power(1);
s3cfb_set_backlight_power(1);
break;

case VESA_VSYNC_SUSPEND: /* lcd on, backlight off */
case VESA_HSYNC_SUSPEND:
s3cfb_set_lcd_power(1);
s3cfb_set_backlight_power(0);
break;

case VESA_POWERDOWN: /* lcd and backlight off */
s3cfb_set_lcd_power(0);
s3cfb_set_backlight_power(0);
break;

default:
return -EINVAL;
}


return 0;
}


static int s3cfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info)
{
s3cfb_info_t *fbi = (s3cfb_info_t *)info;
unsigned int val = 0;


switch (fbi->fb.fix.visual) {
case FB_VISUAL_TRUECOLOR:真彩色,使用了调色板
if (regno < 16) {
/* Fake palette of 16 colors */ 
unsigned int *pal = fbi->fb.pseudo_palette;

根据颜色值生成需要的数据
val = s3cfb_chan_to_field(red, fbi->fb.var.red);
val |= s3cfb_chan_to_field(green, fbi->fb.var.green);
val |= s3cfb_chan_to_field(blue, fbi->fb.var.blue);
val |= s3cfb_chan_to_field(transp, fbi->fb.var.transp);


pal[regno] = val;
}


break;


case FB_VISUAL_PSEUDOCOLOR:/* This means that the color format isn't 16, 24, 28 bpp. */
/* S3C6410 has 256 palette entries */
if (regno < 256) {
/* When var.bits_per_pixel is 8bp, then WIN0's palette is always set as 16 bit */
val = ((red >> 0) & 0xf800);
val |= ((green >> 5) & 0x07e0);
val |= ((blue >> 11) & 0x001f);


DPRINTK("index = %d, val = 0x%08x\n", regno, val);
s3cfb_update_palette(fbi, regno, val);更新相关寄存器
}

break;

default:
return 1; /* unknown type */
}
return 0;
}

下面来看看对编写应用层程序非常重要的一个函数s3cfb_ioctl,应用程序都是利用ioctl设置Frambuffer的。

int s3cfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
s3cfb_info_t *fbi = container_of(info, s3cfb_info_t, fb);
s3cfb_win_info_t win_info;
s3cfb_color_key_info_t colkey_info;
s3cfb_color_val_info_t colval_info;
s3cfb_dma_info_t dma_info;
s3cfb_next_info_t next_fb_info;
struct fb_var_screeninfo *var= &fbi->fb.var;
unsigned int crt, alpha_level, alpha_mode;


#if defined(CONFIG_S3C6410_PWM)
int brightness;
#endif


#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
unsigned int f_num_val;
#endif


#if defined(CONFIG_FB_S3C_EXT_VIRTUAL_SCREEN)
s3cfb_vs_info_t vs_info;
#endif

switch(cmd){
case S3CFB_GET_INFO:
dma_info.map_dma_f1 = fbi->map_dma_f1;
dma_info.map_dma_f2 = fbi->map_dma_f2;

if(copy_to_user((void *) arg, (const void *) &dma_info, sizeof(s3cfb_dma_info_t)))
return -EFAULT;
break;

case S3CFB_OSD_SET_INFO:
if (copy_from_user(&win_info, (s3cfb_win_info_t *) arg, sizeof(s3cfb_win_info_t)))
return -EFAULT;

s3cfb_init_win(fbi, win_info.bpp, win_info.left_x, win_info.top_y, win_info.width, win_info.height, OFF);
break;

case S3CFB_OSD_START:
s3cfb_onoff_win(fbi, ON);
break;

case S3CFB_OSD_STOP:
s3cfb_onoff_win(fbi, OFF);
break;

case S3CFB_OSD_ALPHA_UP:
alpha_level = readl(S3C_VIDOSD0C + (0x10 * fbi->win_id)) & 0xf;


if (alpha_level < S3CFB_MAX_ALPHA_LEVEL)
alpha_level++;

s3cfb_set_alpha_level(fbi, alpha_level, 1);
break;

case S3CFB_OSD_ALPHA_DOWN:
alpha_level = readl(S3C_VIDOSD0C + (0x10 * fbi->win_id)) & 0xf;

if (alpha_level > 0)
alpha_level--;

s3cfb_set_alpha_level(fbi, alpha_level, 1);
break;

case S3CFB_OSD_ALPHA0_SET:
alpha_level = (unsigned int) arg;


if (alpha_level > S3CFB_MAX_ALPHA_LEVEL)
alpha_level = S3CFB_MAX_ALPHA_LEVEL;

s3cfb_set_alpha_level(fbi, alpha_level, 0);
break;

case S3CFB_OSD_ALPHA1_SET:
alpha_level = (unsigned int) arg;

if (alpha_level > S3CFB_MAX_ALPHA_LEVEL)
alpha_level = S3CFB_MAX_ALPHA_LEVEL;

s3cfb_set_alpha_level(fbi, alpha_level, 1);
break;

case S3CFB_OSD_ALPHA_MODE:
alpha_mode = (unsigned int) arg;
s3cfb_set_alpha_mode(fbi, alpha_mode);
break;

case S3CFB_OSD_MOVE_LEFT:
if (var->xoffset > 0)
var->xoffset--;


s3cfb_set_win_position(fbi, var->xoffset, var->yoffset, var->xres, var->yres);
break;

case S3CFB_OSD_MOVE_RIGHT:
if (var->xoffset < (s3cfb_fimd.width - var->xres))
var->xoffset++;

s3cfb_set_win_position(fbi, var->xoffset, var->yoffset, var->xres, var->yres);
break;

case S3CFB_OSD_MOVE_UP:
if (var->yoffset > 0)
var->yoffset--;

s3cfb_set_win_position(fbi, var->xoffset, var->yoffset, var->xres, var->yres);
break;

case S3CFB_OSD_MOVE_DOWN:
if (var->yoffset < (s3cfb_fimd.height - var->yres))
var->yoffset++;

s3cfb_set_win_position(fbi, var->xoffset, var->yoffset, var->xres, var->yres);
break;

case FBIO_WAITFORVSYNC:
if (get_user(crt, (unsigned int __user *)arg))
return -EFAULT;

return s3cfb_wait_for_vsync();

case S3CFB_COLOR_KEY_START:
s3cfb_onoff_color_key(fbi, ON);
break;

case S3CFB_COLOR_KEY_STOP:
s3cfb_onoff_color_key(fbi, OFF);
break;

case S3CFB_COLOR_KEY_ALPHA_START:
s3cfb_onoff_color_key_alpha(fbi, ON);
break;

case S3CFB_COLOR_KEY_ALPHA_STOP:
s3cfb_onoff_color_key_alpha(fbi, OFF);
break;

case S3CFB_COLOR_KEY_SET_INFO:
if (copy_from_user(&colkey_info, (s3cfb_color_key_info_t *) arg, sizeof(s3cfb_color_key_info_t)))
return -EFAULT;

s3cfb_set_color_key_registers(fbi, colkey_info);
break;

case S3CFB_COLOR_KEY_VALUE:
if (copy_from_user(&colval_info, (s3cfb_color_val_info_t *) arg, sizeof(s3cfb_color_val_info_t)))
return -EFAULT;

s3cfb_set_color_value(fbi, colval_info);
break;

case S3CFB_SET_VSYNC_INT:
s3cfb_fimd.vidintcon0 &= ~S3C_VIDINTCON0_FRAMESEL0_MASK;
s3cfb_fimd.vidintcon0 |= S3C_VIDINTCON0_FRAMESEL0_VSYNC;

if (arg)
s3cfb_fimd.vidintcon0 |= S3C_VIDINTCON0_INTFRMEN_ENABLE;
else
s3cfb_fimd.vidintcon0 &= ~S3C_VIDINTCON0_INTFRMEN_ENABLE;


writel(s3cfb_fimd.vidintcon0, S3C_VIDINTCON0);
break;

case S3CFB_SET_NEXT_FB_INFO:
if (copy_from_user(&next_fb_info, (s3cfb_next_info_t *) arg, sizeof(s3cfb_next_info_t)))
return -EFAULT;


/* check arguments */
if ((next_fb_info.xres + next_fb_info.xoffset) > next_fb_info.xres_virtual ||
(next_fb_info.yres + next_fb_info.yoffset) > next_fb_info.yres_virtual ||
(next_fb_info.xres + next_fb_info.lcd_offset_x ) > s3cfb_fimd.width ||
(next_fb_info.yres + next_fb_info.lcd_offset_y ) > s3cfb_fimd.height) {
printk("Error : S3CFB_SET_NEXT_FB_INFO\n");
  return -EINVAL;
}

fbi->next_fb_info = next_fb_info;
fbi->next_fb_info_change_req = 1;
break;

case S3CFB_GET_CURR_FB_INFO:
next_fb_info.phy_start_addr = fbi->fb.fix.smem_start;
next_fb_info.xres = fbi->fb.var.xres;
next_fb_info.yres = fbi->fb.var.yres;
next_fb_info.xres_virtual = fbi->fb.var.xres_virtual;
next_fb_info.yres_virtual = fbi->fb.var.yres_virtual;
next_fb_info.xoffset = fbi->fb.var.xoffset;
next_fb_info.yoffset = fbi->fb.var.yoffset;
next_fb_info.lcd_offset_x = fbi->lcd_offset_x;
next_fb_info.lcd_offset_y = fbi->lcd_offset_y;


if (copy_to_user((void *)arg, (s3cfb_next_info_t *) &next_fb_info, sizeof(s3cfb_next_info_t)))
return -EFAULT;
break;


case S3CFB_GET_BRIGHTNESS:
if (copy_to_user((void *)arg, (const void *) &s3cfb_fimd.brightness, sizeof(int)))
return -EFAULT;
break;


#if defined(CONFIG_S3C6410_PWM)
case S3CFB_SET_BRIGHTNESS:
if (copy_from_user(&brightness, (int *) arg, sizeof(int)))
return -EFAULT;


s3cfb_set_brightness(brightness);
break;
#endif


#if defined(CONFIG_FB_S3C_EXT_VIRTUAL_SCREEN)
case S3CFB_VS_START:
s3cfb_fimd.wincon0 &= ~(S3C_WINCONx_ENWIN_F_ENABLE);
writel(s3cfb_fimd.wincon0 | S3C_WINCONx_ENWIN_F_ENABLE, S3C_WINCON0);


fbi->fb.var.xoffset = s3cfb_fimd.xoffset;
fbi->fb.var.yoffset = s3cfb_fimd.yoffset;
break;


case S3CFB_VS_STOP:
s3cfb_fimd.vidw00add0b0 = fbi->screen_dma_f1;
s3cfb_fimd.vidw00add0b1 = fbi->screen_dma_f2;
fbi->fb.var.xoffset = 0;
fbi->fb.var.yoffset = 0;


writel(s3cfb_fimd.vidw00add0b0, S3C_VIDW00ADD0B0);
writel(s3cfb_fimd.vidw00add0b1, S3C_VIDW00ADD0B1);


break;


case S3CFB_VS_SET_INFO:
if (copy_from_user(&vs_info, (s3cfb_vs_info_t *) arg, sizeof(s3cfb_vs_info_t)))
return -EFAULT;


if (s3cfb_set_vs_info(vs_info)) {
printk("Error S3CFB_VS_SET_INFO\n");
return -EINVAL;
}


s3cfb_set_vs_registers(S3CFB_VS_SET);


fbi->fb.var.xoffset = s3cfb_fimd.xoffset;
fbi->fb.var.yoffset = s3cfb_fimd.yoffset;
break;


case S3CFB_VS_MOVE:
s3cfb_set_vs_registers(arg);


fbi->fb.var.xoffset = s3cfb_fimd.xoffset;
fbi->fb.var.yoffset = s3cfb_fimd.yoffset;
break;
#endif


#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
case S3CFB_GET_NUM:
if (copy_from_user((void *)&f_num_val, (const void *)arg, sizeof(u_int)))
return -EFAULT;


if (copy_to_user((void *)arg, (const void *) &f_num_val, sizeof(u_int)))
return -EFAULT;

break;

case S3CFB_CHANGE_REQ:
s3cfb_change_buff(0, (int) arg);
break;
#endif


default:
return -EINVAL;
}
return 0;
}


到目前为止,整个驱动的主要部分已经解释完毕了。最后还是得提一下中断处理函数s3cfb_irq,这个函数实现也比较短,它的主要调用了s3c2410fb_write_palette函数将它的功能是将调色板中的数据显示到LCD上。两个函数的实现也不难,这里就不再赘述。
OK!good bye everyone!see you next time。
The End