LCD驱动程序之代码编写

学习目的:

  • 实现LCD驱动程序编写

前面通过对linux内核中LCD的驱动框架进行了分析,弄清楚了内核中LCD的驱动框架,通过分析知道内核中已经在fbmem.c文件中注册了LCD这一类设备的字符设备驱动,向上实现了上层通用的访问接口,向下给驱动设计者预留了fb_info注册接口。现在基于我们的开发板平台(JZ2240),编写适配于自己硬件的驱动程序

从前面分析可以知道,编写LCD驱动需要驱动的编写者去完成以下内容(可参考内核中s3c2410fb.c):

1) 分配一个fb_info结构体

2) 根据使用LCD硬件特征,设置fb_info结构体的一些参数

3) 配置硬件相关操作

4) 注册fb_info结构体

下面我们就开始按照这些步骤,完成LCD驱动程序的编写

1、fb_info结构体介绍

fb_info结构体是LCD驱动程序中最核心的数据结构,应用程序通过fbmem.c文件中注册的设备访问入口,找到打开设备文件的fb_info结构体,最终访问到硬件,fb_info结构体声明如下:

struct fb_info {
    int node;
    int flags;
    struct fb_var_screeninfo var;    /* Current var */
    struct fb_fix_screeninfo fix;    /* Current fix */
    struct fb_monspecs monspecs;    /* Current Monitor specs */
    struct work_struct queue;    /* Framebuffer event queue */
    struct fb_pixmap pixmap;    /* Image hardware mapper */
    struct fb_pixmap sprite;    /* Cursor hardware mapper */
    struct fb_cmap cmap;        /* Current cmap */
    struct list_head modelist;      /* mode list */
    struct fb_videomode *mode;    /* current mode */

#ifdef CONFIG_FB_BACKLIGHT
    /* assigned backlight device */
    /* set before framebuffer registration, 
       remove after unregister */
    struct backlight_device *bl_dev;

    /* Backlight level curve */
    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;        /* This is the parent */
    struct device *dev;        /* This is this fb device */
    int class_flag;                    /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
    struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
    char __iomem *screen_base;    /* Virtual address */
    unsigned long screen_size;    /* Amount of ioremapped VRAM or 0 */ 
    void *pseudo_palette;        /* Fake palette of 16 colors */ 
#define FBINFO_STATE_RUNNING    0
#define FBINFO_STATE_SUSPENDED    1
    u32 state;            /* Hardware state i.e suspend */
    void *fbcon_par;                /* fbcon use-only private area */
    /* From here on everything is device dependent */
    void *par;    
};

可以看到fb_info的结构体比较复杂,它的肯定是为了抽象出所有LCD硬件而设计的,但我们不需要去了解每一个成员变量的功能和使用方法。在这里面有三个比较重要的结构体,分别是fb_var_screeninfo、fb_fix_screeninfo、fb_ops,这三个结构体也是在每个LCD驱动程序中都必需设置的

1)fb_var_screeninfo结构体记录了用户可以修改的显示参数,比如屏幕的分辨率、屏幕位域

2)fb_fix_screeninfo结构体记录了用户不可修改的参数,比如屏幕的显存的位置、长度

3)fb_ops结构体记录了操作底层硬件函数的地址,如果fb_ops中定义了read函数,应用程序的read函数最终调用到fb_ops中read函数指针指向的函数,通过file->fops->read中找到打开设备fb_info,调用fb_info->fbops->read

2、驱动使用函数介绍

struct fb_info *framebuffer_alloc(size_t size, struct device *dev);------------------------------>①
void framebuffer_release(struct fb_info *info);
/*================================================================================================*/
int register_framebuffer(struct fb_info *fb_info);----------------------------------------------->② int unregister_framebuffer(struct fb_info *fb_info);
/*================================================================================================*/

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);---->③
size:分配内存的大小
handler:申请到内存的物理地址

gfp:分配出来内存的参数,常用标志如下:
GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
GFP_KERNEL 内核内存的正常分配. 可能睡眠.
GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.
返回申请到内存的虚拟地址

void dma_free_writecombine(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);
cpu_addr:虚拟地址,
handle:物理地址

动态分配一个fb_info结构体,size代表额外分配的内存供驱动使用,可以用fb_info中的fbcon_par指针指向这块内存,如果不使用设置为0,dev指针,指向fb的device,可以设置为NULL

② 向内核注册和注销fb_info结构体

③ 分配和注销连续地址的内存,一般LCD控制器使用的显存地址是连续的,分配显存时需要使用这些函数

3、驱动程序编写

3.1 入口函数

入口函数里面完成了LCD驱动中的大部分工作,分配了fb_info结构体、分配了连续地址内存作为LCD控制器显存,设置了fb_info结构体的一些信息,配置硬件,注册fb_info结构体

