具体LCD设备驱动分析

上一篇博客已经分析过了LCD驱动程序的框架,下面总结一下怎样将自己的
LCD设备驱动加到这个驱动架构体系中。
总结:我们自己写LCD设备驱动大体上包括如下步骤
    一、分配一个fb_info结构体
    二、设置这个fb_info结构体
    三、register_framebuffer注册这个fb_info结构体
    四、进行必要硬件相关的设置(比如gpio、时钟、SFR、中断等)
    
下面以一个具体的LCD设备驱动来进行说明分析:

文件s3cfb.c:
入口函数:
s3cfb_probe:
    // 获取平台数据,类型为s3c_platform_fb
    pdata = to_fb_plat(&pdev->dev)
    // 设置fbdev结构体的lcd位,里面包括LCD的一些参数(时序,极性等)
    fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd
    // gpio配置
    pdata->cfg_gpio(pdev)
    // 获取LCD特殊功能寄存器地址资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0)
    // 虚拟地址映射,设置fbdev->regs为虚拟起始地址
    fbdev->regs = ioremap(res->start, res->end - res->start + 1)
    // 开frame中断
    s3cfb_set_vsync_interrupt(fbdev, 1);
    s3cfb_set_global_interrupt(fbdev, 1);
    s3cfb_init_global(fbdev)
        ctrl->output = OUTPUT_RGB
        ctrl->rgb_mode = MODE_RGB_P
        // 驱动开发人员的接口(设置video输出RGB格式)
        s3cfb_set_output(ctrl)
        // 驱动开发人员的接口(设置video输出模式,并行输出)
        s3cfb_set_display_mode(ctrl)
        // 驱动开发人员的接口(设置时序极性)
        s3cfb_set_polarity(ctrl)
        // 驱动开发人员的接口(设置时序)
        s3cfb_set_timing(ctrl)
        // 驱动开发人员的接口(设置时序中的水平与垂直宽度)
        s3cfb_set_lcd_size(ctrl)
    s3cfb_alloc_framebuffer(fbdev)
        // 分配五个fb_info结构体
        ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb), ctrl->dev)
        // 设置五个fb_info结构体
        s3cfb_init_fbinfo(ctrl, i)
        // 给默认LCD设备(LCD2)分配显存,并填充全0,显示背景黑色
        if (i == pdata->default_win) {
            s3cfb_map_video_memory(ctrl->fb[i])
                fb->screen_base = dma_alloc_writecombine(fbdev->dev,
                         PAGE_ALIGN(fix->smem_len),
                         (unsigned int *)
                         &fix->smem_start, GFP_KERNEL)
            memset(fb->screen_base, 0, fix->smem_len)
        }
    s3cfb_register_framebuffer(fbdev)
        // 注册fb_info
        ret = register_framebuffer(ctrl->fb[j])
        // 如果是默认LCD
        if (j == pdata->default_win) {
            // 核查参数
            s3cfb_check_var(&ctrl->fb[j]->var, ctrl->fb[j]);
            // 设置窗口寄存器,将显存地址与大小告诉LCD控制器
            s3cfb_set_par(ctrl->fb[j]);
            // 画logo
            s3cfb_draw_logo(ctrl->fb[j]);
        }
    // 设置时钟
    s3cfb_set_clock(fbdev)
    // 开启video输出
    s3cfb_set_window(fbdev, pdata->default_win, 1)
    // 开始显示
    s3cfb_display_on(fbdev)
    // 注册中断
    fbdev->irq = platform_get_irq(pdev, 0)
    request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED,
            pdev->name, fbdev)
    // 显示logo        
    if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
        printk("Start display and show logo\n");
        /* Start display and show logo on boot */
        fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
        fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
    }
    // 开背光
    pdata->backlight_on(pdev)
    


下面是该驱动所需要的设备与设备资源数据,在开发板初始化时设置好留给
驱动使用(数据与程序分离的思想)

static struct resource s3cfb_resource[] = {
    [0] = {
        .start = S5P_PA_LCD,
        .end   = S5P_PA_LCD + S5P_SZ_LCD - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_LCD1,
        .end   = IRQ_LCD1,
        .flags = IORESOURCE_IRQ,
    },
    [2] = {
        .start = IRQ_LCD0,
        .end   = IRQ_LCD0,
        .flags = IORESOURCE_IRQ,
    },
};

static u64 fb_dma_mask = 0xffffffffUL;

struct platform_device s3c_device_fb = {
    .name          = "s3cfb",
    .id          = -1,
    .num_resources      = ARRAY_SIZE(s3cfb_resource),
    .resource      = s3cfb_resource,
    .dev          = {
        .dma_mask        = &fb_dma_mask,
        .coherent_dma_mask    = 0xffffffffUL
    }
};

static struct s3c_platform_fb ek070tn93_fb_data __initdata = {
    .hw_ver    = 0x62,
    .nr_wins = 5,
    .default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
    .swap = FB_SWAP_WORD | FB_SWAP_HWORD,

    .lcd = &ek070tn93,
    .cfg_gpio    = ek070tn93_cfg_gpio,
    .backlight_on    = ek070tn93_backlight_on,
    .backlight_onoff    = ek070tn93_backlight_off,
    .reset_lcd    = ek070tn93_reset_lcd,
};

