itop exynos4412 lcd驱动 详细分析 (四)

(若转载,请注明出处,若有错误请指正,谢谢)
(以下分析皆基于:itop4412精英板设备和代码资源)
(内核为:iTop4412_Kernel_3.0提供)
(看客需要一定的linux平台驱动基础,和lcd操作基础)

lcd的工作,在kernel 中有device 和driver两个描述,这也是必然

在第二节中我们详解介绍了 s3cfb_main.c ——-probe函数的框架。
回顾一下probe函数的作用:
1. 获取平台设备 device中的资源
2. 对设备做了一下相应的初始化
3. 申请了fb_info ,根据要求进行了填充
4. 向内核提交了fb_info
5. 使能设备等
6. 创建属性文件

在上一节中,我们对probe函数中的部分接口进行了阐述,现在接着来看剩下的。
(请与第二节probe函数框架对应分析)

看这一节之前请务必 先了解该三个结构体:
struct fb_info ;
struct fb_fix_screeninfo ;
struct fb_var_screeninfo ;

1 .probe 之 s3cfb_alloc_framebuffer(fbdev[i], i);

 该函数的作用在第二节中提到:
  /* alloc fb_info */
        /* 这个函数在s3cfb_ops.c 文件中   -------fb 操作集合*/
        /*这个函数的作用是申请struct fb_info 结构体,初始化fb_info 结构体的信息*/
        /*fb_info 结构体 上与内核接口耦合的关系,我们写lcd驱动就是除了初始化相关硬件以外*/
        /*就是把fb_info 结构体初始化后 注册到内核,供 fb_mem 调用,上层才能跟底层结合起来*/
        /*该函数具体内容将在接下来介绍*/

函数原型如下:

int s3cfb_alloc_framebuffer(struct s3cfb_global *fbdev, int fimd_id)
{
    struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);
    int ret = 0;
    int i;
    // 根据wins 个数开辟fb_info 空间的指针,这个在platform device中指定了
    //这里并没有申请fb_info空间,数据结构体框图请参见第二节的介绍
    /*
        static struct s3c_platform_fb default_fb_data __initdata = {
        #if defined(CONFIG_ARCH_EXYNOS4)
            .hw_ver = 0x70,
        #else
            .hw_ver = 0x62,
        #endif
            .nr_wins    = 5,  //就是它
        #if defined(CONFIG_FB_S5P_DEFAULT_WINDOW)
            .default_win    = CONFIG_FB_S5P_DEFAULT_WINDOW,
        #else
            .default_win    = 0,
        #endif
            .swap       = FB_SWAP_WORD | FB_SWAP_HWORD,
        };
            */

    fbdev->fb = kmalloc(pdata->nr_wins *
                sizeof(struct fb_info *), GFP_KERNEL);
    if (!fbdev->fb) {
        dev_err(fbdev->dev, "not enough memory\n");
        ret = -ENOMEM;
        goto err_alloc;
    }

    //接下来就是为每个fb_info 申请空间,进行初始化了
    for (i = 0; i < pdata->nr_wins; i++) {
        /*这个函数的功能是:开辟 fb_info s3cfb_windows 空间
    并且把 fb_info 中的dev 指向 fbdev->dev 
    把 fb_info->par 指向 s3cfb_windows,可以参考下面的图*/ 
        fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window),
                        fbdev->dev);
        if (!fbdev->fb[i]) {
            dev_err(fbdev->dev, "not enough memory\n");
            ret = -ENOMEM;
            goto err_alloc_fb;
        }

        /*主要是初始化fb_info 结构体,对var ,fix进行填充等....*/
        ret = s3cfb_init_fbinfo(fbdev, i);
        if (ret) {
            dev_err(fbdev->dev,
                "failed to allocate memory for fb%d\n", i);
            ret = -ENOMEM;
            goto err_alloc_fb;
        }

#ifdef CONFIG_FB_S5P_SOFTBUTTON_UI
        if (i == pdata->default_win || i == 4)
#else
        if (i == pdata->default_win)
#endif      
            /*主要是为窗体分配存放RGB数据的空间。(该分配一般用DMA)*/
            if (s3cfb_map_default_video_memory(fbdev,
                        fbdev->fb[i], fimd_id)) {
                dev_err(fbdev->dev,
                "failed to map video memory "
                "for default window (%d)\n", i);
            ret = -ENOMEM;
            goto err_alloc_fb;
        }
    }

    return 0;

err_alloc_fb:
    for (i = 0; i < pdata->nr_wins; i++) {
        if (fbdev->fb[i])
            framebuffer_release(fbdev->fb[i]);
    }
    kfree(fbdev->fb);

err_alloc:
    return ret;
}
    fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window),
    fbdev->dev);函数的作用开辟 fb_info s3cfb_windows 空间
