linux lcd设备驱动剖析二

上一节中,分析了s3c2410fb,c的入口出口函数,以及一些重要结构体的分析,初步知道了这是一个平台驱动的架构。

上一节文章链接:http://blog.csdn.net/lwj103862095/article/details/18188259

上一节讲到probe函数就没继续往下深究了,这一节里,我们来详细分析s3c24xxfb_probe函数,整体分析如下:

[cpp] view plain?
  1. static int __init s3c24xxfb_probe(struct platform_device *pdev,  
  2.                   enum s3c_drv_type drv_type)  
  3. {  
  4.     struct s3c2410fb_info *info;  
  5.     struct s3c2410fb_display *display;  
  6.     struct fb_info *fbinfo;  
  7.     struct s3c2410fb_mach_info *mach_info;  /* 包含s3c2410fb_display */  
  8.     struct resource *res;  
  9.     int ret;  
  10.     int irq;  
  11.     int i;  
  12.     int size;  
  13.     u32 lcdcon1;  
  14.   
  15.     /*  s3c24xx_fb_set_platdata()里会设置platform_data 
  16.      *  tq2440_machine_init()函数调用s3c24xx_fb_set_platdata(&tq2440_fb_info); 
  17.      *  所以这里传入来的platform_data就是tq2440_fb_info结构体实例 
  18.      */  
  19.     mach_info = pdev->dev.platform_data;  
  20.   
  21.     /* 执行完上面的语句后mach_info指向tq2440_fb_info结构体,而不为NULL  */  
  22.     if (mach_info == NULL) {  
  23.         dev_err(&pdev->dev,  
  24.             "no platform data for lcd, cannot attach ");  
  25.         return -EINVAL;     /* 表示无效的参数 */  
  26.     }  
  27.   
  28.     /* tq2440_fb_info设置了default_display = 0,num_displays = 1,故这句不会执行 */  
  29.     if (mach_info->default_display >= mach_info->num_displays) {  
  30.         dev_err(&pdev->dev, "default is %d but only %d displays ",  
  31.             mach_info->default_display, mach_info->num_displays);  
  32.         return -EINVAL;  
  33.     }  
  34.   
  35.     /* display指向tq2440_lcd_cfg,关于LCD屏相关参数的设置 */  
  36.     display = mach_info->displays + mach_info->default_display;  
  37.   
  38.     /* 通过平台设备platform_device获得IRQ 
  39.      * platform_get_irq其实是调用platform_get_resource(dev, IORESOURCE_IRQ, num) 
  40.      */  
  41.     irq = platform_get_irq(pdev, 0);  
  42.     if (irq < 0) {  
  43.         dev_err(&pdev->dev, "no irq for device ");  
  44.         return -ENOENT;  
  45.     }  
  46.   
  47.     /*  分配一个fb_info结构体,第一参数不为0表示,额外多申请的空间 
  48.      *  用来存放额外的数据,这里用来存放s3c2410fb_info额外的数据 
  49.      *  比如:clk,resource,io,irq_base,drv_type等额外信息 
  50.      */  
  51.     fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);  
  52.     if (!fbinfo)  
  53.         return -ENOMEM;     /* 返回NULL表示失败 */  
  54.   
  55.     /* 相当于pdev->dev->driver_data = fbinfo */  
  56.     platform_set_drvdata(pdev, fbinfo);  
  57.   
  58.     /* 在framebuffer_alloc函数里info->par指向了额外多申请内存空间的首地址 */  
  59.     info = fbinfo->par;          /* 将私有数据赋给info指针变量 */  
  60.     info->dev = &pdev->dev;       /* 指定struct s3c2410fb_info中dev为平台设备中的dev */  
  61.     info->drv_type = drv_type;   /* 驱动类型, DRV_S3C2410还是DRV_S3C2412 */  
  62.   
  63.     /*  通过平台设备platform_device获得资源(IO) */  
  64.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  65.     if (res == NULL) {  
  66.         dev_err(&pdev->dev, "failed to get memory registers ");  
  67.         ret = -ENXIO;  
  68.         goto dealloc_fb;  
  69.     }  
  70.   
  71.     size = (res->end - res->start) + 1;       /* 资源的大小 */  
  72.   
  73.     /* 申请以res->start地址开始大小为size的I/O内存 */  
  74.     info->mem = request_mem_region(res->start, size, pdev->name);  
  75.     if (info->mem == NULL) {  
  76.         dev_err(&pdev->dev, "failed to get memory region ");  
  77.         ret = -ENOENT;  
  78.         goto dealloc_fb;  
  79.     }  
  80.   
  81.     /* 映射I/O地址,其实就是将S3C2440的LCD首寄存器(LCDCON1)的物理地址映射为虚拟地址 */  
  82.     info->io = ioremap(res->start, size);  
  83.     if (info->io == NULL) {  
  84.         dev_err(&pdev->dev, "ioremap() of registers failed ");  
  85.         ret = -ENXIO;  
  86.         goto release_mem;  
  87.     }  
  88.   
  89.     /* 这里相当于info->irq_base = info->io + 0x54,刚好是LCDINTPND寄存器的地址 */  
  90.     info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);  
  91.   
  92.     dprintk("devinit ");  
  93.   
  94.     /* 驱动名,fbinfo->fix.id = s3c2410fb */  
  95.     strcpy(fbinfo->fix.id, driver_name);   
  96.   
  97.     /* Stop the video */  
  98.     lcdcon1 = readl(info->io + S3C2410_LCDCON1);  
  99.     /* 禁止Video output */  
  100.     writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);  
  101.   
  102.     /* 设置fb_info结构体通用的固定参数fb_fix_screeninfo结构体 */  
  103.     fbinfo->fix.type         = FB_TYPE_PACKED_PIXELS;  
  104.     fbinfo->fix.type_aux     = 0;  
  105.     fbinfo->fix.xpanstep     = 0;  
  106.     fbinfo->fix.ypanstep     = 0;  
  107.     fbinfo->fix.ywrapstep        = 0;  
  108.     fbinfo->fix.accel            = FB_ACCEL_NONE;    /* 无硬件加速 */  
  109.   
  110.     /* 设置fb_info结构体通用的可变参数fb_var_screeninfo结构体 */  
  111.     fbinfo->var.nonstd           = 0;  
  112.     fbinfo->var.activate     = FB_ACTIVATE_NOW;  
  113.     fbinfo->var.accel_flags     = 0;  
  114.     fbinfo->var.vmode            = FB_VMODE_NONINTERLACED;  
  115.   
  116.     /* 设置fb_ops结构体 */  
  117.     fbinfo->fbops                = &s3c2410fb_ops;  
  118.       
  119.     fbinfo->flags                = FBINFO_FLAG_DEFAULT;  
  120.   
  121.     /* 设置假调色板 */  
  122.     fbinfo->pseudo_palette      = &info->pseudo_pal;  
  123.   
  124.     /* palette_buffer[i] = 0x80000000,清空调色板 */  
  125.     for (i = 0; i < 256; i++)  
  126.         info->palette_buffer[i] = PALETTE_BUFF_CLEAR;  
  127.   
  128.     /* 申请中断,s3c2410fb_irq是中断处理函数 */  
  129.     ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);  
  130.     if (ret) {  
  131.         dev_err(&pdev->dev, "cannot get irq %d - err %d ", irq, ret);  
  132.         ret = -EBUSY;  
  133.         goto release_regs;  
  134.     }  
  135.   
  136.     /* 获取lcd时钟 */  
  137.     info->clk = clk_get(NULL, "lcd");  
  138.     if (!info->clk || IS_ERR(info->clk)) {  
  139.         printk(KERN_ERR "failed to get lcd clock source ");  
  140.         ret = -ENOENT;  
  141.         goto release_irq;  
  142.     }  
  143.   
  144.     /* 使能lcd时钟 */  
  145.     clk_enable(info->clk);         
  146.     dprintk("got and enabled clock ");  
  147.   
  148.     msleep(1);  
  149.   
  150.     /* find maximum required memory size for display */  
  151.   
  152.     /* 计算出lcd的显存大小,显存大小为width * height * bpp所以还要左移3位, 
  153.      * 即刚好一帧大小空间,前面计算出来的是多少bit,计算出显存为多少字节。 
  154.      * 显示配置有可能有多个,所以呢,这个for循环计算出的是最大显存大小。 
  155.      */  
  156.     for (i = 0; i < mach_info->num_displays; i++) {           /* 这里mach_info->num_displays = 1 */  
  157.         unsigned long smem_len = mach_info->displays[i].xres;    /* x方向分辨率 */  
  158.   
  159.         smem_len *= mach_info->displays[i].yres;             /* y方向分辨率 */  
  160.         smem_len *= mach_info->displays[i].bpp;                  /* bpp */  
  161.         smem_len >>= 3;                                           /* smem_len除以8 */  
  162.         if (fbinfo->fix.smem_len < smem_len)  
  163.             fbinfo->fix.smem_len = smem_len;  
  164.     }  
  165.   
  166.     /* Initialize video memory */  
  167.     ret = s3c2410fb_map_video_memory(fbinfo);   /* 分配显存 */  
  168.     if (ret) {  
  169.         printk(KERN_ERR "Failed to allocate video RAM: %d ", ret);  
  170.         ret = -ENOMEM;  
  171.         goto release_clock;  
  172.     }  
  173.   
  174.     dprintk("got video memory ");  
  175.   
  176.     /* display指向tq2440_lcd_cfg */  
  177.     fbinfo->var.xres = display->xres;         /* 设置x方向的分辨率 */  
  178.     fbinfo->var.yres = display->yres;         /* 设置y方向的分辨率 */  
  179.     fbinfo->var.bits_per_pixel = display->bpp;    /* 设置bpp位数 */  
  180.   
  181.     /* 初始化LCD相关的寄存器 */  
  182.     s3c2410fb_init_registers(fbinfo);  
  183.   
  184.     /* 检查可变参数 */  
  185.     s3c2410fb_check_var(&fbinfo->var, fbinfo);  
  186.   
  187.     /* 注册fb_info结构体 */  
  188.     ret = register_framebuffer(fbinfo);  
  189.     if (ret < 0) {  
  190.         printk(KERN_ERR "Failed to register framebuffer device: %d ",  
  191.             ret);  
  192.         goto free_video_memory;  
  193.     }  
  194.   
  195.     /* create device files */  
  196.     ret = device_create_file(&pdev->dev, &dev_attr_debug);  
  197.     if (ret) {  
  198.         printk(KERN_ERR "failed to add debug attribute ");  
  199.     }  
  200.   
  201.   
  202.     /* TQ2440开发板内核启动时打印的信息,fb0: s3c2410fb frame buffer device  */  
  203.     printk(KERN_INFO "fb%d: %s frame buffer device ",  
  204.         fbinfo->node, fbinfo->fix.id);  
  205.   
  206.     return 0;  
  207.   
  208. free_video_memory:  
  209.     s3c2410fb_unmap_video_memory(fbinfo);  
  210. release_clock:  
  211.     clk_disable(info->clk);              /* 禁止lcd时钟 */  
  212.     clk_put(info->clk);                  /* 删除lcd时钟 */  
  213. release_irq:  
  214.     free_irq(irq, info);                /* 释放IRQ */  
  215. release_regs:  
  216.     iounmap(info->io);                   /* 解除映射 */  
  217. release_mem:  
  218.     release_resource(info->mem);     /* 释放资源 */  
  219.     kfree(info->mem);                    /* 释放刚申请的内存 */  
  220. dealloc_fb:  
  221.     platform_set_drvdata(pdev, NULL);   /* 相当于pdev->dev->driver_data = NULL */  
  222.     framebuffer_release(fbinfo);        /* 释放fb_info结构体 */  
  223.     return ret;  
  224. }  
