上一节中,分析了s3c2410fb,c的入口出口函数,以及一些重要结构体的分析,初步知道了这是一个平台驱动的架构。
上一节文章链接:http://blog.csdn.net/lwj103862095/article/details/18188259
上一节讲到probe函数就没继续往下深究了,这一节里,我们来详细分析s3c24xxfb_probe函数,整体分析如下:
static int __init s3c24xxfb_probe(struct platform_device *pdev, enum s3c_drv_type drv_type) { struct s3c2410fb_info *info; struct s3c2410fb_display *display; struct fb_info *fbinfo; struct s3c2410fb_mach_info *mach_info; /* 包含s3c2410fb_display */ struct resource *res; int ret; int irq; int i; int size; u32 lcdcon1; /* s3c24xx_fb_set_platdata()里会设置platform_data * tq2440_machine_init()函数调用s3c24xx_fb_set_platdata(&tq2440_fb_info); * 所以这里传入来的platform_data就是tq2440_fb_info结构体实例 */ mach_info = pdev->dev.platform_data; /* 执行完上面的语句后mach_info指向tq2440_fb_info结构体,而不为NULL */ if (mach_info == NULL) { dev_err(&pdev->dev, "no platform data for lcd, cannot attach\n"); return -EINVAL; /* 表示无效的参数 */ } /* tq2440_fb_info设置了default_display = 0,num_displays = 1,故这句不会执行 */ if (mach_info->default_display >= mach_info->num_displays) { dev_err(&pdev->dev, "default is %d but only %d displays\n", mach_info->default_display, mach_info->num_displays); return -EINVAL; } /* display指向tq2440_lcd_cfg,关于LCD屏相关参数的设置 */ display = mach_info->displays + mach_info->default_display; /* 通过平台设备platform_device获得IRQ * platform_get_irq其实是调用platform_get_resource(dev, IORESOURCE_IRQ, num) */ irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "no irq for device\n"); return -ENOENT; } /* 分配一个fb_info结构体,第一参数不为0表示,额外多申请的空间 * 用来存放额外的数据,这里用来存放s3c2410fb_info额外的数据 * 比如:clk,resource,io,irq_base,drv_type等额外信息 */ fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); if (!fbinfo) return -ENOMEM; /* 返回NULL表示失败 */ /* 相当于pdev->dev->driver_data = fbinfo */ platform_set_drvdata(pdev, fbinfo); /* 在framebuffer_alloc函数里info->par指向了额外多申请内存空间的首地址 */ info = fbinfo->par; /* 将私有数据赋给info指针变量 */ info->dev = &pdev->dev; /* 指定struct s3c2410fb_info中dev为平台设备中的dev */ info->drv_type = drv_type; /* 驱动类型, DRV_S3C2410还是DRV_S3C2412 */ /* 通过平台设备platform_device获得资源(IO) */ 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; /* 资源的大小 */ /* 申请以res->start地址开始大小为size的I/O内存 */ 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; } /* 映射I/O地址,其实就是将S3C2440的LCD首寄存器(LCDCON1)的物理地址映射为虚拟地址 */ info->io = ioremap(res->start, size); if (info->io == NULL) { dev_err(&pdev->dev, "ioremap() of registers failed\n"); ret = -ENXIO; goto release_mem; } /* 这里相当于info->irq_base = info->io + 0x54,刚好是LCDINTPND寄存器的地址 */ info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE); dprintk("devinit\n"); /* 驱动名,fbinfo->fix.id = s3c2410fb */ strcpy(fbinfo->fix.id, driver_name); /* Stop the video */ lcdcon1 = readl(info->io + S3C2410_LCDCON1); /* 禁止Video output */ writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); /* 设置fb_info结构体通用的固定参数fb_fix_screeninfo结构体 */ fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; fbinfo->fix.type_aux = 0; fbinfo->fix.xpanstep = 0; fbinfo->fix.ypanstep = 0; fbinfo->fix.ywrapstep = 0; fbinfo->fix.accel = FB_ACCEL_NONE; /* 无硬件加速 */ /* 设置fb_info结构体通用的可变参数fb_var_screeninfo结构体 */ fbinfo->var.nonstd = 0; fbinfo->var.activate = FB_ACTIVATE_NOW; fbinfo->var.accel_flags = 0; fbinfo->var.vmode = FB_VMODE_NONINTERLACED; /* 设置fb_ops结构体 */ fbinfo->fbops = &s3c2410fb_ops; fbinfo->flags = FBINFO_FLAG_DEFAULT; /* 设置假调色板 */ fbinfo->pseudo_palette = &info->pseudo_pal; /* palette_buffer[i] = 0x80000000,清空调色板 */ for (i = 0; i < 256; i++) info->palette_buffer[i] = PALETTE_BUFF_CLEAR; /* 申请中断,s3c2410fb_irq是中断处理函数 */ ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); if (ret) { dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); ret = -EBUSY; goto release_regs; } /* 获取lcd时钟 */ info->clk = clk_get(NULL, "lcd"); if (!info->clk || IS_ERR(info->clk)) { printk(KERN_ERR "failed to get lcd clock source\n"); ret = -ENOENT; goto release_irq; } /* 使能lcd时钟 */ clk_enable(info->clk); dprintk("got and enabled clock\n"); msleep(1); /* find maximum required memory size for display */ /* 计算出lcd的显存大小,显存大小为width * height * bpp所以还要左移3位, * 即刚好一帧大小空间,前面计算出来的是多少bit,计算出显存为多少字节。 * 显示配置有可能有多个,所以呢,这个for循环计算出的是最大显存大小。 */ for (i = 0; i < mach_info->num_displays; i++) { /* 这里mach_info->num_displays = 1 */ unsigned long smem_len = mach_info->displays[i].xres; /* x方向分辨率 */ smem_len *= mach_info->displays[i].yres; /* y方向分辨率 */ smem_len *= mach_info->displays[i].bpp; /* bpp */ smem_len >>= 3; /* smem_len除以8 */ if (fbinfo->fix.smem_len < smem_len) fbinfo->fix.smem_len = smem_len; } /* Initialize video memory */ ret = s3c2410fb_map_video_memory(fbinfo); /* 分配显存 */ if (ret) { printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; goto release_clock; } dprintk("got video memory\n"); /* display指向tq2440_lcd_cfg */ fbinfo->var.xres = display->xres; /* 设置x方向的分辨率 */ fbinfo->var.yres = display->yres; /* 设置y方向的分辨率 */ fbinfo->var.bits_per_pixel = display->bpp; /* 设置bpp位数 */ /* 初始化LCD相关的寄存器 */ s3c2410fb_init_registers(fbinfo); /* 检查可变参数 */ s3c2410fb_check_var(&fbinfo->var, fbinfo); /* 注册fb_info结构体 */ ret = register_framebuffer(fbinfo); if (ret < 0) { printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); goto free_video_memory; } /* create device files */ ret = device_create_file(&pdev->dev, &dev_attr_debug); if (ret) { printk(KERN_ERR "failed to add debug attribute\n"); } /* TQ2440开发板内核启动时打印的信息,fb0: s3c2410fb frame buffer device */ printk(KERN_INFO "fb%d: %s frame buffer device\n", fbinfo->node, fbinfo->fix.id); return 0; free_video_memory: s3c2410fb_unmap_video_memory(fbinfo); release_clock: clk_disable(info->clk); /* 禁止lcd时钟 */ clk_put(info->clk); /* 删除lcd时钟 */ release_irq: free_irq(irq, info); /* 释放IRQ */ release_regs: iounmap(info->io); /* 解除映射 */ release_mem: release_resource(info->mem); /* 释放资源 */ kfree(info->mem); /* 释放刚申请的内存 */ dealloc_fb: platform_set_drvdata(pdev, NULL); /* 相当于pdev->dev->driver_data = NULL */ framebuffer_release(fbinfo); /* 释放fb_info结构体 */ return ret; }拆分详解:
一、获得平台数据
mach_info = pdev->dev.platform_data; /* 执行完上面的语句后mach_info指向tq2440_fb_info结构体,而不为NULL */ if (mach_info == NULL) { dev_err(&pdev->dev, "no platform data for lcd, cannot attach\n"); return -EINVAL; /* 表示无效的参数 */ }s3c24xx_fb_set_platdata()里会设置platform_data,tq2440_machine_init()函数调用s3c24xx_fb_set_platdata(&tq2440_fb_info);
static void __init tq2440_machine_init(void) { /* 初始化tq2440_fb_info实体结构体 */ s3c24xx_fb_set_platdata(&tq2440_fb_info); s3c_i2c0_set_platdata(NULL); /* 添加tq2440_devices到内核,它是platform_device类的设备 */ platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices)); EmbedSky_machine_init(); s3c2410_gpio_setpin(S3C2410_GPG12, 0); s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPIO_OUTPUT); s3c24xx_udc_set_platdata(&EmbedSky_udc_cfg); }s3c24xx_fb_set_platdata函数将tq2440_fb_info拷贝到s3c2410fb_mach_info,并将s3c_device_lcd.dev.platform_data指向 tq2440_fb_info
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd) { struct s3c2410fb_mach_info *npd; /* 申请分配s3c2410fb_mach_info大小的内存 */ npd = kmalloc(sizeof(*npd), GFP_KERNEL); if (npd) { /* 拷贝s3c2410fb_mach_info型实体给npd */ memcpy(npd, pd, sizeof(*npd)); /* 最后将s3c2410fb_mach_info型实体赋给platform_data */ s3c_device_lcd.dev.platform_data = npd; } else { printk(KERN_ERR "no memory for LCD platform data\n"); } }二、设置s3c2410fb_display指向tq2440_lcd_cfg
/* tq2440_fb_info设置了default_display = 0,num_displays = 1,故这句不会执行 */ if (mach_info->default_display >= mach_info->num_displays) { dev_err(&pdev->dev, "default is %d but only %d displays\n", mach_info->default_display, mach_info->num_displays); return -EINVAL; } /* display指向tq2440_lcd_cfg,关于LCD屏相关参数的设置 */ display = mach_info->displays + mach_info->default_display;三、获得IRQ资源
irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "no irq for device\n"); return -ENOENT; }四、分配fb_info内存
fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); if (!fbinfo) return -ENOMEM; /* 返回NULL表示失败 */framebuffer_alloc第一参数不为0表示,额外多申请的空间,用来存放额外的数据,这里用来存放s3c2410fb_info额外的数据,比如:clk,resource,io,irq_base,drv_type等额外信息。
/* 相当于pdev->dev->driver_data = fbinfo */ platform_set_drvdata(pdev, fbinfo); /* 在framebuffer_alloc函数里info->par指向了额外多申请内存空间的首地址 */ info = fbinfo->par; /* 将私有数据赋给info指针变量 */ info->dev = &pdev->dev; /* 指定struct s3c2410fb_info中dev为平台设备中的dev */ info->drv_type = drv_type; /* 驱动类型, DRV_S3C2410还是DRV_S3C2412 */六、获取IO资源,映射IO
/* 通过平台设备platform_device获得资源(IO) */ 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; /* 资源的大小 */ /* 申请以res->start地址开始大小为size的I/O内存 */ 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; } /* 映射I/O地址,其实就是将S3C2440的LCD首寄存器(LCDCON1)的物理地址映射为虚拟地址 */ info->io = ioremap(res->start, size); if (info->io == NULL) { dev_err(&pdev->dev, "ioremap() of registers failed\n"); ret = -ENXIO; goto release_mem; } /* 这里相当于info->irq_base = info->io + 0x54,刚好是LCDINTPND寄存器的地址 */ info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);七、读写LCDCON1,禁止视频数据输出
/* Stop the video */ lcdcon1 = readl(info->io + S3C2410_LCDCON1); /* 禁止Video output */ writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);八、设置fb_info结构体的固定参数(fb_fix_screeninfo),可变参数(fb_var_screeninfo),fbops结构体,flags,假调色板(pseudo_palette)等
/* 设置fb_info结构体通用的固定参数fb_fix_screeninfo结构体 */ fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; fbinfo->fix.type_aux = 0; fbinfo->fix.xpanstep = 0; fbinfo->fix.ypanstep = 0; fbinfo->fix.ywrapstep = 0; fbinfo->fix.accel = FB_ACCEL_NONE; /* 无硬件加速 */ /* 设置fb_info结构体通用的可变参数fb_var_screeninfo结构体 */ fbinfo->var.nonstd = 0; fbinfo->var.activate = FB_ACTIVATE_NOW; fbinfo->var.accel_flags = 0; fbinfo->var.vmode = FB_VMODE_NONINTERLACED; /* 设置fb_ops结构体 */ fbinfo->fbops = &s3c2410fb_ops; fbinfo->flags = FBINFO_FLAG_DEFAULT; /* 设置假调色板 */ fbinfo->pseudo_palette = &info->pseudo_pal; /* palette_buffer[i] = 0x80000000,清空调色板 */ for (i = 0; i < 256; i++) info->palette_buffer[i] = PALETTE_BUFF_CLEAR;九、申请中断、获取LCD时钟,使能LCD时钟
/* 申请中断,s3c2410fb_irq是中断处理函数 */ ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info); if (ret) { dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); ret = -EBUSY; goto release_regs; } /* 获取lcd时钟 */ info->clk = clk_get(NULL, "lcd"); if (!info->clk || IS_ERR(info->clk)) { printk(KERN_ERR "failed to get lcd clock source\n"); ret = -ENOENT; goto release_irq; } /* 使能lcd时钟 */ clk_enable(info->clk); dprintk("got and enabled clock\n");十、计算显存大小、分配显存内存
/* 计算出lcd的显存大小,显存大小为width * height * bpp所以还要左移3位, * 即刚好一帧大小空间,前面计算出来的是多少bit,计算出显存为多少字节。 * 显示配置有可能有多个,所以呢,这个for循环计算出的是最大显存大小。 */ for (i = 0; i < mach_info->num_displays; i++) { /* 这里mach_info->num_displays = 1 */ unsigned long smem_len = mach_info->displays[i].xres; /* x方向分辨率 */ smem_len *= mach_info->displays[i].yres; /* y方向分辨率 */ smem_len *= mach_info->displays[i].bpp; /* bpp */ smem_len >>= 3; /* smem_len除以8 */ if (fbinfo->fix.smem_len < smem_len) fbinfo->fix.smem_len = smem_len; } /* Initialize video memory */ ret = s3c2410fb_map_video_memory(fbinfo); /* 分配显存 */ if (ret) { printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; goto release_clock; }十一、设置fb_info结构体中的可变参数的x、y分辨率以及BPP为tq2440_lcd_cfg中的x、y分辨率和BPP
/* display指向tq2440_lcd_cfg */ fbinfo->var.xres = display->xres; /* 设置x方向的分辨率 */ fbinfo->var.yres = display->yres; /* 设置y方向的分辨率 */ fbinfo->var.bits_per_pixel = display->bpp; /* 设置bpp位数 */十二、LCD相关寄存器的设置和fb_info的可变参数的检测
/* 初始化LCD相关的寄存器 */ s3c2410fb_init_registers(fbinfo); /* 检查可变参数 */ s3c2410fb_check_var(&fbinfo->var, fbinfo);十三、注册fb_info结构体
/* 注册fb_info结构体 */ ret = register_framebuffer(fbinfo); if (ret < 0) { printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); goto free_video_memory; }