int lcd_drv_init(void)
{
    lcd_fb_info = framebuffer_alloc(0, NULL);

    /* set fb_info variable part */
    lcd_fb_info->var.xres           = 480;   //x方向分辨率      
    lcd_fb_info->var.yres           = 272;   //y方向分别率
    lcd_fb_info->var.xres_virtual   = 480;   //x方向虚拟分辨率
    lcd_fb_info->var.yres_virtual   = 272;   //y方向虚拟分辨率
    lcd_fb_info->var.bits_per_pixel = 16;    //每像素位数

    /* color format RGB565 */
    lcd_fb_info->var.red.length     = 5;     //lcd颜色格式
    lcd_fb_info->var.red.offset      = 11;
    lcd_fb_info->var.green.length     = 6;
    lcd_fb_info->var.green.offset     = 5;
    lcd_fb_info->var.blue.length     = 5;
    lcd_fb_info->var.blue.offset     = 0;
     
    lcd_fb_info->var.activate       = FB_ACTIVATE_NOW;
        
    /* set fb_info fixed part */
    strcpy(lcd_fb_info->fix.id, "tft-lcd");    
    lcd_fb_info->fix.smem_len       = 480*272*16/8;           //显存大小
    lcd_fb_info->fix.type             = FB_TYPE_PACKED_PIXELS;
    lcd_fb_info->fix.visual            = FB_VISUAL_TRUECOLOR;
    lcd_fb_info->fix.line_length    = 480*16/8;               //显示一行需要字节数

    /* set fb_info fbops pointer*/
    lcd_fb_info->fbops                = &lcd_fb_ops;

    lcd_fb_info->pseudo_palette = pseudo_palette;             //调色板颜色

    lcd_fb_info->screen_size        = 480*272*16/8;
    
    /* set lcd connect gpio pin register */
    gpbcon = ioremap(0x56000010, 8);
    gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020, 4);
    gpdcon = ioremap(0x56000030, 4);
    gpgcon = ioremap(0x56000060, 4);

    *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
    *gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
    
    *gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
    *gpbcon |= 1;
    *gpbdat &= ~1;     /* 输出低电平 */

    *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */

    /* set s3c2440 lcd control register */
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

    /* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
     *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
     *            CLKVAL = 4
     * bit[6:5]: 0b11, TFT LCD
     * bit[4:1]: 0b1100, 16 bpp for TFT
     * bit[0]  : 0 = Disable the video output and the LCD control signal.
     */
    lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

    /* 垂直方向的时间参数
     * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
     *             
     * bit[23:14]: 多少行, 
     * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
     *             
     * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度
     */
    lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9<<0);


    /* 水平方向的时间参数
     * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
     *             HBPD=16
     * bit[18:8]: 多少列,
     * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
     *             
     */
    lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);

    /* 水平方向的同步信号
     * bit[7:0]    : HSPW, HSYNC信号的脉冲宽度
     */    
    lcd_regs->lcdcon4 = 40;

    /* 信号的极性 
     * bit[11]: 1=565 format
     * bit[10]: 0 = The video data is fetched at VCLK falling edge
     * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
     * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
     * bit[6] : 0 = VDEN不用反转
     * bit[3] : 0 = PWREN输出0
     * bit[1] : 0 = BSWP
     * bit[0] : 1 = HWSWP 2440手册P413
     */
    lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
    
    /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
    lcd_fb_info->screen_base = dma_alloc_writecombine(NULL, lcd_fb_info->fix.smem_len, &lcd_fb_info->fix.smem_start, GFP_KERNEL);
    
    lcd_regs->lcdsaddr1  = (lcd_fb_info->fix.smem_start >> 1) & ~(3<<30);
    lcd_regs->lcdsaddr2  = ((lcd_fb_info->fix.smem_start + lcd_fb_info->fix.smem_len) >> 1) & 0x1fffff;
    lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */    
     
    /* 启动LCD */
    lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD本身 */
    lcd_regs->lcdcon5 |= (1<<3);
    *gpbdat |= 1;     /* 输出高电平, 使能背光 */
    
    register_framebuffer(lcd_fb_info);

    return 0;
}

配置硬件部分分以下几个步骤

1)寄存器物理地址到虚拟地址映射

2)配置相关GPIO引脚

3)根据LCD芯片手册时序图,设置LCD控制器各寄存器值

4)使能LCD控制器、开启背光

入口函数在设置好fb_info结构体,完成硬件配置工作后,调用register_framebuffer向内核注册fb_info信息

3.2 fb_ops结构体实现

static struct fb_ops lcd_fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c_lcdfb_setcolreg,------------------>①
    .fb_fillrect    = cfb_fillrect,-------------------------->②
    .fb_copyarea    = cfb_copyarea,-------------------------->③
    .fb_imageblit    = cfb_imageblit,------------------------>④
};