拆分详解:

一、获得平台数据

[cpp] view plain?
  1. mach_info = pdev->dev.platform_data;  
  2.   
  3. /* 执行完上面的语句后mach_info指向tq2440_fb_info结构体,而不为NULL  */  
  4. if (mach_info == NULL) {  
  5.     dev_err(&pdev->dev,  
  6.         "no platform data for lcd, cannot attach ");  
  7.     return -EINVAL;     /* 表示无效的参数 */  
  8. }  
s3c24xx_fb_set_platdata()里会设置platform_data,tq2440_machine_init()函数调用s3c24xx_fb_set_platdata(&tq2440_fb_info);
所以这里传入来的platform_data就是tq2440_fb_info结构体实例。

[cpp] view plain?
  1. static void __init tq2440_machine_init(void)  
  2. {  
  3.     /* 初始化tq2440_fb_info实体结构体 */  
  4.     s3c24xx_fb_set_platdata(&tq2440_fb_info);  
  5.     s3c_i2c0_set_platdata(NULL);  
  6.   
  7.     /* 添加tq2440_devices到内核,它是platform_device类的设备 */  
  8.     platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices));  
  9.     EmbedSky_machine_init();  
  10.     s3c2410_gpio_setpin(S3C2410_GPG12, 0);  
  11.     s3c2410_gpio_cfgpin(S3C2410_GPG12, S3C2410_GPIO_OUTPUT);  
  12.     s3c24xx_udc_set_platdata(&EmbedSky_udc_cfg);  
  13. }  