并且把 fb_info 中的dev 指向 fbdev->dev 
把 fb_info->par 指向 s3cfb_windows

这里写图片描述

现在来看看s3cfb_init_fbinfo函数

int s3cfb_init_fbinfo(struct s3cfb_global *fbdev, int id)
{
    /*
        对刚刚开辟的fb_info 空间中的相应地址 进行提取,方便接下来的填充
                (对fb_info 中相关成员的信息,请查阅相关资料,这里不做阐述)
                (所这里需要读者具有一定的功底)
    */
    struct fb_info *fb = fbdev->fb[id];
    struct fb_fix_screeninfo *fix = &fb->fix;
    struct fb_var_screeninfo *var = &fb->var;
    struct s3cfb_window *win = fb->par;
    struct s3cfb_alpha *alpha = &win->alpha;
    struct s3cfb_lcd *lcd = fbdev->lcd;
    struct s3cfb_lcd_timing *timing = &lcd->timing;

    /*对窗体进行清空,由此可见 fb->par 的作用。设置fix ->id   */
    memset(win, 0, sizeof(struct s3cfb_window));
    platform_set_drvdata(to_platform_device(fbdev->dev), fb);
    strcpy(fix->id, S3CFB_NAME);

    /*    指定窗体id,窗体数据路径,dma burst ,win的win->power_state 状态,设置透明度方式*/
    /* fimd specific */
    win->id = id;
    /*选择数据来源*/
    /*enum s3cfb_data_path_t {
    DATA_PATH_FIFO = 0,
    DATA_PATH_DMA = 1,
    DATA_PATH_IPC = 2,
    };*/
    win->path = DATA_PATH_DMA;
    /*设置dma_burst ,大小范围可以根据数据数据手册决定,请看下图*/
    win->dma_burst = 16;
    s3cfb_update_power_state(fbdev, win->id, FB_BLANK_POWERDOWN);
    alpha->mode = PLANE_BLENDING;

    /* fbinfo */
    /*   设置fb->fbops = &s3cfb_ops;  s3cfb_ops 是一个全局变量在,s3cfb_main 中指定:*/
    fb->fbops = &s3cfb_ops;
    fb->flags = FBINFO_FLAG_DEFAULT;//  然后是设置FBINFO
    fb->pseudo_palette = &win->pseudo_pal;//设置虚拟的调色板地址
#if (CONFIG_FB_S5P_NR_BUFFERS != 1) // 设置 偏移: 一般为0
    fix->xpanstep = 2;
    fix->ypanstep = 1;
#else
    fix->xpanstep = 0;
    fix->ypanstep = 0;
#endif
    /*  设置type --- FB_TYPE_PACKED_PIXELS   -----像素与内存对应,TFT就是基于这个管理内存。根据设备需求不同选择*/
     fix->type = FB_TYPE_PACKED_PIXELS;
    fix->accel = FB_ACCEL_NONE;//无此设备
    /*-----设置显示格式真彩,当然还有黑白*/
    /*索引等显示方式,请查阅相关资料*/
    fix->visual = FB_VISUAL_TRUECOLOR;
    /*设置可变参数宽高,lcd 是一个指针,指向了wa101 结构体,(还记得否?)*/
    var->xres = lcd->width;
    var->yres = lcd->height;

     /*设置虚拟分辨率,嵌入式设备一般不该分辨率*/
     /*所以通常设置成  xres和yres 一样*/
     /*这里是为了驱动兼容*/
#if defined(CONFIG_FB_S5P_VIRTUAL)
    var->xres_virtual = CONFIG_FB_S5P_X_VRES;
    var->yres_virtual = CONFIG_FB_S5P_Y_VRES * CONFIG_FB_S5P_NR_BUFFERS;
#else
    var->xres_virtual = var->xres;
    var->yres_virtual = var->yres * CONFIG_FB_S5P_NR_BUFFERS;
#endif
    /* 设置成 32 bpp 的分辨率,这里啰嗦一句: 分辨率由 WINCONx 寄存器中BBPMODE决定,你可以看到这里支持的格式有很多。而代码写死了(可惜,也可能是跟其他硬件兼容,也可能是为了一口气开辟最大的空间,低bpp可以不用
重新申请空间,bpp向下兼容(猜测)
    */
    var->bits_per_pixel = 32;
    /*设置xoffset ,yoffset 偏移 都为0*/
    /*不为0 的话:var->xoffset = var->xres_virtual - var->xres - 1*/
    var->xoffset = 0;
    /*不为0的话:var->yoffset = var->yres_virtual - var->yres - 1;*/
    var->yoffset = 0;
    var->width = 0;
    var->height = 0;
    var->transp.length = 0;

    fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
    fix->smem_len = fix->line_length * var->yres_virtual;

    var->nonstd = 0;//----标准格式 ,!=0则为非标准格式
    var->activate = FB_ACTIVATE_NOW;//----完全应用
    var->vmode = FB_VMODE_NONINTERLACED;//-----正常扫描
    // 以下是设置timing
    var->hsync_len = timing->h_sw;
    var->vsync_len = timing->v_sw;
    var->left_margin = timing->h_bp;
    var->right_margin = timing->h_fp;
    var->upper_margin = timing->v_bp;
    var->lower_margin = timing->v_fp;
    //根据相应参数计算pixclock的值,
    var->pixclock = (lcd->freq *
        (var->left_margin + var->right_margin
        + var->hsync_len + var->xres) *
        (var->upper_margin + var->lower_margin
        + var->vsync_len + var->yres));
    var->pixclock = KHZ2PICOS(var->pixclock/1000);

    //设置fb的R/G/B位域,-----也就是RGB的格式,这里非常简单,不做描述
    s3cfb_set_bitfield(var);
    /*
    设置透明度 模式
    设置channel---0 通道
    设置value -------最大值不透明
    */
    s3cfb_set_alpha_info(var, win);

    return 0;
}

