前些天在Cortex-A8做小项目时对linux的LCD驱动进行了简单的代码分析,就记录下来,以便自己日后继续学习,同时希望能帮到有需要的人。
首先,
Lcd是一个帧缓冲设备,他的驱动构架是基于帧缓冲子系统的。
帧缓冲子系统包括以下几个文件:
Fbmem.c (核心层、提供通用接口)
S3c-fb.c(控制器驱动层、配置相关寄存器)
Dev-fb.c(资源层、提供寄存器的地址)
Fbmem.c分析
static int __init fbmem_init(void)
{
proc_create (创建虚拟文件)
register_chrdev(注册设备号、绑定操作方法接口)
class_create (创建一类fb设备)
}
/*实现接口,供应用程序调用*/
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release_boss,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
};
static int fb_open分析
定义一个struct fb_info *info;
int fbidx = iminor(inode); //取得次设备号
info = registered_fb[fbidx]; //从已注册的FB数组中根据次设备号取出相应的fb_info结构体
file->private_data = info;//将取到的fb_info放入file的私有数据中
if (info->fbops->fb_open)
res = info->fbops->fb_open(info,1); //如果下层驱动实现了open函数,就调用open
static ssize_t fb_read 分析
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);//如果下层实现了read就调用
下面实现的是对显存内容的读取,实际应用中很少用到
static int fb_mmap分析
该函数是将物理显存映射到用户空间
一般是调用底层驱动实现的mmap函数
相关I/O的映射
static long fb_ioctl 分析(重要)
int fbidx = iminor(inode); //获取次设备号
struct fb_info *info = registered_fb[fbidx]; //根据次设备号获取fb_info结构体
static long do_fb_ioctl()函数
switch (cmd) //分析命令
case FBIOGET_VSCREENINFO://获取可变参数
case FBIOPUT_VSCREENINFO://设置可变参数
case FBIOGET_FSCREENINFO://获取固定参数
case FBIOPUTCMAP://设置颜色表
case FBIOGETCMAP://获取颜色表
case FBIOPAN_DISPLAY:
case FBIOBLANK://lcd相关操作,包括断电,关背光
case FBIO_FILLRECT: //这俩是我自己实现,填充一个矩形
case FBIO_COPYAREA://拷贝一块区域
S3c-fb.c分析
/*本层驱动采用平台设备机制编写*/
static int __init s3c_fb_init(void)
{
return platform_driver_register(&s3c_fb_driver); //向平台设备虚拟总线注册
}
/*实现底层控制器的操作方法,即帧缓冲设备文件操作结构体*/
static struct fb_ops s3c_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = s3c_fb_check_var, //核对可变参数
.fb_set_par = s3c_fb_set_par,
.fb_blank = s3c_fb_blank,
.fb_setcolreg = s3c_fb_setcolreg,
.fb_fillrect = cfb_fillrect, //矩形填充
.fb_copyarea = cfb_copyarea, //区域拷贝
.fb_imageblit = cfb_imageblit, //图形填充
};
/*实现帧缓冲设备驱动的模块加载、卸载及平台驱动的探测和移除函数的模块*/
static struct platform_driver s3c_fb_driver = {
.probe = s3c_fb_probe,
.remove = __devexit_p(s3c_fb_remove),
.suspend = s3c_fb_suspend,
.resume = s3c_fb_resume,
.driver = {
.name = "s3c-fb", //驱动名
.owner = THIS_MODULE,
},
};
static int s3c_fb_blank() //LCD相关控制
switch (blank_mode) //分析模式
case FB_BLANK_POWERDOWN: //断电
case FB_BLANK_NORMAL: //disable the DMA and display 0x0 (black)
case FB_BLANK_UNBLANK: //未关背光,但是关了显示窗口
if (index == 0) //如果是窗口0,则有将其使能或者关闭的操作
s3c_fb_enable(sfb, blank_mode != FB_BLANK_POWERDOWN ? 1 : 0);
static int s3c_fb_set_par(struct fb_info *info) //设置lcd的参数(重点)
注:每个窗口匹配探测成功后都会执行到这个函数
struct fb_var_screeninfo *var = &info->var; //获取当前fb_info的可变参数
struct s3c_fb_win *win = info->par; //获取窗口的当前参数
struct s3c_fb *sfb = win->parent; //获取窗口的父设备
void __iomem *regs = sfb->regs; //获取fb设备的映射后的虚拟地址
int win_no = win->index; //窗口的序号
u32 osdc_data = 0;
u32 data;
u32 pagewidth; //每页的宽度
int clkdiv;
switch (var->bits_per_pixel) //根据可变参数里的像素选择色彩模式
/*每行像素所占用的内存大小*/
info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
/* 设置lcd参数的时候先关闭对应窗口 */
writel(0, regs + WINCON(win_no));
/*根据win0设置硬件时序*/
if (win_no == 0) {
clkdiv = s3c_fb_calc_pixclk(sfb, var->pixclock);
data = sfb->pdata->vidcon0;
data &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR);
if (clkdiv > 1)
data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR;
else
data &= ~VIDCON0_CLKDIR; /* 1:1 clock */
/* write the timing data to the panel */
data |= VIDCON0_ENVID | VIDCON0_ENVID_F;
writel(data, regs + VIDCON0);
data = VIDTCON0_VBPD(var->upper_margin - 1) |
VIDTCON0_VFPD(var->lower_margin - 1) |
VIDTCON0_VSPW(var->vsync_len - 1);
writel(data, regs + VIDTCON0);
data = VIDTCON1_HBPD(var->left_margin - 1) |
VIDTCON1_HFPD(var->right_margin - 1) |
VIDTCON1_HSPW(var->hsync_len - 1);
writel(data, regs + VIDTCON1);
/*LCD实际的大小*/
data = VIDTCON2_LINEVAL(var->xres - 1) |
VIDTCON2_HOZVAL(var->yres- 1);
writel(data, regs + VIDTCON2);
}
/*将显存的地址和大小写入相关的寄存器*/
writel(info->fix.smem_start, regs + VIDW_BUF_START(win_no));
data = info->fix.smem_start + info->fix.line_length * var->yres;
writel(data, regs + VIDW_BUF_END(win_no));
pagewidth = (var->xres * var->bits_per_pixel) >> 3;
data = VIDW_BUF_SIZE_OFFSET(info->fix.line_length - pagewidth) |
VIDW_BUF_SIZE_PAGEWIDTH(pagewidth);
writel(data, regs + VIDW_BUF_SIZE(win_no));
/*设置窗口位置与大小*/
窗口0:
data = VIDOSDxA_TOPLEFT_X(0) | VIDOSDxA_TOPLEFT_Y(0);
writel(data, regs + VIDOSD_A(win_no));
data = VIDOSDxB_BOTRIGHT_X(361) |
VIDOSDxB_BOTRIGHT_Y(271);
writel(data, regs + VIDOSD_B(win_no));
data = 362* 272;
writel(data, regs + VIDOSD_C(win_no));
}
窗口1:
data = VIDOSDxA_TOPLEFT_X(364) | VIDOSDxA_TOPLEFT_Y(0);
writel(data, regs + VIDOSD_A(win_no)); //起始地址
data = VIDOSDxB_BOTRIGHT_X(479) |VIDOSDxB_BOTRIGHT_Y(271);
writel(data, regs + VIDOSD_B(win_no));//窗口结束地址
data = 116*272;
writel(data, regs + VIDOSD_D(win_no)); //窗口大小
switch (var->bits_per_pixel) //根据像素选择RGB模式,我们选的是RGB:565模式
/*相关特效的设置,win0无特效设置*/
if (win_no > 0) {
u32 keycon0_data = 0, keycon1_data = 0;
keycon0_data = ~(WxKEYCON0_KEYBL_EN |
WxKEYCON0_KEYEN_F |
WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0);
keycon1_data = WxKEYCON1_COLVAL(0xff00);
writel(keycon0_data, regs + WxKEYCONy(win_no-1, 0));
writel(keycon1_data, regs + WxKEYCONy(win_no-1, 1));
}
/*win的相关参数设置完毕,使能fb设备*/
writel(data, regs + WINCON(win_no));
writel(0x0, regs + WINxMAP(win_no));
static int __devinit s3c_fb_probe(struct platform_device *pdev)分析,(重点)
每个驱动加载后都会用
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
里的match函数进行设备和驱动的匹配,若匹配成功,则执行probe函数
struct device *dev = &pdev->dev;//定义并获取平台设备结构体
struct s3c_fb_platdata *pd; // 定义帧缓冲设备的平台数据
struct s3c_fb *sfb; //帧缓冲设备结构体
struct resource *res; //帧缓冲平台资源
int win;
pd = pdev->dev.platform_data; //取得平台设备里平台数据
sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL);//分配帧缓冲结构体内存
sfb->dev = dev;
sfb->pdata = pd; //设置帧缓冲结构体相关设置
sfb->bus_clk = clk_get(dev, "lcd"); //获取时钟
clk_enable(sfb->bus_clk);//使能时钟
/*获取平台资源*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*申请内存注册并放入帧缓冲结构体*/
sfb->regs_res = request_mem_region(res->start, resource_size(res),dev_name(dev));
/*映射资源*/
sfb->regs = ioremap(res->start, resource_size(res));
/* setup gpio and output polarity controls */
pd->setup_gpio();
writel(pd->vidcon1, sfb->regs + VIDCON1);
/*设置参数前,将所有win的寄存器清零*/
for (win = 0; win < S3C_FB_MAX_WIN; win++)
s3c_fb_clear_win(sfb, win);
/*根据不同的win,进行相应的匹配*/
ret = s3c_fb_probe_win(sfb, win, &sfb->windows[win]);
static int __devinit s3c_fb_probe_win()分析
struct fb_var_screeninfo *var; //帧缓冲可变参数结构体
struct fb_videomode *initmode; //多媒体模式设置
struct s3c_fb_pd_win *windata; //该窗口的平台数据
struct s3c_fb_win *win; //用于描述本窗口的结构体
struct fb_info *fbinfo; //帧缓冲结构体
/*分配fb_info结构体*/
fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) + palette_size * sizeof(u32), sfb->dev);
/*获取窗口平台数据*/
windata = sfb->pdata->win[win_no];
/*获取初始化数据*/
initmode = &windata->win_mode;
/*一系列数据的获取*/
win = fbinfo->par;
var = &fbinfo->var;
win->fbinfo = fbinfo;
win->parent = sfb;
win->windata = windata;
win->index = win_no;
win->palette_buffer = (u32 *)(win + 1);
/*分配显存*/
ret = s3c_fb_alloc_memory(sfb, win);
/* setup the r/b/g positions for the window's palette */
s3c_fb_init_palette(win_no, &win->palette);
/* setup the initial video mode from the window */
fb_videomode_to_var(&fbinfo->var, initmode);
/*设置结构体*/
fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.accel = FB_ACCEL_NONE;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
fbinfo->var.bits_per_pixel = windata->default_bpp;
fbinfo->fbops = &s3c_fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &win->pseudo_palette;
/*核对可变参数*/
ret = s3c_fb_check_var(&fbinfo->var, fbinfo);
/*构建颜色表*/
ret = fb_alloc_cmap(&fbinfo->cmap, s3c_fb_win_pal_size(win_no), 1);
fb_set_cmap(&fbinfo->cmap, fbinfo);
/*设置结构体中的参数,重点,该函数上面已分析*/
s3c_fb_set_par(fbinfo);
/*注册帧缓冲设备*/
ret = register_framebuffer(fbinfo);
Dev-fb.c(资源层、提供寄存器的地址)
/*各种资源*/
static struct resource s3c_fb_resource[] = {
[0] = { //I/O内存资源
.start = S3C_PA_FB,
.end = S3C_PA_FB + SZ_16K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD_VSYNC,
.end = IRQ_LCD_VSYNC,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_LCD_FIFO,
.end = IRQ_LCD_FIFO,
.flags = IORESOURCE_IRQ,
},
[3] = {
.start = IRQ_LCD_SYSTEM,
.end = IRQ_LCD_SYSTEM,
.flags = IORESOURCE_IRQ,
},
};
/*平台设备结构体*/
struct platform_device s3c_device_fb = {
.name = "s3c-fb",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_fb_resource),
.resource = s3c_fb_resource,
.dev.dma_mask = &s3c_device_fb.dev.coherent_dma_mask,
.dev.coherent_dma_mask = 0xffffffffUL,
};
void __init s3c_fb_set_platdata ()//设置平台数据
Mach-smdkc100.c
* LCD power controller */
static void smdkc100_lcd_power_set(struct plat_lcd_data *pd,
unsigned int power)
{
/* backlight */
gpio_direction_output(S5PC100_GPD(0), power);
if (power) {
/* module reset */
gpio_direction_output(S5PC100_GPH0(6), 1);
mdelay(100);
gpio_direction_output(S5PC100_GPH0(6), 0);
mdelay(10);
gpio_direction_output(S5PC100_GPH0(6), 1);
mdelay(10);
}
}
static struct plat_lcd_data smdkc100_lcd_power_data = {
.set_power = smdkc100_lcd_power_set,
};
/*LCD电源控制器的驱动,在机器启动的时候执行*/
static struct platform_device smdkc100_lcd_powerdev = {
.name = "platform-lcd",
.dev.parent = &s3c_device_fb.dev,
.dev.platform_data = &smdkc100_lcd_power_data,
};
static void __init smdkc100_machine_init
//该函数在机器启动的时候执行,开机就初始化的函数都放这儿
static void __init smdkc100_machine_init(void)
{
/* I2C */
s3c_i2c0_set_platdata(NULL);
s3c_i2c1_set_platdata(NULL);
i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
s3c_fb_set_platdata(&smdkc100_lcd_pdata);
/* LCD init */
gpio_request(S5PC100_GPD(0), "GPD");
gpio_request(S5PC100_GPH0(6), "GPH0");
smdkc100_lcd_power_set(&smdkc100_lcd_power_data, 0);
platform_add_devices(smdkc100_devices, ARRAY_SIZE(smdkc100_devices));
/*TS*/
#if defined(CONFIG_TOUCHSCREEN_S3C2410)
s3c24xx_ts_set_platdata(&s5pc100_ts_cfg);
#endif
}
static struct s3c_fb_platdata smdkc100_lcd_pdata __initdata = {
.win[0] = &smdkc100_fb_win0,
.win[1] = &smdkc100_fb_win1,
//设置 output format of Video Controller(=000)
//和Selects the display mode(=00)分别为:000: RGB I/F和00 = RGB Parallel format (RGB)
.vidcon0 = VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,
//设置相反信号极性
.vidcon1 = VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,
//用来初始化管脚,配置成LCD模式。
.setup_gpio = s5pc100_fb_gpio_setup_24bpp,
};