s3c24xx_fb_set_platdata函数将tq2440_fb_info拷贝到s3c2410fb_mach_info,并将s3c_device_lcd.dev.platform_data指向tq2440_fb_info

[cpp] view plain?
  1. void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)  
  2. {  
  3.     struct s3c2410fb_mach_info *npd;  
  4.   
  5.     /* 申请分配s3c2410fb_mach_info大小的内存 */  
  6.     npd = kmalloc(sizeof(*npd), GFP_KERNEL);  
  7.     if (npd) {  
  8.         /* 拷贝s3c2410fb_mach_info型实体给npd */  
  9.         memcpy(npd, pd, sizeof(*npd));  
  10.   
  11.         /* 最后将s3c2410fb_mach_info型实体赋给platform_data */  
  12.         s3c_device_lcd.dev.platform_data = npd;  
  13.     } else {  
  14.         printk(KERN_ERR "no memory for LCD platform data ");  
  15.     }  
  16. }  
二、设置s3c2410fb_display指向tq2440_lcd_cfg

[cpp] view plain?
  1. /* tq2440_fb_info设置了default_display = 0,num_displays = 1,故这句不会执行 */  
  2. if (mach_info->default_display >= mach_info->num_displays) {  
  3.     dev_err(&pdev->dev, "default is %d but only %d displays ",  
  4.         mach_info->default_display, mach_info->num_displays);  
  5.     return -EINVAL;  
  6. }  
  7.   
  8. /* display指向tq2440_lcd_cfg,关于LCD屏相关参数的设置 */  
  9. display = mach_info->displays + mach_info->default_display;  
