请选择 进入手机版 | 继续访问电脑版
    查看: 280|回复: 1

    Linux下的LCD驱动(一)

    [复制链接]

    2

    主题

    2

    帖子

    6

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    6
    基情
    4
    发表于 2016-9-18 15:32:34 | 显示全部楼层 |阅读模式
    版权所有,转载必须说明转自 http://my.csdn.net/weiqing1981127
    原创作者:南京邮电大学  通信与信息系统专业 研二 魏清

    一.LCD屏理论
    1.1 LCD屏基本概念
    我们知道,诸如PCII2CUSB等外围设备总线都来自于系统PC机的南桥,今天我们看到的视频控制器则来自于北桥。LCD主要由TN(扭转向列型)STN(超扭转向列型)DSTN(双层超扭曲向列阵)TFT(薄膜式晶体管型)四种显示器,许多MCU内部直接集成了LCD控制器,通过LCD控制器可以方便地控制STNTFT屏,其中TFT屏是目前嵌入式系统应用的主流。LCD常的接口类型有RGBCPUSPIMIPIMDDILVDSVGA

    显示标准:VGA(视频图形阵列)IBM早期提出的显示标准,VGA的分辨率是640x480,而更新标准的SVGA(高级视频图形阵列)XGA(扩展图形阵列)则支持800x6001024x768分辨率,嵌入式设备常用分辨率为320x240QVGA面板。
    视频标准:许多接口标准对视频控制器和显示设备的连接做了规定,视频电缆有如下标准,其一,模拟显示器;其二,数字平面显示器,如笔记本的TFT LCD,有LVDS(低电压差分信号)连接器;其三,与DVI(数字视频接口)规范标准兼容的显示器;其四,与HDTV(高清电视)规范兼容的显示器,它使用HDMI(高清多媒体接口)
    LCD常用参数:PPI是每平方英寸所拥有的像素数目,BPP是每个像素使用多少位来表示其颜色。

    1.2帧缓冲的理解
    FrameBuffer又叫帧缓冲,是Linux为操作显示设备提供的一个用户接口,用户应用程序可以通过帧缓冲透明地访问不同类型的显示设备。对于帧缓冲设备,只要在显示缓冲区与显示点对应区域写入颜色值,对应的颜色会自动在屏幕上显示,帧缓冲设备是标准的字符设备,主设备号是29,对应于/dev/fbn设备文件。
    Linux中,帧缓冲可以看成一个内存,既可以向这块内存中写数据,也可以向这个内存中读数据,用户不需要关心物理显存的位置和换页机制,这些都是由帧缓冲设备驱动完成的。帧缓冲区位于Linux内核态地址空间,所以Linux在文件操作file_operations结构中提供了mmap函数,可将缓冲区的物理地址映射到用户空间的一段虚拟地址中,之后用户就可以通过读写这段虚拟地址访问屏幕缓冲区了。帧缓冲驱动的功能就是分配一块内存作显存,然后设置LCD控制器的寄存器,LCD显示器就会不断从显存中获得数据,并显示在LCD屏上。

    二.Mini2440X35LCD移植
    先说明下像素时钟pixclock的概念
    pixclock=1/dotclock  其中dotclock是视频硬件在显示器上绘制像素的速率
    dotclock=(x向分辨率+左空边+右空边+HSYNC长度)*(y向分辨率+上空边+下空边+YSYNC长度)*整屏的刷新率
    其中x向分辨率、左空边、右空边、HSYNC长度、y向分辨率、上空边、下空边和YSYNC长度可以在X35LCD说明文档中查到。
    整屏的刷新率计算方法如下:
    假如我们通过查X35LCD说明文档,知道fclk=6.34MHZ,那么画一个像素需要的时间就是1/6.34us,如果屏的大小是240*320,那么现实一行需要的时间就是240/6.34us,每条扫描线是240,但是水平回扫和水平同步也需要时间,如果水平回扫和水平同步需要29个像素时钟,因此,画一条扫描线完整的时间就是(240+29)/6.34us。完整的屏有320根线,但是垂直回扫和垂直同步也需要时间,如果垂直回扫和垂直同步需要13个像素时钟,那么画一个完整的屏需要(240+29)*(320+13)/6.34us,所以整屏的刷新率就是6.34/((240+29)*(320+13))MHZ

    下面我们来看看怎么移植LCD驱动,我们的mini2440使用的是X35LCD屏,根据X35LCD说明文档,需要在BSPX35LCD屏的一些参数。
    mach-mini2440.c中添加X35LCD的参数
    #if defined(CONFIG_FB_S3C2410_X240320)   //定义X35LCD参数
    #define LCD_WIDTH 240   //屏宽
    #define LCD_HEIGHT 320  //屏高
    #define LCD_PIXCLOCK 170000  //时钟
    #define LCD_RIGHT_MARGIN 25    //左边界
    #define LCD_LEFT_MARGIN 0    //右边界
    #define LCD_HSYNC_LEN 4      //行同步
    #define LCD_UPPER_MARGIN 0   //上边界
    #define LCD_LOWER_MARGIN 4   //下边界
    #define LCD_VSYNC_LEN 9     //帧同步
    #define LCD_CON5 (S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVDEN | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVCLK | S3C2410_LCDCON5_HWSWP )
    #elif  //定义其他LCD屏参数
    #endif

    好了,我们现在发现要想上面定义的X35LCD的参数正在起作用,必须使得CONFIG_FB_S3C2410_X240320=y;我们需要在/driver/video/Kconfig中定义
    config FB_S3C2410_X240320
           boolean "3.5 inch 240X320 LCD(ACX502BMU)"
           depends on FB_S3C2410
           help
             3.5 inch 240X320 LCD(ACX502BMU)
    然后我们通过make menuconfig选中"3.5 inch 240X320 LCD(ACX502BMU)"这一选项。
    根据我们的X35LCD屏的说明文档,我们已经定义了一些边界参数和同步参数,因为我们的LCD驱动是基于platform总线的,所以需要在这个BSP中添加LCD的平台设备。
    struct platform_device s3c_device_lcd = {   //添加LCD平台设备
           .name              = "s3c2410-lcd",       //设备名
           .id            = -1,               
           .num_resources      = ARRAY_SIZE(s3c_lcd_resource),
           .resource         = s3c_lcd_resource,        //资源
           .dev              = {
                  .dma_mask           = &s3c_device_lcd_dmamask,
                  .coherent_dma_mask   = 0xffffffffUL
           }
    };
    资源的定义如下
    static struct resource s3c_lcd_resource[] = {
           [0] = {                       //内存空间资源
                  .start = S3C24XX_PA_LCD,
                  .end   = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
                  .flags = IORESOURCE_MEM,
           },
           [1] = {                    //中断资源
                  .start = IRQ_LCD,
                  .end   = IRQ_LCD,
                  .flags = IORESOURCE_IRQ,
           }

    };
    然后我们把s3c_device_lcd放到mini2440_devices[]结构体中,接着调用platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))LCD平台设备注册到内核。
    对于我们的LCD,需要给这个平台设备添加平台设备数据,通过调用
    s3c24xx_fb_set_platdata(&mini2440_fb_info);
    static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
           .displays = &mini2440_lcd_cfg,   //定义s3c2410fb_display数据
           .num_displays       = 1,
           .default_display = 0,
           .gpccon =       0xaa955699,  //GPC端口设置
           .gpccon_mask =  0xffc003cc,
           .gpcup =        0x0000ffff,
           .gpcup_mask =   0xffffffff,
           .gpdcon =       0xaa95aaa1,     //GPD端口设置
           .gpdcon_mask =  0xffc0fff0,
           .gpdup =        0x0000faff,
           .gpdup_mask =   0xffffffff,
           .lpcsel            = 0xf82,
    };
    继续看
    static struct s3c2410fb_display mini2440_lcd_cfg __initdata = {
    #if !defined (LCD_CON5)
           .lcdcon5 = S3C2410_LCDCON5_FRM565 |
                           S3C2410_LCDCON5_INVVLINE |
                           S3C2410_LCDCON5_INVVFRAME |
                           S3C2410_LCDCON5_PWREN |
                           S3C2410_LCDCON5_HWSWP,
    #else
           .lcdcon5 = LCD_CON5,
    #endif
           .type              = S3C2410_LCDCON1_TFT,   //屏的类型
           .width            = LCD_WIDTH,        //屏宽
           .height           = LCD_HEIGHT,      //屏高
           .pixclock       = LCD_PIXCLOCK,   //时钟
           .xres              = LCD_WIDTH,        //水平分辨率
           .yres              = LCD_HEIGHT,       //垂直分辨率
           .bpp              = 16,                 //每个像素的比特数
           .left_margin    = LCD_LEFT_MARGIN + 1,       //左边界
           .right_margin  = LCD_RIGHT_MARGIN + 1,  //右边界
           .hsync_len     = LCD_HSYNC_LEN + 1,      //行同步
           .upper_margin      = LCD_UPPER_MARGIN + 1,   //上边界
           .lower_margin       = LCD_LOWER_MARGIN + 1,  //下边界
           .vsync_len     = LCD_VSYNC_LEN + 1,          //帧同步
    };
    好了,这样我们就完成了LCD驱动的移植工作,接着我们通过make menuconfig选择相应的文件层、设备层和X35LCD屏这个三个选项,最后编译生成内核。

    三.LCD文件层和驱动层设计思路
    LCD驱动可以分为文件层和设备层,文件层又叫FrameBuffer设备驱动,对应的文件是fbmem.c,主要实现为用户提供file_operations接口,同时为设备层提供一些函数接口,这个帧缓冲设备驱动内核已经帮我们编写好,我们不需要编写。在设备层我们专门Mini2440LCD编写的驱动在s3c2410fb.c中,该驱动叫LCD驱动,主要是填充一个fbinfo结构,然后用register_framebuffer注册到内核,对于fbinfo结构,最主要的是填充它的fs_ops成员。对于驱动工程师,第一件事就是学会根据LCD说明文档,移植LCD。第二件事就是会写设备层LCD驱动。
    3.1 LCD驱动中几个重要的数据结构
    在分析内核LCD驱动代码之前,我们先要熟悉几个结构体。
    struct fb_info {
           int node;
           int flags;
           struct mutex lock;
           struct mutex mm_lock;        
           struct fb_var_screeninfo var;           //当前缓冲区的可变参数
           struct fb_fix_screeninfo fix;       //当前缓冲区的固定参数
           struct fb_monspecs monspecs;
           struct work_struct queue;
           struct fb_pixmap pixmap;   
           struct fb_pixmap sprite;      
           struct fb_cmap cmap;                    //当前的调试板
           struct list_head modelist;     
           struct fb_videomode *mode;     
    #ifdef CONFIG_FB_BACKLIGHT       //背光
           struct backlight_device *bl_dev;
           struct mutex bl_curve_mutex;        //背光灯层次
           u8 bl_curve[FB_BACKLIGHT_LEVELS];  //调整背光灯
    #endif
    #ifdef CONFIG_FB_DEFERRED_IO
           struct delayed_work deferred_work;
           struct fb_deferred_io *fbdefio;
    #endif
           struct fb_ops *fbops;   //帧缓冲操作函数集合
           struct device *device;        
           struct device *dev;            
           int class_flag;                  
    #ifdef CONFIG_FB_TILEBLITTING
           struct fb_tile_ops *tileops;  
    #endif
           char __iomem *screen_base;     //虚拟基地址
           unsigned long screen_size;    //虚拟内存大小
           void *pseudo_palette;        
    #define FBINFO_STATE_RUNNING    0
    #define FBINFO_STATE_SUSPENDED      1
           u32 state;            
           void *fbcon_par;              
           void *par;            //私有数据
           resource_size_t aperture_base;
           resource_size_t aperture_size;
    };
    为了清晰起见,对于fb_info结构体,我只注释了重点几个成员,每个帧设备都有一个fb_info,该结构体包含了驱动实现的底层函数和记录设备状态的数据。fb_info结构体主要包含fb_var_screeninfofb_fix_screeninfofb_cmapfb_ops
    struct fb_var_screeninfo {
           __u32 xres;                 //水平分辨率
           __u32 yres;        //垂直分辨率
           __u32 xres_virtual;            
           __u32 yres_virtual;
           __u32 xoffset;                  
           __u32 yoffset;            
           __u32 bits_per_pixel;         //每个像素所占的比特数
           __u32 grayscale;         
           struct fb_bitfield red;         
           struct fb_bitfield green;
           struct fb_bitfield blue;
           struct fb_bitfield transp;
           __u32 nonstd;            
           __u32 activate;                  
           __u32 height;               //屏高
           __u32 width;               //屏宽
           __u32 accel_flags;            
           __u32 pixclock;                  //像素时钟
           __u32 left_margin;              //左边界
           __u32 right_margin;            //右边界
           __u32 upper_margin;          //上边界
           __u32 lower_margin;   //下边界
           __u32 hsync_len;         //水平同步长度
           __u32 vsync_len;         //垂直同步长度
           __u32 sync;                 
           __u32 vmode;            
           __u32 rotate;               
           __u32 reserved[5];            
    };
    上面的fb_var_screeninfo结构体存放了用户可以修改的显示控制器参数,如分辨率,BPP等参数。
    struct fb_fix_screeninfo {
           char id[16];                 
           unsigned long smem_start;   //fb缓冲区开始的位置
           __u32 smem_len;               //fb缓冲区长度
           __u32 type;         
           __u32 type_aux;         
           __u32 visual;                 //屏幕色彩模式
           __u16 xpanstep;               
           __u16 ypanstep;         
           __u16 ywrapstep;        
           __u32 line_length;        
           unsigned long mmio_start;     //内存映射开始位置
           __u32 mmio_len;              //内存映射长度
           __u32 accel;               
           __u16 reserved[3];            
    };
    上面这个fb_fix_screeninfo主要记录了用户不能修改的固定显示控制器参数,如缓冲区物理地址、缓冲区长度、显示色彩模式、内核映射的开始位置等,这些结构体程序都需要驱动程序初始化时设置。
    struct fb_cmap {
           __u32 start;          //颜色板的第一个元素入口位置
           __u32 len;            //元素长度
           __u16 *red;         //
           __u16 *green;   //绿
           __u16 *blue;    //
           __u16 *transp;     //透明分量值     
    };
    对于上面的fb_cmap,它主要记录了一个颜色板信息,用户空间可以使用ioctl函数的FBIOGETCMAPFBIOPUTCMAP读取和设置颜色表的值。
    struct fb_ops {
           struct module *owner;
           int (*fb_open)(struct fb_info *info, int user);
           int (*fb_release)(struct fb_info *info, int user);
           ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
                            size_t count, loff_t *ppos);
           ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
                             size_t count, loff_t *ppos);
           int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
           int (*fb_set_par)(struct fb_info *info);
           int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
                             unsigned blue, unsigned transp, struct fb_info *info);
           int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);
           int (*fb_blank)(int blank, struct fb_info *info);
           int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
           void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
           void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
           void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
           int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
           void (*fb_rotate)(struct fb_info *info, int angle);
           int (*fb_sync)(struct fb_info *info);
           int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
                         unsigned long arg);
           int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
                         unsigned long arg);
           int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
           void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
                             struct fb_var_screeninfo *var);
           void (*fb_destroy)(struct fb_info *info);
    };
    其中fb_ops就是用来实现对帧缓冲设备的操作。

    3.2  LCD驱动层
    好了,我们先看看驱动层代码s3c2410fb.c
    static struct platform_driver s3c2410fb_driver = {
           .probe           = s3c2410fb_probe,  //探测
           .remove         = s3c2410fb_remove, //移除
           .suspend = s3c2410fb_suspend,   //挂起
           .resume         = s3c2410fb_resume,  //恢复
           .driver           = {
                  .name     = "s3c2410-lcd",   //驱动名
                  .owner    = THIS_MODULE,
           },
    };
    我们看看探测函数s3c2410fb_probe
    static int __init s3c2410fb_probe(struct platform_device *pdev)
    {
           return s3c24xxfb_probe(pdev, DRV_S3C2410);
    }
    继续看
    static int __init s3c24xxfb_probe(struct platform_device *pdev,
                                  enum s3c_drv_type drv_type)
    {
           struct s3c2410fb_info *info;  //该驱动的全局变量结构体
           struct s3c2410fb_display *display; //LCD屏的配置信息
           struct fb_info *fbinfo;    //帧缓冲驱动中对应的fb_info结构体
           struct s3c2410fb_mach_info *mach_info;  //内核平台设备数据
           struct resource *res;             //LCD资源
           int ret;
           int irq;
           int i;
           int size;
           u32 lcdcon1;
           mach_info = pdev->dev.platform_data;        //获得平台设备数据
           if (mach_info == NULL) {
                  dev_err(&pdev->dev,
                         "no platform data for lcd, cannot attach\n");
                  return -EINVAL;
           }
           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;
           }
           //获得LCD配置信息结构体
           display = mach_info->displays + mach_info->default_display;
           irq = platform_get_irq(pdev, 0);       //获得中断号
           if (irq < 0) {
                  dev_err(&pdev->dev, "no irq for device\n");
                  return -ENOENT;
           }
       //给帧缓冲fb_info分配空间,并将struct s3c2410fb_info作为其私有数据
           fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
           if (!fbinfo)
                  return -ENOMEM;
           platform_set_drvdata(pdev, fbinfo);   //fb_info作为平台设备的私有数据
           info = fbinfo->par; //获得fb_info的私有数据
           info->dev = &pdev->dev;  
           info->drv_type = drv_type;
           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) &#43; 1;
           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;
           }
           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 &#43; ((drv_type == DRV_S3C2412) ? S3C2412_LCDINTBASE : S3C2410_LCDINTBASE);  //基地址
           dprintk("devinit\n");
           strcpy(fbinfo->fix.id, driver_name);  //驱动名
           lcdcon1 = readl(info->io &#43; S3C2410_LCDCON1);
           writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io &#43; S3C2410_LCDCON1);  //禁止输出使能
           fbinfo->fix.type         = FB_TYPE_PACKED_PIXELS;
           fbinfo->fix.type_aux         = 0;  //LCD屏固定参数设置
           fbinfo->fix.xpanstep         = 0;
           fbinfo->fix.ypanstep         = 0;
           fbinfo->fix.ywrapstep       = 0;
           fbinfo->fix.accel        = FB_ACCEL_NONE;
           fbinfo->var.nonstd           = 0;     //LCD屏可变参数设置
           fbinfo->var.activate          = FB_ACTIVATE_NOW;
           fbinfo->var.accel_flags     = 0;
           fbinfo->var.vmode           = FB_VMODE_NONINTERLACED;
           fbinfo->fbops                  = &s3c2410fb_ops;  //操作函数集合
           fbinfo->flags             = FBINFO_FLAG_DEFAULT;
           fbinfo->pseudo_palette      = &info->pseudo_pal;
           for (i = 0; i < 256; i&#43;&#43;)
                  info->palette_buffer = PALETTE_BUFF_CLEAR;//初始化调试板为空
           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;
           }
           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;
           }
           clk_enable(info->clk);   //使能时钟
           dprintk("got and enabled clock\n");
           msleep(1);
           info->clk_rate = clk_get_rate(info->clk) //设置时钟;
           for (i = 0; i < mach_info->num_displays; i&#43;&#43;) { //获取最大需要的显存大小
                  unsigned long smem_len = mach_info->displays.xres;
                  smem_len *= mach_info->displays.yres;
                  smem_len *= mach_info->displays.bpp;
                  smem_len >>= 3;
                  if (fbinfo->fix.smem_len < smem_len)
                         fbinfo->fix.smem_len = smem_len;
           }
           //申请fb_info的显示缓冲区空间,并将其地址写入fbinfo
           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");
           fbinfo->var.xres = display->xres;  //水平分辨率
           fbinfo->var.yres = display->yres;  //垂直分辨率
           fbinfo->var.bits_per_pixel = display->bpp;  //每个像素的比特数
    <p><span style="font-family:Times New Roman">       s3c2410fb_init_registers(fbinfo);&nb
    回复

    使用道具 举报

    0

    主题

    1

    帖子

    1

    积分

    新手上路

    Rank: 1

    积分
    1
    基情
    0
    发表于 2017-6-26 15:13:13 | 显示全部楼层
    支持~~顶顶~~~












    专业代发帖子,网络推广,发帖宣传,产品推广,外链代发。
    __________论坛发帖价格____________
    联系QQ:188662616  微信号:188662616
    套餐一 80元500帖
    套餐二 100元1000帖
    套餐三 200元3000帖
    套餐四 300元6000帖
    套餐五 500元12000帖
    当天发完,提供详细报表,保证数量。
    包月:600元每天500帖共15000帖
    包月:800元每天1000帖共30000帖
    包月:1000元每天2000帖共60000帖
    包月:1500元每天3000帖共90000帖
    包月:2000元每天5000帖共150000帖
    包月:3000元每天10000帖共300000帖
    包月:5000元每天30000帖共900000帖
    联系QQ:188662616  微信号:188662616
    每天发完,提供详细报表。
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|小黑屋|网站地图|DZ商业模板|VR福利资源|嵌入式Linux论坛 ( 粤ICP备15085165号-2

    GMT+8, 2017-8-23 18:05 , Processed in 0.280801 second(s), 12 queries , File On.

    Powered by 深嵌论坛 X3.3

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表