2012 年 3月 30日 星期五
最近看了lcd驱动,本着不注解无真相的精神,写下android2.3.4下,linux2.6.36内核的lcd驱动
硬件平台:mini6410+4.3LCD
系统平台:android2.3.4+linux2.6.36
驱动文件:s3c-fb.c
我们这里主要分析s3c-fb.c中的probe函数, probe函数里最重要的是s3c_fb_probe_win函数
首先解释一个概念,很多书中有framebuffer这个概念,但是在三星的显示控制器文档或代码中,常出现win或window的概念,显示控制器可以控制0~5个windows,代码中分给它们分别编号win0, win1,win2......这里一张win或window就对应一个framebuffer, 每个framebuffer有自己的一个FBI(fb_info)结构,
代码中, 显示控制器是s3c_fb结构体, window是s3c_fb_win结构体。
代码中有两种data,一种是platform data(在板文件中定义),另一种是driver data(在驱动文件中定义),在它们各自的结构体里面,又可以分为两部份,一是用于sfb的data, 另一是用于win的data。
framebuffer是fb_info结构体,里面主要存储设置参数的数据结构有两个,fb_var_screeninfo和fb_fix_screeninfo结构体。
1、同IIC设备一样,首先需要在mach-mini6410.c 文件中配置和创建平台设备(platform),在linux2. 6设备模型中,关心总线,设备,驱动这三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动。相反,在系统每注册一个驱动的时候,寻找与之匹配的设备,匹配是由总线来完成的。一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC 系统中集成的独立的外设控制器、挂接在SoC 内存空间的外设等确不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为platform 总线。SOC系统中集成的独立外设单元(LCD,RTC,WDT等)都被当作平台设备来处理,而它们本身是字符型设备。其实可以和IIC设备比较一下,IIC的控制器为IIC适配器,LCD为LCD控制器(平台设备)。下面是mach-mini6410.c 文件中platform device的创建:
static struct platform_device *mini6410_devices[] __initdata = {
#ifdef CONFIG_MINI6410_SD_CH0
&s3c_device_hsmmc0,
#endif
#ifdef CONFIG_MINI6410_SD_CH1
&s3c_device_hsmmc1,
#endif
&s3c_device_i2c0,
#ifdef CONFIG_S3C_DEV_I2C1
&s3c_device_i2c1,
#endif
&s3c_device_nand,
&s3c_device_fb, //这个就是LCD控制器结构体
&s3c_device_ohci,
&s3c_device_usb_hsotg,
#ifdef CONFIG_SND_S3C_SOC_AC97
&s3c64xx_device_ac97,
#else
&s3c64xx_device_iisv4,
#endif
&mini6410_lcd_powerdev,
&usb_mass_storage_device,
#ifdef CONFIG_DM9000
&s3c_device_dm9000,
#endif
#ifdef CONFIG_S3C_ADC
&s3c_device_adc,
#endif
#if defined(CONFIG_TOUCHSCREEN_MINI6410) || defined(CONFIG_SAMSUNG_DEV_TS)
&s3c_device_ts,
#endif
&s3c_device_wdt,
#ifdef CONFIG_S3C_DEV_RTC
&s3c_device_rtc,
#endif
&s3c_device_gpio_btns,
&s3c_device_android_usb,
/* Multimedia support */
#ifdef CONFIG_VIDEO_SAMSUNG
&s3c_device_vpp,
&s3c_device_mfc,
&s3c_device_tvenc,
&s3c_device_tvscaler,
&s3c_device_rotator,
&s3c_device_jpeg,
&s3c_device_fimc0,
&s3c_device_fimc1,
&s3c_device_g2d,
&s3c_device_g3d,
&android_pmem_device,
#ifdef CONFIG_VIDEO_G3D
&android_pmem_gpu1_device,
&android_pmem_adsp_device,
#endif
#endif
};
可以看到平台设备可以包含很多控制器,这里需要添加&s3c_device_fb这个结构体,然后在static void __init mini6410_map_io(void)函数中增加两个函数
1) s3c_fb_set_platdata ( &mini6410_lcd_pdata );,其中的mini6410_lcd_pdata记录了LCD的机器信息(硬件特征),函数的作用为将LCD硬件特征赋值给平台设备的平台数据platrorm_data。其中mini6410_lcd_pdata为
static struct s3c_fb_platdata mini6410_lcd_pdata __initdata = {
.setup_gpio = s3c64xx_fb_gpio_setup_24bpp,
.win[0] = &mini6410_fb_win0,//只定义了win【0】,其中包含了LCD设备的信息
/* static struct s3c_fb_pd_win mini6410_fb_win0 = {
.win_mode = {
#if 0
.pixclock = 115440,
#endif
.left_margin = 0x03,
.right_margin = 0x02,
.upper_margin = 0x01,
.lower_margin = 0x01,
.hsync_len = 0x28,
.vsync_len = 0x01,
.xres = 480,
.yres = 272,
},
.max_bpp = 32,
.default_bpp = 16,
};*/
.max_bpp = 32,
.default_bpp = 16,
};
.vidcon0 = VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,
.vidcon1 = VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,
};
也就是将平台数据进行了赋值,以备后面的s3c-fb.c函数中使用
2)platform_add_devices(mini6410_devices, ARRAY_SIZE(mini6410_devices));将所有的平台设备添加到系统中,其中包括&s3c_device_fb,来看下这个结构体
struct platform_device s3c_device_fb = {
.name = "s3c-fb", //设备名称,同driver匹配的时候要用
.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,
};
其中的s3c_fb_resource为平台设备所要用的资源
static struct resource s3c_fb_resource[] = {
[0] = { //IO内存资源
.start = S3C_PA_FB,
.end = S3C_PA_FB + SZ_16K - 1,
.flags = IORESOURCE_MEM,
},
[1] = { //IRQ资源
.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,
},
};
至此,LCD控制器平台设备创建完成
2、驱动文件s3c-fb.c
static int __init s3c_fb_init(void)
{
return platform_driver_register(&s3c_fb_driver);
}
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,
.id_table = s3c_fb_driver_ids,
.driver = {
.name = "s3c-fb",//用于寻找对应的平台设备,与mach-mini6410.c中定义的LCD平台设备相对应
.owner = THIS_MODULE,
},
};
static struct platform_device_id s3c_fb_driver_ids[] = {
{
.name = "s3c-fb",
.driver_data = (unsigned long)&s3c_fb_data_64xx,
}, {
.name = "s5pc100-fb",
.driver_data = (unsigned long)&s3c_fb_data_s5pc100,
}, {
.name = "s5pv210-fb",
.driver_data = (unsigned long)&s3c_fb_data_s5pv210,
}, {
.name = "s3c2443-fb",
.driver_data = (unsigned long)&s3c_fb_data_s3c2443,
},
{},
};
MODULE_DEVICE_TABLE(platform, s3c_fb_driver_ids);
同IIC设备驱动一样,这里也有与mach-mini6410.c中相呼应的platform_device_id,另外注意此结构体中有一项driver_data,是进行了初始化的,在后面的probe函数中是要进行调用的,
首先进行平台驱动的注册,platform_driver_register(&s3c_fb_driver);此会导致平台驱动中的s3c_fb_probe函数(探测函数)被调用。这个探测函数会完成LCD硬件的初始化,FBI(帧缓存设备结构体,后面会说)参数的填充,注册显示缓冲区,并注册帧缓存设备。
static int __devinit s3c_fb_probe(struct platform_device *pdev)
{
struct s3c_fb_driverdata *fbdrv;
struct device *dev = &pdev->dev;
struct s3c_fb_platdata *pd;
struct s3c_fb *sfb;
struct resource *res;
int win;
int ret = 0;
fbdrv = (struct s3c_fb_driverdata *)platform_get_device_id(pdev)->driver_data; /*获取对应的driver data,也就是上面定义了的s3c_fb_data_64xx
static struct s3c_fb_driverdata s3c_fb_data_64xx = {
.variant = {
.nr_windows = 5,
.vidtcon = VIDTCON0,
.wincon = WINCON(0),
.winmap = WINxMAP(0),
.keycon = WKEYCON,
.osd = VIDOSD_BASE,
.osd_stride = 16,
.buf_start = VIDW_BUF_START(0),
.buf_size = VIDW_BUF_SIZE(0),
.buf_end = VIDW_BUF_END(0),
.palette = {
[0] = 0x400,
[1] = 0x800,
[2] = 0x300,
[3] = 0x320,
[4] = 0x340,
},
.has_prtcon = 1,
},
.win[0] = &s3c_fb_data_64xx_wins[0],
.win[1] = &s3c_fb_data_64xx_wins[1],
.win[2] = &s3c_fb_data_64xx_wins[2],
.win[3] = &s3c_fb_data_64xx_wins[3],
.win[4] = &s3c_fb_data_64xx_wins[4],
};*/
if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) {
dev_err(dev, "too many windows, cannot attach\n");
return -EINVAL;
}
pd = pdev->dev.platform_data; //将平台数据赋值, 在mach-mini6410.c中已经对平台数据进行了填充(s3c_fb_set_platdata),这次是将其转到s3c_fb_platdata结构中
if (!pd) {
dev_err(dev, "no platform data specified\n");
return -EINVAL;
}
sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL);//申请帧缓冲结构体空间
if (!sfb) {
dev_err(dev, "no memory for framebuffers\n");
return -ENOMEM;
}
dev_dbg(dev, "allocate new framebuffer %p\n", sfb);
sfb->dev = dev; //对s3c-fb结构体进行填充
sfb->pdata = pd;
sfb->variant = fbdrv->variant;
sfb->bus_clk = clk_get(dev, "lcd");
if (IS_ERR(sfb->bus_clk)) {
dev_err(dev, "failed to get bus clock\n");
goto err_sfb;
}
clk_enable(sfb->bus_clk);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获得LCD控制器IO口内存资源
if (!res) {
dev_err(dev, "failed to find registers\n");
ret = -ENOENT;
goto err_clk;
}
sfb->regs_res = request_mem_region(res->start, resource_size(res),
dev_name(dev));//sfb中regs_res指针重定向到申请到的IO内存资源中,其中的request_mem_region函数的释义为:
. request_mem_region() -- I/O内存申请usr/src/linux-2.6.21.5/kernel/resource.c.request_mem_region() -- 将起始地址为[start, start+n-1]的资源插入根资源iomem_resource中。参数start是I/O内存资源的起始物理地址(是CPU的RAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。注: 调用request_mem_region()不是必须的,但是建议使用。
if (!sfb->regs_res) {
dev_err(dev, "failed to claim register region\n");
ret = -ENOENT;
goto err_clk;
}
sfb->regs = ioremap(res->start, resource_size(res));//将一个IO地址空间映射到内核的虚拟地址空间上去,其中res->start为IO空间(物理地址)首地址, resource_size(res)是需要映射的长度,另外应该返回值就是映射的虚拟地址的首地址(未得到证实)
if (!sfb->regs) {
dev_err(dev, "failed to map registers\n");
ret = -ENXIO;
goto err_req_region;
}
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);//获得LCD控制器IRQ内存资源(mach-mini6410.c中定义的 resource 中,flag为 IORESOURCE_IRQ的一共有三个,获得的是哪一个?看到0了吗?说明是第一个,也就是IRQ_LCD_VSYNC,帧同步中断)
if (!res) {
dev_err(dev, "failed to acquire irq resource\n");
ret = -ENOENT;
goto err_ioremap;
}
sfb->irq_no = res->start;
ret = request_irq(sfb->irq_no, s3c_fb_irq,
0, "s3c_fb", sfb); //中断注册 其中已经构建好的sfb作为参数进行传递
if (ret) {
dev_err(dev, "irq request failed\n");
goto err_ioremap;
}
dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs);
/* setup gpio and output polarity controls */
pd->setup_gpio();/*其实也就是调用s3c64xx_fb_gpio_setup_24bpp这个函数(在mach-mini6410.c中设置了平台数据,可以参照上面的1-1)的说明),也就是对LCD控制器用到的GPIO口进行了设置。具体入下: extern void s3c64xx_fb_gpio_setup_24bpp(void)
{
unsigned int gpio;
for (gpio = S3C64XX_GPI(0); gpio <= S3C64XX_GPI(15); gpio++) {
s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
}
for (gpio = S3C64XX_GPJ(0); gpio <= S3C64XX_GPJ(11); gpio++) {
s3c_gpio_cfgpin(gpio, S3C_GPIO_SFN(2));
s3c_gpio_setpull(gpio, S3C_GPIO_PULL_NONE);
}
}
从字面意思上看,他是按24位色进行GPIO设置的*/
writel(pd->vidcon1, sfb->regs + VIDCON1); //对VIDCON1寄存器进行设置,可以查看6410的spec进行查看(硬件层的东西)。
/* zero all windows before we do anything */
for (win = 0; win < fbdrv->variant.nr_windows; win++)
s3c_fb_clear_win(sfb, win);/*static void s3c_fb_clear_win(struct s3c_fb *sfb, int win)
{
void __iomem *regs = sfb->regs;
u32 reg;
writel(0, regs + sfb->variant.wincon + (win * 4));
writel(0, regs + VIDOSD_A(win, sfb->variant));
writel(0, regs + VIDOSD_B(win, sfb->variant));
writel(0, regs + VIDOSD_C(win, sfb->variant));
reg = readl(regs + SHADOWCON);
writel(reg & ~SHADOWCON_WINx_PROTECT(win), regs + SHADOWCON);
}
*/
函数作用为将wincon寄存器以及VIDOSD寄存器全部清零,禁止update各个window的shadow。(这个具体要分析6410的spec)
/* initialise colour key controls */
for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
void __iomem *regs = sfb->regs + sfb->variant.keycon;
regs += (win * 8);
writel(0xffffff, regs + WKEYCON0);
writel(0xffffff, regs + WKEYCON1);
}
/* we have the register setup, start allocating framebuffers */
for (win = 0; win < fbdrv->variant.nr_windows; win++) {
if (!pd->win[win])//mach-mini6410.c文件的platform_data文件中只定义了win【0】所以,下面的s3c_fb_probe_win(sfb, win, fbdrv->win[win], &sfb->windows[win]);中,参数只传入了win【0】。
continue;
if (!pd->win[win]->win_mode.pixclock)
s3c_fb_missing_pixclock(&pd->win[win]->win_mode);
ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win], &sfb->windows[win]); /*分配 及 注册framebuffer的重要函数
static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
struct s3c_fb_win_variant *variant,
struct s3c_fb_win **res)
{
struct fb_var_screeninfo *var;
struct fb_videomode *initmode;
struct s3c_fb_pd_win *windata;
struct s3c_fb_win *win;
struct fb_info *fbinfo;
int palette_size;
int ret;
dev_dbg(sfb->dev, "probing window %d, variant %p\n", win_no, variant);
初始化等待队列头
init_waitqueue_head(&sfb->vsync_info.wait);
调色板大小 , 这方面内容我还不了解,为什么乘以4?
palette_size = variant->palette_sz * 4;
分配fb_info结构体,返回一个fb_info结构体地址,这个结构体现在没什么内容,只赋值了par(win的起始地址)和device(也就是sfb->dev) (父设备)两个变量
fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +
palette_size * sizeof(u32), sfb->dev);
if (!fbinfo) {
dev_err(sfb->dev, "failed to allocate framebuffer\n");
return -ENOENT;
}
windata = sfb->pdata->win[win_no];
initmode = &windata->win_mode;
WARN_ON(windata->max_bpp == 0);
WARN_ON(windata->win_mode.xres == 0);
WARN_ON(windata->win_mode.yres == 0);
win = fbinfo->par;
*res = win; --->par就是win的起始地址,现在把起始地址给*res,那么*res就是指向s3c_fb_win的指针
var = &fbinfo->var; ---> 现在fbinfo->var还是空的, 只是将地址给var而已
win->variant = *variant; --->将win的参数填进win->variant里
win->fbinfo = fbinfo; ---> 让win->fbinfo指向这个FBI结构实体
win->parent = sfb; ---> win的parent是显示控制器,所以它指向sfb结构体
win->windata = windata; --->windata指向 platform data中win的部份
win->index = win_no;
win->palette_buffer = (u32 *)(win + 1); --->这个也不太理解?
ret = s3c_fb_alloc_memory(sfb, win);//为framebuffer申请内存空间,这个函数在最后进行注解
if (ret) {
dev_err(sfb->dev, "failed to allocate display memory\n");
return ret;
}
//setup the r/b/g positions for the window's palette
if (win->variant.palette_16bpp) {->设置调色板信息,由于之前传入的是win【0】所以,下面else中的内容
//Set RGB 5:6:5 as default
win->palette.r.offset = 11;
win->palette.r.length = 5;
win->palette.g.offset = 5;
win->palette.g.length = 6;
win->palette.b.offset = 0;
win->palette.b.length = 5;
} else {
//Set 8bpp or 8bpp and 1bit alpha
win->palette.r.offset = 16;
win->palette.r.length = 8;
win->palette.g.offset = 8;
win->palette.g.length = 8;
win->palette.b.offset = 0;
win->palette.b.length = 8;
}
// setup the initial video mode from the window
fb_videomode_to_var(&fbinfo->var, initmode);--> 给FBI填上各个参数
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; -->16位,mach-mini6410.c 中,platform_data定义的win【0】中进行的赋值(不明白为什么是16位,前面都是按24位设置的啊)
fbinfo->fbops = &s3c_fb_ops; ---->对framebuffer的操作
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &win->pseudo_palette;
// prepare to actually start the framebuffer
ret = s3c_fb_check_var(&fbinfo->var, fbinfo); -->检查可变参数
if (ret < 0) {
dev_err(sfb->dev, "check_var failed on initial video params\n");
return ret;
}
// create initial colour map
ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
if (ret == 0)
fb_set_cmap(&fbinfo->cmap, fbinfo);
else
dev_err(sfb->dev, "failed to allocate fb cmap\n");
s3c_fb_set_par(fbinfo);
dev_dbg(sfb->dev, "about to register framebuffer\n");
//run the check_var and set_par on our configuration.
ret = register_framebuffer(fbinfo);//最终注册FBI。在/dev/graphics下产生fb设备文件
if (ret < 0) {
dev_err(sfb->dev, "failed to register framebuffer\n");
return ret;
}
dev_info(sfb->dev, "window %d: fb %s\n", win_no, fbinfo->fix.id);
return 0;
}
*/
if (ret < 0) {
dev_err(dev, "failed to create window %d\n", win);
for (; win >= 0; win--)
s3c_fb_release_win(sfb, sfb->windows[win]);
goto err_irq;
}
}
platform_set_drvdata(pdev, sfb);// 将sfb 填入pdev->dev->p->driverdata 结构体中????????????????
return 0;
err_irq:
free_irq(sfb->irq_no, sfb);
err_ioremap:
iounmap(sfb->regs);
err_req_region:
release_resource(sfb->regs_res);
kfree(sfb->regs_res);
err_clk:
clk_disable(sfb->bus_clk);
clk_put(sfb->bus_clk);
err_sfb:
kfree(sfb);
return ret;
}
附录:
1、s3c_fb_probe_win中的函数 s3c_fb_alloc_memory 为framebuffer申请内存空间
static int __devinit s3c_fb_alloc_memory(struct s3c_fb *sfb,
struct s3c_fb_win *win)
{
struct s3c_fb_pd_win *windata = win->windata;//platform_data中的win部分,也就是win【0】
unsigned int real_size, virt_size, size;
struct fb_info *fbi = win->fbinfo;
dma_addr_t map_dma;
dev_dbg(sfb->dev, "allocating memory for display\n");
real_size = windata->win_mode.xres * windata->win_mode.yres;//win[0]中的值,xres=480,yres=272.
virt_size = windata->virtual_x * windata->virtual_y;//win[0]中并没有定义virtual_x、virtual_y。
dev_dbg(sfb->dev, "real_size=%u (%u.%u), virt_size=%u (%u.%u)\n",
real_size, windata->win_mode.xres, windata->win_mode.yres,
virt_size, windata->virtual_x, windata->virtual_y);
size = (real_size > virt_size) ? real_size : virt_size;
size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;//win【0】中定义的max_bpp=32
size /= 8;
fbi->fix.smem_len = size;//分配内存大小
size = PAGE_ALIGN(size);//页对齐
dev_dbg(sfb->dev, "want %u bytes for window\n", size);
fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,
&map_dma, GFP_KERNEL);//分配framebuffer内存,关于为何要用dma_alloc_writecombine,在《linux设备驱动程序详解》的LCD驱动里有介绍
if (!fbi->screen_base)
return -ENOMEM;
dev_dbg(sfb->dev, "mapped %x to %p\n",
(unsigned int)map_dma, fbi->screen_base);
memset(fbi->screen_base, 0x0, size);//为分配到的内存清零。
fbi->fix.smem_start = map_dma;
return 0;
}
.目前存在的问题点:1、在填充FBI结构体的fbinfo->var.bits_per_pixel时,填充的是16位,可之前6410寄存器设置的都是按24位进行设置的(fbinfo->var.bits_per_pixel = windata->default_bpp)。
2、在probe的s3c_fb_probe_win函数中进行了 register_framebuffer(fbinfo);此函数中调用了creat_device函数, 但是奇怪的是在驱动加载后并没有在/dev/下找到fb的相关设备文件,也没有主设备号为29的设备文件(平台设备主设备号均为29).
此问题已经解决,是因为在device_creat调用之前已经对设备类进行了定义fb_class = class_create(THIS_MODULE, "graphics");(fbmem.c文件中)所以设备文件的地址在/dev/graphics/下,一共定义了两个即fb0,fb1.
3、注意,以上程序中只是涉及到了,触摸屏平台设备驱动的编写,实际上I2C适配器也是平台设备,6410中沿用了2410的适配器驱动文件i2c-s3c2410.c(由于在mach-mini6410.c中的平台设备中定义了I2C的平台设备,其中定义了I2C适配器寄存器的起始位置和终止位置,而s3c2410的寄存器地址偏移同6410的相同,所以可以使用同一个适配器驱动文件)