三、获得IRQ资源

[cpp] view plain?
  1. irq = platform_get_irq(pdev, 0);  
  2. if (irq < 0) {  
  3.     dev_err(&pdev->dev, "no irq for device ");  
  4.     return -ENOENT;  
  5. }  
四、分配fb_info内存
[cpp] view plain?
  1. fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);  
  2.     if (!fbinfo)  
  3.         return -ENOMEM;     /* 返回NULL表示失败 */  
framebuffer_alloc第一参数不为0表示,额外多申请的空间,用来存放额外的数据,这里用来存放s3c2410fb_info额外的数据,比如:clk,resource,io,irq_base,drv_type等额外信息。
五、设置s3c2410fb_info结构体

[cpp] view plain?
  1. /* 相当于pdev->dev->driver_data = fbinfo */  
  2.     platform_set_drvdata(pdev, fbinfo);  
  3.   
  4.     /* 在framebuffer_alloc函数里info->par指向了额外多申请内存空间的首地址 */  
  5.     info = fbinfo->par;          /* 将私有数据赋给info指针变量 */  
  6.     info->dev = &pdev->dev;       /* 指定struct s3c2410fb_info中dev为平台设备中的dev */  
  7.     info->drv_type = drv_type;   /* 驱动类型, DRV_S3C2410还是DRV_S3C2412 */  
