Chinaunix首页 | 论坛 | 博客
  • 博客访问: 16895
  • 博文数量: 13
  • 博客积分: 610
  • 博客等级: 上士
  • 技术积分: 90
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-19 20:26
文章分类
文章存档

2010年(13)

我的朋友

分类: 嵌入式

2010-06-03 14:17:18

接嵌入式Linux之我行——S3C2440上LCD驱动(FrameBuffer)实例开发讲解(二上)

③、帧缓冲设备驱动对底层硬件操作的函数接口实现(即:my2440fb_ops的实现):

/*Framebuffer底层硬件操作各接口函数*/
static struct fb_ops my2440fb_ops = 
{
    .owner          = THIS_MODULE,
    .fb_check_var   = my2440fb_check_var,/*第②步中已实现*/
    .fb_set_par     = my2440fb_set_par,/*设置fb_info中的参数,主要是LCD的显示模式*/
    .fb_blank       = my2440fb_blank,/*显示空白(即:LCD开关控制)*/
    .fb_setcolreg   = my2440fb_setcolreg,/*设置颜色表*/
    /*以下三个函数是可选的,主要是提供fb_console的支持,在内核中已经实现,这里直接调用即可*/
    .fb_fillrect    = cfb_fillrect,/*定义在drivers/video/cfbfillrect.c中*/
    .fb_copyarea    = cfb_copyarea,/*定义在drivers/video/cfbcopyarea.c中*/
    .fb_imageblit   = cfb_imageblit,/*定义在drivers/video/cfbimgblt.c中*/
};

/*设置fb_info中的参数,这里根据用户设置的可变参数var调整固定参数fix*/
static int my2440fb_set_par(struct fb_info *fbinfo)
{
    /*获得fb_info中的可变参数*/
    struct fb_var_screeninfo *var = &fbinfo->var;

    /*判断可变参数中的色位模式,根据色位模式来设置色彩模式*/
    switch (var->bits_per_pixel) 
    {
        case 32:
        case 16:
        case 12:/*12BPP时,设置为真彩色(分成红、绿、蓝三基色)*/
            fbinfo->fix.visual = FB_VISUAL_TRUECOLOR;
            break;
        case 1:/*1BPP时,设置为黑白色(分黑、白两种色,FB_VISUAL_MONO01代表黑,FB_VISUAL_MONO10代表白)*/
            fbinfo->fix.visual = FB_VISUAL_MONO01;
            break;
        default:/*默认设置为伪彩色,采用索引颜色显示*/
            fbinfo->fix.visual = FB_VISUAL_PSEUDOCOLOR;
            break;
    }

    /*设置fb_info中固定参数中一行的字节数,公式:1行字节数=(1行像素个数*每像素位数BPP)/8 */
    fbinfo->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;

    /*修改以上参数后,重新激活fb_info中的参数配置(即:使修改后的参数在硬件上生效)*/
    my2440fb_activate_var(fbinfo);

    return 0;
}

/*重新激活fb_info中的参数配置*/
static void my2440fb_activate_var(struct fb_info *fbinfo)
{
    /*获得结构体变量*/
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

    /*获得fb_info可变参数*/
    struct fb_var_screeninfo *var = &fbinfo->var;

    /*计算LCD控制寄存器1中的CLKVAL值, 根据数据手册中该寄存器的描述,计算公式如下:
    * STN屏:VCLK = HCLK / (CLKVAL * 2), CLKVAL要求>= 2
    * TFT屏:VCLK = HCLK / [(CLKVAL + 1) * 2], CLKVAL要求>= 0*/

    int clkdiv = my2440fb_calc_pixclk(fbvar, var->pixclock) / 2;

    /*获得屏幕的类型*/
    int type = fbvar->regs.lcdcon1 & S3C2410_LCDCON1_TFT;

    if (type == S3C2410_LCDCON1_TFT) 
    {
        /*根据数据手册按照TFT屏的要求配置LCD控制寄存器1-5*/
        my2440fb_config_tft_lcd_regs(fbinfo, &fbvar->regs);

        --clkdiv;

        if (clkdiv < 0)
        {
            clkdiv = 0;
        }
    } 
    else 
    {
        /*根据数据手册按照STN屏的要求配置LCD控制寄存器1-5*/
        my2440fb_config_stn_lcd_regs(fbinfo, &fbvar->regs);

        if (clkdiv < 2)
        {
            clkdiv = 2;
        }
    }

    /*设置计算的LCD控制寄存器1中的CLKVAL值*/
    fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);

    /*将各参数值写入LCD控制寄存器1-5中*/
    writel(fbvar->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, regs + S3C2410_LCDCON1);
    writel(fbvar->regs.lcdcon2, regs + S3C2410_LCDCON2);
    writel(fbvar->regs.lcdcon3, regs + S3C2410_LCDCON3);
    writel(fbvar->regs.lcdcon4, regs + S3C2410_LCDCON4);
    writel(fbvar->regs.lcdcon5, regs + S3C2410_LCDCON5);

    /*配置帧缓冲起始地址寄存器1-3*/
    my2440fb_set_lcdaddr(fbinfo);

    fbvar->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID,
    writel(fbvar->regs.lcdcon1, regs + S3C2410_LCDCON1);
}

