上一篇博客已经分析过了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;
}