六、获取IO资源,映射IO

[cpp] view plain?
  1. /*  通过平台设备platform_device获得资源(IO) */  
  2.     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);  
  3.     if (res == NULL) {  
  4.         dev_err(&pdev->dev, "failed to get memory registers ");  
  5.         ret = -ENXIO;  
  6.         goto dealloc_fb;  
  7.     }  
  8.   
  9.     size = (res->end - res->start) + 1;       /* 资源的大小 */  
  10.   
  11.     /* 申请以res->start地址开始大小为size的I/O内存 */  
  12.     info->mem = request_mem_region(res->start, size, pdev->name);  
  13.     if (info->mem == NULL) {  
  14.         dev_err(&pdev->dev, "failed to get memory region ");  
  15.         ret = -ENOENT;  
  16.         goto dealloc_fb;  
  17.     }  
  18.   
  19.     /* 映射I/O地址,其实就是将S3C2440的LCD首寄存器(LCDCON1)的物理地址映射为虚拟地址 */  
  20.     info->io = ioremap(res->start, size);  
  21.     if (info->io == NULL) {  
  22.         dev_err(&pdev->dev, "ioremap() of registers failed ");  
  23.         ret = -ENXIO;  
  24.         goto release_mem;  
  25.     }  
  26.   
  27.     /* 这里相当于info->irq_base = info->io + 0x54,刚好是LCDINTPND寄存器的地址 */  
  28.     info->irq_base = info->io + ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);  
七、读写LCDCON1,禁止视频数据输出

[cpp] view plain?
  1. /* Stop the video */  
  2. lcdcon1 = readl(info->io + S3C2410_LCDCON1);  
  3. /* 禁止Video output */  
  4. writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);  
八、设置fb_info结构体的固定参数(fb_fix_screeninfo),可变参数(fb_var_screeninfo),fbops结构体,flags,假调色板(pseudo_palette)等

[cpp] view plain?
  1. /* 设置fb_info结构体通用的固定参数fb_fix_screeninfo结构体 */  
  2.     fbinfo->fix.type         = FB_TYPE_PACKED_PIXELS;  
  3.     fbinfo->fix.type_aux     = 0;  
  4.     fbinfo->fix.xpanstep     = 0;  
  5.     fbinfo->fix.ypanstep     = 0;  
  6.     fbinfo->fix.ywrapstep        = 0;  
  7.     fbinfo->fix.accel            = FB_ACCEL_NONE;    /* 无硬件加速 */  
  8.   
  9.     /* 设置fb_info结构体通用的可变参数fb_var_screeninfo结构体 */  
  10.     fbinfo->var.nonstd           = 0;  
  11.     fbinfo->var.activate     = FB_ACTIVATE_NOW;  
  12.     fbinfo->var.accel_flags     = 0;  
  13.     fbinfo->var.vmode            = FB_VMODE_NONINTERLACED;  
  14.   
  15.     /* 设置fb_ops结构体 */  
  16.     fbinfo->fbops                = &s3c2410fb_ops;  
  17.       
  18.     fbinfo->flags                = FBINFO_FLAG_DEFAULT;  
  19.   
  20.     /* 设置假调色板 */  
  21.     fbinfo->pseudo_palette      = &info->pseudo_pal;  
  22.   
  23.     /* palette_buffer[i] = 0x80000000,清空调色板 */  
  24.     for (i = 0; i < 256; i++)  
  25.         info->palette_buffer[i] = PALETTE_BUFF_CLEAR;  