/*计算LCD控制寄存器1中的CLKVAL值*/
static unsigned int my2440fb_calc_pixclk(struct my2440fb_var *fbvar, unsigned long pixclk)
{
    /*获得LCD的时钟*/
    unsigned long clk = clk_get_rate(fbvar->lcd_clock);

    /* 像素时钟单位是皮秒,而时钟的单位是赫兹,所以计算公式为:
     * Hz -> picoseconds is / 10^-12
     */

    unsigned long long div = (unsigned long long)clk * pixclk;

    div >>= 12;            /* div / 2^12 */
    do_div(div, 625 * 625UL * 625); /* div / 5^12, do_div宏定义在asm/div64.h中*/

    return div;
}

/*根据数据手册按照TFT屏的要求配置LCD控制寄存器1-5*/
static void my2440fb_config_tft_lcd_regs(const struct fb_info *fbinfo, struct s3c2410fb_hw *regs)
{
    const struct my2440fb_var *fbvar = fbinfo->par;
    const struct fb_var_screeninfo *var = &fbinfo->var;

    /*根据色位模式设置LCD控制寄存器1和5,参考数据手册*/
    switch (var->bits_per_pixel) 
    {
        case 1:/*1BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
            break;
        case 2:/*2BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
            break;
        case 4:/*4BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
            break;
        case 8:/*8BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
            regs->lcdcon5 |= S3C2410_LCDCON5_BSWP | S3C2410_LCDCON5_FRM565;
            regs->lcdcon5 &= ~S3C2410_LCDCON5_HWSWP;
            break;
        case 16:/*16BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
            regs->lcdcon5 &= ~S3C2410_LCDCON5_BSWP;
            regs->lcdcon5 |= S3C2410_LCDCON5_HWSWP;
            break;
        case 32:/*32BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_TFT24BPP;
            regs->lcdcon5 &= ~(S3C2410_LCDCON5_BSWP | S3C2410_LCDCON5_HWSWP | S3C2410_LCDCON5_BPP24BL);
            break;
        default:/*无效的BPP*/
            dev_err(fbvar->dev, "invalid bpp %d\n", var->bits_per_pixel);
    }

    /*设置LCD配置寄存器2、3、4*/
    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |
            S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
            S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
            S3C2410_LCDCON2_VSPW(var->vsync_len - 1);

    regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
            S3C2410_LCDCON3_HFPD(var->left_margin - 1) |
            S3C2410_LCDCON3_HOZVAL(var->xres - 1);

    regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
}

/*根据数据手册按照STN屏的要求配置LCD控制寄存器1-5*/
static void my2440fb_config_stn_lcd_regs(const struct fb_info *fbinfo, struct s3c2410fb_hw *regs)
{
    const struct my2440fb_var    *fbvar = fbinfo->par;
    const struct fb_var_screeninfo *var = &fbinfo->var;

    int type = regs->lcdcon1 & ~S3C2410_LCDCON1_TFT;
    int hs = var->xres >> 2;
    unsigned wdly = (var->left_margin >> 4) - 1;
    unsigned wlh = (var->hsync_len >> 4) - 1;

    if (type != S3C2410_LCDCON1_STN4)
    {
        hs >>= 1;
    }

    /*根据色位模式设置LCD控制寄存器1,参考数据手册*/
    switch (var->bits_per_pixel) 
    {
        case 1:/*1BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN1BPP;
            break;
        case 2:/*2BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN2GREY;
            break;
        case 4:/*4BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN4GREY;
            break;
        case 8:/*8BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN8BPP;
            hs *= 3;
            break;
        case 12:/*12BPP*/
            regs->lcdcon1 |= S3C2410_LCDCON1_STN12BPP;
            hs *= 3;
            break;
        default:/*无效的BPP*/
            dev_err(fbvar->dev, "invalid bpp %d\n", var->bits_per_pixel);
    }
    
    /*设置LCD配置寄存器2、3、4, 参考数据手册*/
    if (wdly > 3) wdly = 3;
    if (wlh > 3) wlh = 3;
    regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1);

    regs->lcdcon3 = S3C2410_LCDCON3_WDLY(wdly) |
            S3C2410_LCDCON3_LINEBLANK(var->right_margin / 8) |
            S3C2410_LCDCON3_HOZVAL(hs - 1);

    regs->lcdcon4 = S3C2410_LCDCON4_WLH(wlh);
}