dma burst 参数参考
这里写图片描述

exynos 所能支持的bpp:由寄存器决定
这里是WINCON0 的参数:
这里写图片描述
这里写图片描述

接下来看:

    if (s3cfb_map_default_video_memory(fbdev,
                        fbdev->fb[i], fimd_id)) {
                dev_err(fbdev->dev,
                "failed to map video memory "
                "for default window (%d)\n", i);
            ret = -ENOMEM;
            goto err_alloc_fb;
        }
    s3cfb_map_default_video_memory 主要是为窗体分配存放RGB数据的空间。(该分配一般用DMA)
    函数太长,功能单一,这里就简单的阐述一下就可以了
    并且让 fix->smem_start ------指向开辟空间的物理空间      (空间长度有上面fix->mem_len 指定了)
                fb->screen_base ------指向开辟空间的虚拟地址

2 probe 之 s3cfb_register_framebuffer(fbdev[i])) ./* register fb_info */
代码如下 终于向内核提交fb_info 了


int s3cfb_register_framebuffer(struct s3cfb_global *fbdev)
{
    struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);
    int ret, i, j;

    /* on registering framebuffer, framebuffer of default window is registered at first. */
    for (i = pdata->default_win; i < pdata->nr_wins + pdata->default_win; i++) {
        j = i % pdata->nr_wins;
        ret = register_framebuffer(fbdev->fb[j]); //向内核注册lcd设备,成功过后就
                //在/dev/fbx的节点  (创建不是drive做的,有兴趣的可以分析该函数)
        if (ret) {
            dev_err(fbdev->dev, "failed to register \
                framebuffer device\n");
            return -EINVAL;
        }
#ifdef CONFIG_FB_S5P_SOFTBUTTON_UI /* Add Menu UI Window 4 */
        if(j==4){
            dev_info(fbdev->dev, " set parameters for win4");
            s3cfb_check_var_window(fbdev, &fbdev->fb[j]->var, fbdev->fb[j]);
            s3cfb_set_par_window(fbdev, fbdev->fb[j]);
        }
#endif
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
        //lcd 控制台
        if (j == pdata->default_win) {
            //fops  /* 检查参数和设置参数将在后面涉及中&s3cfb_ops;讲到  */

            // 检查参数
            s3cfb_check_var_window(fbdev, &fbdev->fb[j]->var,
                    fbdev->fb[j]);
            // 设置参数
            s3cfb_set_par_window(fbdev, fbdev->fb[j]);
            // 显示logo
            s3cfb_draw_logo(fbdev->fb[j]);
        }
#endif
    }
    return 0;
}

好了返回probe
接下来:这两个都是硬件相关函数:

3.probe 之s3cfb_set_clock(fbdev[i]);