九、申请中断、获取LCD时钟,使能LCD时钟
[cpp] view plain?
  1. /* 申请中断,s3c2410fb_irq是中断处理函数 */  
  2. ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);  
  3. if (ret) {  
  4.     dev_err(&pdev->dev, "cannot get irq %d - err %d ", irq, ret);  
  5.     ret = -EBUSY;  
  6.     goto release_regs;  
  7. }  
  8.   
  9. /* 获取lcd时钟 */  
  10. info->clk = clk_get(NULL, "lcd");  
  11. if (!info->clk || IS_ERR(info->clk)) {  
  12.     printk(KERN_ERR "failed to get lcd clock source ");  
  13.     ret = -ENOENT;  
  14.     goto release_irq;  
  15. }  
  16.   
  17. /* 使能lcd时钟 */  
  18. clk_enable(info->clk);         
  19. dprintk("got and enabled clock ");  
十、计算显存大小、分配显存内存

[cpp] view plain?
  1. /* 计算出lcd的显存大小,显存大小为width * height * bpp所以还要左移3位, 
  2.     * 即刚好一帧大小空间,前面计算出来的是多少bit,计算出显存为多少字节。 
  3.     * 显示配置有可能有多个,所以呢,这个for循环计算出的是最大显存大小。 
  4.  */  
  5. for (i = 0; i < mach_info->num_displays; i++) {           /* 这里mach_info->num_displays = 1 */  
  6.     unsigned long smem_len = mach_info->displays[i].xres;    /* x方向分辨率 */  
  7.   
  8.     smem_len *= mach_info->displays[i].yres;             /* y方向分辨率 */  
  9.     smem_len *= mach_info->displays[i].bpp;                  /* bpp */  
  10.     smem_len >>= 3;                                           /* smem_len除以8 */  
  11.     if (fbinfo->fix.smem_len < smem_len)  
  12.         fbinfo->fix.smem_len = smem_len;  
  13. }  
  14.   
  15. /* Initialize video memory */  
  16. ret = s3c2410fb_map_video_memory(fbinfo);   /* 分配显存 */  
  17. if (ret) {  
  18.     printk(KERN_ERR "Failed to allocate video RAM: %d ", ret);  
  19.     ret = -ENOMEM;  
  20.     goto release_clock;  
  21. }  
十一、设置fb_info结构体中的可变参数的x、y分辨率以及BPP为tq2440_lcd_cfg中的x、y分辨率和BPP

[cpp] view plain?
  1. /* display指向tq2440_lcd_cfg */  
  2. fbinfo->var.xres = display->xres;         /* 设置x方向的分辨率 */  
  3. fbinfo->var.yres = display->yres;         /* 设置y方向的分辨率 */  
  4. fbinfo->var.bits_per_pixel = display->bpp;    /* 设置bpp位数 */  
十二、LCD相关寄存器的设置和fb_info的可变参数的检测

[cpp] view plain?
  1. /* 初始化LCD相关的寄存器 */  
  2.     s3c2410fb_init_registers(fbinfo);  
  3.   
  4.     /* 检查可变参数 */  
  5.     s3c2410fb_check_var(&fbinfo->var, fbinfo);  
十三、注册fb_info结构体

[cpp] view plain?
  1. /* 注册fb_info结构体 */  
  2. ret = register_framebuffer(fbinfo);  
  3. if (ret < 0) {  
  4.     printk(KERN_ERR "Failed to register framebuffer device: %d ",  
  5.         ret);  
  6.     goto free_video_memory;  
  7. }  

原文地址:https://www.cnblogs.com/alan666/p/8312423.html