fb_ops中实现了4个函数指针,cfb_fillrect、cfb_copuarea、cfb_imageblit函数由内核实现的函数,对/dev/fbn设备文件进行访问时,会调用到这些函数

① 调用s3c_lcdfb_setcolreg函数,来设置调色板fb_info->pseudo_palette

② 填充矩形

③ 拷贝显存数据

④ 图片显示

3.3 出口函数

void lcd_drv_exit(void)
{
    unregister_framebuffer(lcd_fb_info);
    lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
    *gpbdat &= ~1;     /* 关闭背光 */
    dma_free_writecombine(NULL, lcd_fb_info->fix.smem_len, lcd_fb_info->screen_base, lcd_fb_info->fix.smem_start);
    framebuffer_release(lcd_fb_info);

    iounmap(gpbcon);
    iounmap(gpccon);
    iounmap(gpdcon);
    iounmap(gpgcon);
    iounmap(lcd_regs);    
}

驱动卸载时,注销注册的fb_info结构体,关闭LCD,释放申请的显存,释放动态分配的fb_info结构体,取消寄存器物理地址到虚拟地址映射

4、驱动程序测试

内核中如果已经将内核自带的lcd驱动编译在内,需要重新make menuconfig将lcd自带的驱动编译成模块

Device Drivers  --->
    Graphics support  --->  
        <*> Support for frame buffer devices  ---> 
             <M>   S3C2410 LCD framebuffer support  

执行make uImage生成uImage文件

执行make modules,因为内核自带的LCD驱动没有编进内核,LCD驱动相关其他代码肯定也没编译进内核,而我们编写驱动的fb_ops里的成员使用了内核中关于LCD驱动的fb_fillrect(), fb_copyarea(),fb_imageblit()函数,我们需要将其编译成.ko文件在加载lcd驱动之前加载

使用insmod加载编译好的驱动程序

insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd_drv.ko

使用echo hello > /dev/tty1命令进行测试

如果驱动正常,内核中Console display driver support / <*> Framebuffer Console support 被编译在内,lcd上将显示hello字符串

完整驱动代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>

#include <asm/mach/map.h>
//nclude <asm/arch/regs-lcd.h>
//nclude <asm/arch/regs-gpio.h>
//nclude <asm/arch/fb.h>

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
                 unsigned int green, unsigned int blue,
                 unsigned int transp, struct fb_info *info);


struct lcd_regs {
    unsigned long    lcdcon1;
    unsigned long    lcdcon2;
    unsigned long    lcdcon3;
    unsigned long    lcdcon4;
    unsigned long    lcdcon5;
    unsigned long    lcdsaddr1;
    unsigned long    lcdsaddr2;
    unsigned long    lcdsaddr3;
    unsigned long    redlut;
    unsigned long    greenlut;
    unsigned long    bluelut;
    unsigned long    reserved[9];
    unsigned long    dithmode;
    unsigned long    tpal;
    unsigned long    lcdintpnd;
    unsigned long    lcdsrcpnd;
    unsigned long    lcdintmsk;
    unsigned long    lpcsel;
};

static struct fb_info *lcd_fb_info;

static struct fb_ops lcd_fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c_lcdfb_setcolreg,
    .fb_fillrect    = cfb_fillrect,
    .fb_copyarea    = cfb_copyarea,
    .fb_imageblit    = cfb_imageblit,
};

static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];


 static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}


static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
                 unsigned int green, unsigned int blue,
                 unsigned int transp, struct fb_info *info)
{
    unsigned int val;
    
    if (regno > 16)
        return 1;

    /* 用red,green,blue三原色构造出val */
    val  = chan_to_field(red,    &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,    &info->var.blue);
    
    //((u32 *)(info->pseudo_palette))[regno] = val;
    pseudo_palette[regno] = val;
    return 0;
}