/*
     该函数的主要作用是获得时钟,计算clk,向VIDCON0 配置相应的时钟参数
*/
int s3cfb_set_clock(struct s3cfb_global *ctrl)
{
    struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
    u32 cfg, maxclk, src_clk, vclk, div;

    /* spec is under 100MHz */
    maxclk = 100 * 1000000;

    cfg = readl(ctrl->regs + S3C_VIDCON0);

    if (pdata->hw_ver == 0x70) {
        cfg &= ~(S3C_VIDCON0_CLKVALUP_MASK |
            S3C_VIDCON0_VCLKEN_MASK);
        cfg |= (S3C_VIDCON0_CLKVALUP_ALWAYS |
            S3C_VIDCON0_VCLKEN_NORMAL);

        src_clk = clk_get_rate(ctrl->clock);
        printk(KERN_DEBUG "FIMD src sclk = %d\n", src_clk);
    } else {
        cfg &= ~(S3C_VIDCON0_CLKSEL_MASK |
            S3C_VIDCON0_CLKVALUP_MASK |
            S3C_VIDCON0_VCLKEN_MASK |
            S3C_VIDCON0_CLKDIR_MASK);
        cfg |= (S3C_VIDCON0_CLKVALUP_ALWAYS |
            S3C_VIDCON0_VCLKEN_NORMAL |
            S3C_VIDCON0_CLKDIR_DIVIDED);

        if (strcmp(pdata->clk_name, "sclk_fimd") == 0) {
            cfg |= S3C_VIDCON0_CLKSEL_SCLK;
            src_clk = clk_get_rate(ctrl->clock);
            printk(KERN_DEBUG "FIMD src sclk = %d\n", src_clk);

        } else {
            cfg |= S3C_VIDCON0_CLKSEL_HCLK;
            src_clk = ctrl->clock->parent->rate;
            printk(KERN_DEBUG "FIMD src hclk = %d\n", src_clk);
        }
    }
    vclk = PICOS2KHZ(ctrl->fb[pdata->default_win]->var.pixclock) * 1000;

    if (vclk > maxclk) {
        dev_info(ctrl->dev, "vclk(%d) should be smaller than %d\n",
            vclk, maxclk);
        /* vclk = maxclk; */
    }

    div = src_clk / vclk;
    if (src_clk % vclk) {
        if ((src_clk % vclk) > (vclk/2))
            div++;
    }

    if ((src_clk/div) > maxclk)
        dev_info(ctrl->dev, "vclk(%d) should be smaller than %d Hz\n",
            src_clk/div, maxclk);

    cfg &= ~S3C_VIDCON0_CLKVAL_F(0xff);
    cfg |= S3C_VIDCON0_CLKVAL_F(div - 1);
    writel(cfg, ctrl->regs + S3C_VIDCON0);

    //dev_dbg(ctrl->dev, 
    printk("parent clock: %d, vclk: %d, vclk div: %d\n",
            src_clk, vclk, div);

    return 0;
}

probe 函数之 s3cfb_enable_window

int s3cfb_enable_window(struct s3cfb_global *fbdev, int id)
{
    struct s3cfb_window *win = fbdev->fb[id]->par;

    if (!win->enabled)
        atomic_inc(&fbdev->enabled_win);

    if (s3cfb_window_on(fbdev, id)) { //使能窗口,具体操作如下
        win->enabled = 0;
        return -EFAULT;
    } else {
        win->enabled = 1;
        return 0;
    }
}

int s3cfb_window_on(struct s3cfb_global *ctrl, int id)
{
    struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
    u32 cfg;

    if ((pdata->hw_ver == 0x62) || (pdata->hw_ver == 0x70)) {
        cfg = readl(ctrl->regs + S3C_WINSHMAP);
        cfg |= S3C_WINSHMAP_CH_ENABLE(id);  //选择通道
        writel(cfg, ctrl->regs + S3C_WINSHMAP);
    }

    cfg = readl(ctrl->regs + S3C_WINCON(id));
    cfg |= S3C_WINCON_ENWIN_ENABLE;  //使能数据,可以查WINCONx ,第0位
    writel(cfg, ctrl->regs + S3C_WINCON(id));

    dev_dbg(ctrl->dev, "[fb%d] turn on\n", id);

    return 0;
}

SHADOWCON 使能
 Base Address = 0x11C0_0000
 Address = Base Address + 0x0034, Reset Value = 0x0000_0000
这里写图片描述

WINCON0 使能窗口数据手册
这里写图片描述

1probe 函数之

        //设置窗口状态,由程序员标记
        s3cfb_update_power_state(fbdev[i], pdata->default_win,
                    FB_BLANK_UNBLANK);
        //着重看这儿,真正开启了lcd设备          
        s3cfb_display_on(fbdev[i]);


    int s3cfb_display_on(struct s3cfb_global *ctrl)
{
    u32 cfg;

    cfg = readl(ctrl->regs + S3C_VIDCON0);
    cfg |= (S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE);
    writel(cfg, ctrl->regs + S3C_VIDCON0);

    dev_dbg(ctrl->dev, "global display is on\n");

    return 0;
}

开启设备VIDCON0
itop exynos4412 lcd驱动 详细分析 (四)_第1张图片

至此probe函数就介绍的差不多了,主要功能就这些,其他就略过。
剩下就是要介绍下面的操作函数 (还有win的尺寸,buf地址,坐标,画图等都还没 介绍)
这里写图片描述

——未完待续

你可能感兴趣的:(lcd驱动)