s3c6410_LCD & frame buffer 驱动分析
static struct platform_driver s3cfb_driver = {
.probe = s3cfb_probe,
.remove = s3cfb_remove,
.suspend = s3cfb_suspend,
.resume = s3cfb_resume,
.driver = {
.name = "s3c-fb",
.owner = THIS_MODULE,
},
};
int __devinit s3cfb_init(void)
{
/**模块加载函数实现平台驱动的注册**/
return platform_driver_register(&s3cfb_driver);
}
static void __exit s3cfb_cleanup(void)
{
platform_driver_unregister(&s3cfb_driver);
}
module_init(s3cfb_init);
module_exit(s3cfb_cleanup);
//s3cfb_probe平台驱动的探测函数,主要初始化fb_info结构体中的可变参数和不可变参数结构体,LCD硬件控制器的初始化,获取时钟来源,显示缓存空间的申请,注册帧缓冲设备等工作。
static int __init s3cfb_probe(struct platform_device *pdev)
{
/*
*定义局部变量
*/
struct resource *res; //用于保存platform_device所需要的平台资源
struct fb_info *fbinfo; //描述frambuffer驱动的结构
s3cfb_info_t *info; //保存LCD驱动相关的各种信息
char driver_name[] = "s3cfb";//驱动名字
int index = 0, ret, size;//临时变量
//给s3cfb_info_t结构体分配空间
fbinfo = framebuffer_alloc(sizeof(s3cfb_info_t), &pdev->dev);
if (!fbinfo)
return -ENOMEM;
//关联平台设备和平台驱动pdev->p->driver_data = fbinfo;
platform_set_drvdata(pdev, fbinfo);//将pdev保存成平台总线设备的私有数据
info = fbinfo->par;
info->dev = &pdev->dev;
//获取平台设备所需要的资源,这些资源一般在bsp文件中被赋值
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get memory registers\n");
ret = -ENXIO;
goto dealloc_fb;
}
//计算资源大小
size = (res->end - res->start) + 1;
//为IO资源分配内存,如果分配成功则返回非NULL
info->mem = request_mem_region(res->start, size, pdev->name);
if (info->mem == NULL) {
dev_err(&pdev->dev, "failed to get memory region\n");
ret = -ENOENT;
goto dealloc_fb;
}
//IO内存是不能直接访问的,需要将IO内存映射成虚拟地址空间
info->io = ioremap(res->start, size);
if (info->io == NULL) {
dev_err(&pdev->dev, "ioremap() of registers failed\n");
ret = -ENXIO;
goto release_mem;
}
//初始化视频中断寄存器0
s3cfb_pre_init();// Video Frame Interrupt 0 at start of vsync 并使能该寄存器
//给LCD上电
s3cfb_set_lcd_power(1);
//设置背光电源
s3cfb_set_backlight_power(1);
//设置LCD电源
s3cfb_set_lcd_power(1);
//设置背光亮度为默认亮度
s3cfb_set_backlight_level(S3CFB_DEFAULT_BACKLIGHT_LEVEL);
//获取时钟并使能
info->clk = clk_get(NULL, "lcd");
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_INFO "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_io;
}
//使能时钟
clk_enable(info->clk);
printk("S3C_LCD clock got enabled :: %ld.%03ld Mhz\n", PRINT_MHZ(clk_get_rate(info->clk)));
//初始化帧同步信号等待队列
s3cfb_fimd.vsync_info.count = 0;
init_waitqueue_head(&s3cfb_fimd.vsync_info.wait_queue);
//获取中断资源
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL) {
dev_err(&pdev->dev, "failed to get irq\n");
ret = -ENXIO;
goto release_clock;
}
//申请中断,中断处理函数s3cfb_irq,中断名称s3c-lcd
ret = request_irq(res->start, s3cfb_irq, 0, "s3c-lcd", pdev);
if (ret != 0) {
printk("Failed to install irq (%d)\n", ret);
goto release_clock;
}
//睡眠10秒
msleep(10);
//对于s3c6410 LCD控制器一共有5个窗口,每一个窗口都可以看成是一个frambuffer设备,上面的fb_info还没分配内存呢
for(index = 0; index < S3CFB_NUM; index++){
s3cfb_info[index].mem = info->mem;//平台资源
s3cfb_info[index].io = info->io; //IO内存
s3cfb_info[index].clk = info->clk;//时钟
//填充s3cfb_info_t结构
s3cfb_init_fbinfo(&s3cfb_info[index], driver_name, index);
/* Initialize video memory */
ret = s3cfb_map_video_memory(&s3cfb_info[index]);
if (ret) {
printk("Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_irq;
}
ret = s3cfb_init_registers(&s3cfb_info[index]);
ret = s3cfb_check_var(&s3cfb_info[index].fb.var, &s3cfb_info[index].fb);
if (index < 2){
if (fb_alloc_cmap(&s3cfb_info[index].fb.cmap, 256, 0) < 0)
goto dealloc_fb;
} else {
if (fb_alloc_cmap(&s3cfb_info[index].fb.cmap, 16, 0) < 0)
goto dealloc_fb;
}
ret = register_framebuffer(&s3cfb_info[index].fb);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}
printk(KERN_INFO "fb%d: %s frame buffer device\n", s3cfb_info[index].fb.node, s3cfb_info[index].fb.fix.id);
}
/* create device files */
ret = device_create_file(&(pdev->dev), &dev_attr_backlight_power);
if (ret < 0)
printk(KERN_WARNING "s3cfb: failed to add entries\n");
ret = device_create_file(&(pdev->dev), &dev_attr_backlight_level);
if (ret < 0)
printk(KERN_WARNING "s3cfb: failed to add entries\n");
ret = device_create_file(&(pdev->dev), &dev_attr_lcd_power);
if (ret < 0)
printk(KERN_WARNING "s3cfb: failed to add entries\n");
printk("jkq debug VIDCON0 is %x\n",readl(S3C_VIDCON0));
return 0;
//下面这些是错误处理函数
free_video_memory:
s3cfb_unmap_video_memory(&s3cfb_info[index]);
release_irq:
free_irq(res->start, &info);
release_io:
iounmap(info->io);
dealloc_fb:
framebuffer_release(fbinfo);
return ret;
release_mem:
release_resource(info->mem);
kfree(info->mem);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
}
//好了接着对probe中所调用的函数从上到下一次分析
/*初始化LCD视频中断控制寄存器0**/
void s3cfb_pre_init(void)
{
/* initialize the fimd specific */
s3cfb_fimd.vidintcon0 &= ~S3C_VIDINTCON0_FRAMESEL0_MASK;
s3cfb_fimd.vidintcon0 |= S3C_VIDINTCON0_FRAMESEL0_VSYNC;
s3cfb_fimd.vidintcon0 |= S3C_VIDINTCON0_INTFRMEN_ENABLE;
/**详情请看S3C6410数据手册第14章482页。。初始化视频中断以VSYNS开始并使能该寄存器*/
writel(s3cfb_fimd.vidintcon0, S3C_VIDINTCON0);
}
//LCD电源控制
static void s3cfb_set_lcd_power(int to)
{
s3cfb_fimd.lcd_power = to;
if (s3cfb_fimd.set_lcd_power)
(s3cfb_fimd.set_lcd_power)(to);
}
//LCD背光电源控制
static void s3cfb_set_backlight_power(int to)
{
s3cfb_fimd.backlight_power = to;
if (s3cfb_fimd.set_backlight_power)
(s3cfb_fimd.set_backlight_power)(to);
}
//LCD背光亮度调节
static void s3cfb_set_backlight_level(int to)
{
s3cfb_fimd.backlight_level = to;
if (s3cfb_fimd.set_brightness)
(s3cfb_fimd.set_brightness)(to);
}
/*
*填充每一个s3cfb_info_t结构
*/
static void s3cfb_init_fbinfo(s3cfb_info_t *finfo, char *drv_name, int index)
{
int i = 0;
if (index == 0)
{//lcdsize是一全局变量用来标记LCD所使用的尺寸。
if(lcdsize == 0)
s3cfb_init_hw_35();
else if(lcdsize == 1)
s3cfb_init_hw_43();
else if(lcdsize == 2)
s3cfb_init_hw_56();
else if(lcdsize == 3)
s3cfb_init_hw_70();
else if(lcdsize == 4)
s3cfb_init_hw_vga800();
else
s3cfb_init_hw_43();
}
strcpy(finfo->fb.fix.id, drv_name);//初始化不可变参数结构中的char id[16]数组
//填充不可变参数结构
finfo->win_id = index;
finfo->fb.fix.type = FB_TYPE_PACKED_PIXELS;
finfo->fb.fix.type_aux = 0;
finfo->fb.fix.xpanstep = 0;
finfo->fb.fix.ypanstep = 1;
finfo->fb.fix.ywrapstep = 0;
finfo->fb.fix.accel = FB_ACCEL_NONE;
finfo->fb.fbops = &s3cfb_ops;//指向frambuffer函数操作集
finfo->fb.flags = FBINFO_FLAG_DEFAULT;
finfo->fb.pseudo_palette = &finfo->pseudo_pal;
//填充可变参数结构
finfo->fb.var.nonstd = 0;
finfo->fb.var.activate = FB_ACTIVATE_NOW;
finfo->fb.var.accel_flags = 0;
finfo->fb.var.vmode = FB_VMODE_NONINTERLACED;
finfo->fb.var.xoffset = s3cfb_fimd.xoffset;//水平方向虚拟到可见之间的偏移
finfo->fb.var.yoffset = s3cfb_fimd.yoffset;//垂直方向虚拟到可见之间的偏移
if (index == 0) {
finfo->fb.var.height = s3cfb_fimd.height;
finfo->fb.var.width = s3cfb_fimd.width;
finfo->fb.var.xres = s3cfb_fimd.xres;
finfo->fb.var.yres = s3cfb_fimd.yres;
finfo->fb.var.xres_virtual = s3cfb_fimd.xres_virtual;
finfo->fb.var.yres_virtual = s3cfb_fimd.yres_virtual;
} else {
finfo->fb.var.height = s3cfb_fimd.osd_height;//图像高度
finfo->fb.var.width = s3cfb_fimd.osd_width;//图像宽度
finfo->fb.var.xres = s3cfb_fimd.osd_xres;//水平方向真实可见的解析度
finfo->fb.var.yres = s3cfb_fimd.osd_yres;//垂直方向真实可见的解析度
finfo->fb.var.xres_virtual = s3cfb_fimd.osd_xres_virtual;//水平方向虚拟可见的解析度
finfo->fb.var.yres_virtual = s3cfb_fimd.osd_yres_virtual;//水平方向虚拟可见的解析度
}
//填充可变参数结构
finfo->fb.var.bits_per_pixel = s3cfb_fimd.bpp;//每一个像素点多少bpp
finfo->fb.var.pixclock = s3cfb_fimd.pixclock; //像素时钟
finfo->fb.var.hsync_len = s3cfb_fimd.hsync_len; //HSPW
finfo->fb.var.left_margin = s3cfb_fimd.left_margin;//行切换(HBPD)当行同步信号产生一个新脉冲后再过多长的时间VDEN有效
finfo->fb.var.right_margin = s3cfb_fimd.right_margin;//行切换当喷枪从左到右喷完数据之后再过多长的时间喷枪又回到屏幕左端(HFPD)
finfo->fb.var.vsync_len = s3cfb_fimd.vsync_len; //VSPW
finfo->fb.var.upper_margin = s3cfb_fimd.upper_margin;//帧切换,当垂直信号发出脉冲后再过多少行VDEN才有效(VBPD)
finfo->fb.var.lower_margin = s3cfb_fimd.lower_margin;//帧切换,当喷枪从上往下喷完数据之后,再过多少的时间喷枪又回到屏幕的最上方(VFPD)
finfo->fb.var.sync = s3cfb_fimd.sync;
finfo->fb.var.grayscale = s3cfb_fimd.cmap_grayscale;//不等于0的时候指的是灰度
//填充不可变参数结构
finfo->fb.fix.smem_len = finfo->fb.var.xres_virtual * finfo->fb.var.yres_virtual * s3cfb_fimd.bytes_per_pixel;//fb缓冲区的长度
finfo->fb.fix.line_length = finfo->fb.var.width * s3cfb_fimd.bytes_per_pixel;//每一行占据多少字节
#if !defined(CONFIG_FB_S3C_EXT_VIRTUAL_SCREEN)
#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
if (index < 2)
finfo->fb.fix.smem_len *= 2;//fb缓冲区的长度
#else
if (index == 0)
finfo->fb.fix.smem_len *= 5;
#endif
#endif
for (i = 0; i < 256; i++)
finfo->palette_buffer[i] = S3CFB_PALETTE_BUFF_CLEAR;//清除缓存
}
//说真的上面这一大段并不是重点,都一些填充而已,真正的实现请看下一遍文章
/*
*帧缓冲设备显示缓存区域的分配
*在probe函数被调用
*/
static int __init s3cfb_map_video_memory(s3cfb_info_t *fbi)
{
DPRINTK("map_video_memory(fbi=%p)\n", fbi);
//smem_len帧缓冲设备显示缓冲区的大小
fbi->map_size_f1 = PAGE_ALIGN(fbi->fb.fix.smem_len);//页对齐
//分配内存,返回的是一个虚拟地址
fbi->map_cpu_f1 = dma_alloc_writecombine(fbi->dev, fbi->map_size_f1, &fbi->map_dma_f1, GFP_KERNEL);
//恢复真实的大小
fbi->map_size_f1 = fbi->fb.fix.smem_len;
if (fbi->map_cpu_f1) {
/* prevent initial garbage on screen */
printk("Window[%d] - FB1: map_video_memory: clear %p:%08x\n",
fbi->win_id, fbi->map_cpu_f1, fbi->map_size_f1);
//数据搬移之前先初始化
memset(fbi->map_cpu_f1, 0xf0, fbi->map_size_f1);
fbi->screen_dma_f1 = fbi->map_dma_f1;
fbi->fb.screen_base = fbi->map_cpu_f1;
fbi->fb.fix.smem_start = fbi->screen_dma_f1;
printk(" FB1: map_video_memory: dma=%08x cpu=%p size=%08x\n",
fbi->map_dma_f1, fbi->map_cpu_f1, fbi->fb.fix.smem_len);
}
if (!fbi->map_cpu_f1)
return -ENOMEM;
#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
if (fbi->win_id < 2 && fbi->map_cpu_f1) {
fbi->map_size_f2 = (fbi->fb.fix.smem_len / 2);
fbi->map_cpu_f2 = fbi->map_cpu_f1 + fbi->map_size_f2;
fbi->map_dma_f2 = fbi->map_dma_f1 + fbi->map_size_f2;
/* prevent initial garbage on screen */
printk("Window[%d] - FB2: map_video_memory: clear %p:%08x\n",
fbi->win_id, fbi->map_cpu_f2, fbi->map_size_f2);
fbi->screen_dma_f2 = fbi->map_dma_f2;
printk(" FB2: map_video_memory: dma=%08x cpu=%p size=%08x\n",
fbi->map_dma_f2, fbi->map_cpu_f2, fbi->map_size_f2);
}
#endif
if (s3cfb_fimd.map_video_memory)
(s3cfb_fimd.map_video_memory)(fbi);
return 0;
}
/*
*帧缓冲设备显示缓存区域的释放
*/
static void s3cfb_unmap_video_memory(s3cfb_info_t *fbi)
{
dma_free_writecombine(fbi->dev, fbi->map_size_f1, fbi->map_cpu_f1, fbi->map_dma_f1);
#if defined(CONFIG_FB_S3C_EXT_DOUBLE_BUFFERING)
dma_free_writecombine(fbi->dev, fbi->map_size_f2, fbi->map_cpu_f2, fbi->map_dma_f2);
#endif
if (s3cfb_fimd.unmap_video_memory)
(s3cfb_fimd.unmap_video_memory)(fbi);
}
/*
* s3cfb_check_var():
* Get the video params out of 'var'. If a value doesn't fit, round it up,
* if it's too big, return -EINVAL.
* 调整可变参数,修正硬件所支持的值
*/
static int s3cfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
s3cfb_info_t *fbi = (s3cfb_info_t *) info;
DPRINTK("check_var(var=%p, info=%p)\n", var, info);
//bits_per_pixel:BPP 像素的格式
switch (var->bits_per_pixel) {
case 8://8BPP
var->red = s3cfb_rgb_8.red;
var->green = s3cfb_rgb_8.green;
var->blue = s3cfb_rgb_8.blue;
var->transp = s3cfb_rgb_8.transp;
s3cfb_fimd.bytes_per_pixel = 1;//每个像素占1个字节
break;
case 16://16BPP
var->red = s3cfb_rgb_16.red;
var->green = s3cfb_rgb_16.green;
var->blue = s3cfb_rgb_16.blue;
var->transp = s3cfb_rgb_16.transp;
s3cfb_fimd.bytes_per_pixel = 2;//每个像素占2个字节
break;
case 24://24BPP
var->red = s3cfb_rgb_24.red;
var->green = s3cfb_rgb_24.green;
var->blue = s3cfb_rgb_24.blue;
var->transp = s3cfb_rgb_24.transp;
s3cfb_fimd.bytes_per_pixel = 4;//每个像素占4个字节
break;
case 28://28BPP
var->red = s3cfb_rgb_28.red;
var->green = s3cfb_rgb_28.green;
var->blue = s3cfb_rgb_28.blue;
var->transp = s3cfb_rgb_28.transp;
s3cfb_fimd.bytes_per_pixel = 4;//每个像素占4个字节
break;
case 32://32BPP
var->red = s3cfb_rgb_32.red;
var->green = s3cfb_rgb_32.green;
var->blue = s3cfb_rgb_32.blue;
var->transp = s3cfb_rgb_32.transp;
s3cfb_fimd.bytes_per_pixel = 4;//每个像素占4个字节
break;
}
//窗口0比较特殊,因为它不支持混合色
if( (fbi->win_id == 0) && (var->bits_per_pixel == 28) ){
var->transp.length = 0;
}
return 0;
}