int lcd_drv_init(void)
{
    lcd_fb_info = framebuffer_alloc(0, NULL);

    /* set fb_info variable part */
    lcd_fb_info->var.xres           = 480;
    lcd_fb_info->var.yres           = 272;
    lcd_fb_info->var.xres_virtual   = 480;
    lcd_fb_info->var.yres_virtual   = 272;
    lcd_fb_info->var.bits_per_pixel = 16;

    /* color format RGB565 */
    lcd_fb_info->var.red.length     = 5;
      lcd_fb_info->var.red.offset      = 11;
      lcd_fb_info->var.green.length     = 6;
      lcd_fb_info->var.green.offset     = 5;
      lcd_fb_info->var.blue.length     = 5;
      lcd_fb_info->var.blue.offset     = 0;
     
    lcd_fb_info->var.activate       = FB_ACTIVATE_NOW;
        
    /* set fb_info fixed part */
    strcpy(lcd_fb_info->fix.id, "tft-lcd");    
    lcd_fb_info->fix.smem_len       = 480*272*16/8;
    lcd_fb_info->fix.type             = FB_TYPE_PACKED_PIXELS;
    lcd_fb_info->fix.visual            = FB_VISUAL_TRUECOLOR;
    lcd_fb_info->fix.line_length    = 480*16/8;

    /* set fb_info fbops pointer*/
    lcd_fb_info->fbops                = &lcd_fb_ops;

    lcd_fb_info->pseudo_palette = pseudo_palette;

    lcd_fb_info->screen_size        = 480*272*16/8;
    
    /* set lcd connect gpio pin register */
    gpbcon = ioremap(0x56000010, 8);
    gpbdat = gpbcon+1;
    gpccon = ioremap(0x56000020, 4);
    gpdcon = ioremap(0x56000030, 4);
    gpgcon = ioremap(0x56000060, 4);

    *gpccon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
    *gpdcon  = 0xaaaaaaaa;   /* GPIO管脚用于VD[23:8] */
    
    *gpbcon &= ~(3);  /* GPB0设置为输出引脚 */
    *gpbcon |= 1;
    *gpbdat &= ~1;     /* 输出低电平 */

    *gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */

    /* set s3c2440 lcd control register */
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

    /* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
     *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
     *            CLKVAL = 4
     * bit[6:5]: 0b11, TFT LCD
     * bit[4:1]: 0b1100, 16 bpp for TFT
     * bit[0]  : 0 = Disable the video output and the LCD control signal.
     */
    lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

    /* 垂直方向的时间参数
     * bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
     *             LCD手册 T0-T2-T1=4
     *             VBPD=3
     * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
     * bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
     *             LCD手册T2-T5=322-320=2, 所以VFPD=2-1=1
     * bit[5:0]  : VSPW, VSYNC信号的脉冲宽度, LCD手册T1=1, 所以VSPW=1-1=0
     */
    lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9<<0);


    /* 水平方向的时间参数
     * bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
     *             LCD手册 T6-T7-T8=17
     *             HBPD=16
     * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
     * bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
     *             LCD手册T8-T11=251-240=11, 所以HFPD=11-1=10
     */
    lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);

    /* 水平方向的同步信号
     * bit[7:0]    : HSPW, HSYNC信号的脉冲宽度, LCD手册T7=5, 所以HSPW=5-1=4
     */    
    lcd_regs->lcdcon4 = 40;

    /* 信号的极性 
     * bit[11]: 1=565 format
     * bit[10]: 0 = The video data is fetched at VCLK falling edge
     * bit[9] : 1 = HSYNC信号要反转,即低电平有效 
     * bit[8] : 1 = VSYNC信号要反转,即低电平有效 
     * bit[6] : 0 = VDEN不用反转
     * bit[3] : 0 = PWREN输出0
     * bit[1] : 0 = BSWP
     * bit[0] : 1 = HWSWP 2440手册P413
     */
    lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
    
    /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
    lcd_fb_info->screen_base = dma_alloc_writecombine(NULL, lcd_fb_info->fix.smem_len, &lcd_fb_info->fix.smem_start, GFP_KERNEL);
    
    lcd_regs->lcdsaddr1  = (lcd_fb_info->fix.smem_start >> 1) & ~(3<<30);
    lcd_regs->lcdsaddr2  = ((lcd_fb_info->fix.smem_start + lcd_fb_info->fix.smem_len) >> 1) & 0x1fffff;
    lcd_regs->lcdsaddr3  = (480*16/16);  /* 一行的长度(单位: 2字节) */    
     
    /* 启动LCD */
    lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD本身 */
    lcd_regs->lcdcon5 |= (1<<3);
    *gpbdat |= 1;     /* 输出高电平, 使能背光 */
    
    register_framebuffer(lcd_fb_info);

    return 0;
}

void lcd_drv_exit(void)
{
    unregister_framebuffer(lcd_fb_info);
    lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本身 */
    *gpbdat &= ~1;     /* 关闭背光 */
    dma_free_writecombine(NULL, lcd_fb_info->fix.smem_len, lcd_fb_info->screen_base, lcd_fb_info->fix.smem_start);
    framebuffer_release(lcd_fb_info);

    iounmap(gpbcon);
    iounmap(gpccon);
    iounmap(gpdcon);
    iounmap(gpgcon);
    iounmap(lcd_regs);    
}

module_init(lcd_drv_init);
module_exit(lcd_drv_exit);

MODULE_LICENSE("GPL");
lcd_drv.c
posted on 2020-10-20 22:35  quinoa  阅读(674)  评论(0编辑  收藏  举报