void __init s3cfb_set_platdata(struct s3c_platform_fb *pd)
{
    struct s3c_platform_fb *npd;
    int i;

    if (!pd)
        pd = &default_fb_data;

    npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
    if (!npd)
        printk(KERN_ERR "%s: no memory for platform data\n", __func__);
    else {
        for (i = 0; i < npd->nr_wins; i++)
            npd->nr_buffers[i] = 1;

        npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS;

        s3cfb_get_clk_name(npd->clk_name);
        npd->clk_on = s3cfb_clk_on;
        npd->clk_off = s3cfb_clk_off;

        /* starting physical address of memory region */
        npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);
        /* size of memory region */
        npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1);

        s3c_device_fb.dev.platform_data = npd;
    }
}


static struct s3cfb_lcd ek070tn93 = {
    .width = S5PV210_LCD_WIDTH,
    .height = S5PV210_LCD_HEIGHT,
    .bpp = 32,
    .freq = 60,

    .timing = {
        .h_fp    = 160,
        .h_bp    = 140,
        .h_sw    = 20,
        .v_fp    = 12,
        .v_fpe    = 1,
        .v_bp    = 20,
        .v_bpe    = 1,
        .v_sw    = 3,
    },
    .polarity = {
        .rise_vclk = 0,
        .inv_hsync = 1,
        .inv_vsync = 1,
        .inv_vden = 0,
    },
};
    
static void ek070tn93_cfg_gpio(struct platform_device *pdev)
{
    int i;

    for (i = 0; i < 8; i++) {
        s3c_gpio_cfgpin(S5PV210_GPF0(i), S3C_GPIO_SFN(2));
        s3c_gpio_setpull(S5PV210_GPF0(i), S3C_GPIO_PULL_NONE);
    }

    for (i = 0; i < 8; i++) {
        s3c_gpio_cfgpin(S5PV210_GPF1(i), S3C_GPIO_SFN(2));
        s3c_gpio_setpull(S5PV210_GPF1(i), S3C_GPIO_PULL_NONE);
    }

    for (i = 0; i < 8; i++) {
        s3c_gpio_cfgpin(S5PV210_GPF2(i), S3C_GPIO_SFN(2));
        s3c_gpio_setpull(S5PV210_GPF2(i), S3C_GPIO_PULL_NONE);
    }

    for (i = 0; i < 4; i++) {
        s3c_gpio_cfgpin(S5PV210_GPF3(i), S3C_GPIO_SFN(2));
        s3c_gpio_setpull(S5PV210_GPF3(i), S3C_GPIO_PULL_NONE);
    }

    /* mDNIe SEL: why we shall write 0x2 ? */
    writel(0x2, S5P_MDNIE_SEL);

    /* drive strength to max */
    writel(0xffffffff, S5PV210_GPF0_BASE + 0xc);
    writel(0xffffffff, S5PV210_GPF1_BASE + 0xc);
    writel(0xffffffff, S5PV210_GPF2_BASE + 0xc);
    writel(0x000000ff, S5PV210_GPF3_BASE + 0xc);
}

#define S5PV210_GPD_0_0_TOUT_0  (0x2)
#define S5PV210_GPD_0_1_TOUT_1  (0x2 << 4)
#define S5PV210_GPD_0_2_TOUT_2  (0x2 << 8)
#define S5PV210_GPD_0_3_TOUT_3  (0x2 << 12)
static int ek070tn93_backlight_on(struct platform_device *pdev)
{
    /* backlight enable pin */
    s3c_gpio_cfgpin(S5PV210_GPF3(5), S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(S5PV210_GPF3(5), S3C_GPIO_PULL_UP);
    gpio_set_value(S5PV210_GPF3(5), 1);

    return 0;
}

static int ek070tn93_backlight_off(struct platform_device *pdev, int onoff)
{
    /* backlight enable pin */
    s3c_gpio_cfgpin(S5PV210_GPF3(5), S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(S5PV210_GPF3(5), S3C_GPIO_PULL_DOWN);
    gpio_set_value(S5PV210_GPF3(5), 0);

    /* LCD_5V */
    s3c_gpio_cfgpin(S5PV210_GPH0(5), S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(S5PV210_GPH0(5), S3C_GPIO_PULL_DOWN);
    gpio_set_value(S5PV210_GPH0(5), 0);

    /* LCD_33 */
    s3c_gpio_cfgpin(S5PV210_GPH2(0), S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(S5PV210_GPH2(0), S3C_GPIO_PULL_UP);
    gpio_set_value(S5PV210_GPH2(0), 1);


    return 0;
}

static int ek070tn93_reset_lcd(struct platform_device *pdev)
{
    /* LCD_5V */
    s3c_gpio_cfgpin(S5PV210_GPH0(5), S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(S5PV210_GPH0(5), S3C_GPIO_PULL_UP);
    gpio_set_value(S5PV210_GPH0(5), 1);

    /* LCD_33 */
    s3c_gpio_cfgpin(S5PV210_GPH2(0), S3C_GPIO_OUTPUT);
    s3c_gpio_setpull(S5PV210_GPH2(0), S3C_GPIO_PULL_DOWN);
    gpio_set_value(S5PV210_GPH2(0), 0);

    /* wait a moment */
    mdelay(200);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Wenlong_L/article/details/81937866