/*配置帧缓冲起始地址寄存器1-3,参考数据手册*/
static void my2440fb_set_lcdaddr(struct fb_info *fbinfo)
{
    unsigned long saddr1, saddr2, saddr3;
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

    saddr1 = fbinfo->fix.smem_start >> 1;
    saddr2 = fbinfo->fix.smem_start;
    saddr2 += fbinfo->fix.line_length * fbinfo->var.yres;
    saddr2 >>= 1;
    saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH((fbinfo->fix.line_length / 2) & 0x3ff);

    writel(saddr1, regs + S3C2410_LCDSADDR1);
    writel(saddr2, regs + S3C2410_LCDSADDR2);
    writel(saddr3, regs + S3C2410_LCDSADDR3);
}

/*显示空白,blank mode有5种模式,定义在fb.h中,是一个枚举*/
static int my2440fb_blank(int blank_mode, struct fb_info *fbinfo)
{
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

    /*根据显示空白的模式来设置LCD是开启还是停止*/
    if (blank_mode == FB_BLANK_POWERDOWN) 
    {
        my2440fb_lcd_enable(fbvar, 0);/*在第②步中定义*/
    } 
    else 
    {
        my2440fb_lcd_enable(fbvar, 1);/*在第②步中定义*/
    }

    /*根据显示空白的模式来控制临时调色板寄存器*/
    if (blank_mode == FB_BLANK_UNBLANK)
    {
        /*临时调色板寄存器无效*/
        writel(0x0, regs + S3C2410_TPAL);
    }
    else 
    {
        /*临时调色板寄存器有效*/
        writel(S3C2410_TPAL_EN, regs + S3C2410_TPAL);
    }

    return 0;
}

/*设置颜色表*/
static int my2440fb_setcolreg(unsigned regno,unsigned red,unsigned green,unsigned blue,unsigned transp,struct fb_info*fbinfo)
{
    unsigned int val;
    struct my2440fb_var *fbvar = fbinfo->par;
    void __iomem *regs = fbvar->lcd_base;

    switch (fbinfo->fix.visual) 
    {
        case FB_VISUAL_TRUECOLOR:
            /*真彩色*/
            if (regno < 16) 
            {
                u32 *pal = fbinfo->pseudo_palette;

                val = chan_to_field(red, &fbinfo->var.red);
                val |= chan_to_field(green, &fbinfo->var.green);
                val |= chan_to_field(blue, &fbinfo->var.blue);

                pal[regno] = val;
            }
            break;
        case FB_VISUAL_PSEUDOCOLOR:
            /*伪彩色*/
            if (regno < 256) 
            {
                val = (red >> 0) & 0xf800;
                val |= (green >> 5) & 0x07e0;
                val |= (blue >> 11) & 0x001f;

                writel(val, regs + S3C2410_TFTPAL(regno));

                /*修改调色板*/
                schedule_palette_update(fbvar, regno, val);
            }
            break;
        default:
            return 1;
    }

    return 0;
}

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

/*修改调色板*/
static void schedule_palette_update(struct my2440fb_var    *fbvar, unsigned int regno, unsigned int val)
{
    unsigned long flags;
    unsigned long irqen;

    /*LCD中断挂起寄存器基地址*/
    void __iomem *lcd_irq_base = fbvar->lcd_base + S3C2410_LCDINTBASE;

    /*在修改中断寄存器值之前先屏蔽中断,将中断状态保存到flags中*/
    local_irq_save(flags);

    fbvar->palette_buffer[regno] = val;

    /*判断调色板是否准备就像*/
    if (!fbvar->palette_ready) 
    {
        fbvar->palette_ready = 1;

        /*使能中断屏蔽寄存器*/
        irqen = readl(lcd_irq_base + S3C24XX_LCDINTMSK);
        irqen &= ~S3C2410_LCDINT_FRSYNC;
        writel(irqen, lcd_irq_base + S3C24XX_LCDINTMSK);
    }

    /*恢复被屏蔽的中断*/
    local_irq_restore(flags);
}

 

五、从整体上再描述一下FrameBuffer设备驱动实例代码的结构:
 
1、在第①部分代码中主要做的事情有:
   a.将LCD设备注册到系统平台设备中;
   b.定义LCD平台设备结构体lcd_fb_driver。
 
2、在第②部分代码中主要做的事情有:
   a.获取和设置LCD平台设备的各种资源;
   b.分配fb_info结构体空间;
   c.初始化fb_info结构体中的各参数;
   d.初始化LCD控制器;
   e.检查fb_info中可变参数;
   f.申请帧缓冲设备的显示缓冲区空间;
   g.注册fb_info。
 
3、在第部分代码中主要做的事情有:
   a.实现对fb_info相关参数进行检查的硬件接口函数;
   b.实现对LCD显示模式进行设定的硬件接口函数;
   c.实现对LCD显示开关(空白)的硬件接口函数等。
阅读(472) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~