LCD液晶屏驱动详解(4)

发布时间:2024年01月24日

3、调色板

image-20210718175642065

  • 临时调色板寄存器TPAL

如果要输出一帧单色的图像,可以在TPAL寄存器中设定这个颜色值,然后使能TPAL寄存器,这种方法可以避免修改整个调色板或帧缓冲区。

TPAL寄存器格式:

功能说明
TPALEN[24]调色板寄存器使能位,0禁止,1使能;
TPALVAL[23:0]颜色值;TPALVAL[23:16]:红色TPALVAL[15:8]:绿色TPALVAL[7:0]:蓝色

注意:临时调色板寄存器TPAL可以用在任何显示模式下,并非只能用在8BPP模式下。

4、编写驱动

4.1 lcd.c

  • 搭建整体框架

可参考内核自带的相关lcd驱动(drivers/video/),添加头文件:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.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 <linux/cpufreq.h>

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

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

static struct fb_info *s3c_lcd;
static int lcd_init(void)
{
    /* 1. 分配一个fb_info */
    s3c_lcd = framebuffer_alloc(0, NULL);

    /* 2. 设置 */
        /* 2.1 设置固定参数 */
        /* 2.2 设置可变参数 */
        /* 2.3 设置操作函数 */
        /* 2.4 设置其它内容 */
    
    /* 3. 硬件相关的操作 */
        /* 3.1 配置GPIO用于LCD */
        /* 3.2 根据LCD手册设置LCD控制器,例如VCLK频率等 */
        /* 3.3 分配显存(frambuffer),并将地址告诉LCD控制器 */
    
    /* 4. 注册 */
    register_framebuffer(s3c_lcd);
    
    return 0;
}

static void lcd_exit(void)
{
    
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
  • 入口函数lcd_init()

    • 1、分配一个fb_info
      s3c_lcd = framebuffer_alloc(0, NULL);
    
    • 2、 设置
    1. 设置固定参数——fb_fix_screeninfo结构体

    image-20210718213023752

    /* 2.1 设置固定的参数 */
    strcpy(s3c_lcd->fix.id, "mylcd");
    s3c_lcd->fix.smem_len = 480*272*16/8; //屏幕分辨率480X272,16bpp/pix
    s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;    //屏幕类型
    s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* 真彩TFT */
    s3c_lcd->fix.line_length = 480*2; //一行需要的存储长度=480像素X2字节
    
  1. 设置可变参数

image-20210718222829528

/* 2.2 设置可变参数 */
s3c_lcd->var.xres           = 480;    //X方向的分辨率
s3c_lcd->var.yres           = 272;    //y方向的分辨率
s3c_lcd->var.xres_virtual   = 480;    //X方向虚拟的分辨率
s3c_lcd->var.yres_virtual   = 272;    //y方向的虚拟分辨率
s3c_lcd->var.bits_per_pixel = 16; //每个像素用多少位表示

/* RGB:565 */
s3c_lcd->var.red.offset     = 11; //从第11位开始
s3c_lcd->var.red.length     = 5;  //占5个位
s3c_lcd->var.red.msb_right  = 0;  //数据在offset的右边吗?默认为0,表示在左边(高位方向)。可以不需设置

s3c_lcd->var.green.offset   = 5;  //从第5位开始
s3c_lcd->var.green.length   = 6;

s3c_lcd->var.blue.offset    = 0;  //从第0位开始
s3c_lcd->var.blue.length    = 5;

s3c_lcd->var.activate       = FB_ACTIVATE_NOW;    //不明白,暂用默认值
  1. 设置操作函数——fbops

s3c_lcd->fbops    = &s3c_lcdfb_ops;
  • 在函数外定义fb_ops结构体:

static struct fb_ops s3c_lcdfb_ops = {
  .owner          = THIS_MODULE,
  .fb_setcolreg   = s3c_lcdfb_setcolreg,  //调色板设置函数
  .fb_fillrect    = cfb_fillrect,
  .fb_copyarea    = cfb_copyarea,
  .fb_imageblit   = cfb_imageblit,
};
  1. 其它的设置

s3c_lcd->pseudo_palette = pseudo_palette; //调色板数组地址
s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */ 
s3c_lcd->screen_size   = 480*272*16/8;
  • 3、硬件相关的操作
  1. 配置GPIO用于LCD

image-20210718230408435

image-20210718231335391

通过原理图可知,所有使用到的引脚均要配置。然后查看原理图,找到各引脚对应的IO端口:

image-20210718230823866

image-20210718231849600

首先在函数外定义用到的IO口的寄存器指针变量:

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;

然后在函数体内映射地址:

/*配置GPIO用于LCD*/
//即使你写了仅映射4个字节,系统也还是会映射至少1页(4KB)
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] */

/* GPB0设置为输出引脚 */
*gpbcon &= ~(3);  
*gpbcon |= 1;
*gpbdat &= ~1;     /* 先输出低电平,使背光电源关闭 */

*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN(LCD本身电源) */
  1. 根据LCD手册设置LCD控制器,例如VCLK频率等

      **首先**,查看S3C2440芯片手册,并设置LCD controller章节中的控制寄存器。为了方便引用,先定义一个全局结构体(lcd_regs),内容就是各寄存器的地址。即:
    

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;
};

            **然后**,再定义一个指向该类型结构体的指针`lcd_regs`:

static volatile struct lcd_regs* lcd_regs;    //所有指向寄存器的地址必须是volatile修饰的

            **最后**,到`lcd.c`函数中进行地址映射,而后根据LCD数据手册设置LCD控制寄存器:

为了便于大家查看,将这3幅图重新放在这边:

LCD时间参数

LCD时间序列

  lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
  //1.设置LCDCON1寄存器
  /* CLKVAL   =>  bit[17:8]:  VCLK = HCLK / [(CLKVAL+1) x 2],
     *                            VCLK取值查看LCD手册3.5.1节的Clock cycle
   *                          9MHz(Typ) = 100MHz / [(CLKVAL+1) x 2]
   *                          CLKVAL = 4(在此取整数4)
   * MMODE    =>  bit[7]:取默认值
   * PRNMODE  =>  bit[6:5]: 0b11  (TFT LCD panel)
   * BPPmode  =>  bit[4:1]: 0b1100(16 bpp for TFT)
   * ENVID    =>  bit[0]  : 0b0   (先暂时禁止,需要时打开.)
   */
  lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

  //2.设置LCDCON2-4寄存器
#if 1
  /* 垂直方向的时间参数
   * VBPD     =>  bit[31:24]: 1, VSYNC之后再过多长时间才能发出第1行数据
   * LINEVAL  =>  bit[23:14]: 271, 所以LINEVAL=272-1=271
   * VFPD     =>  bit[13:6] : 1, 发出最后一行数据之后,再过多长时间才发出VSYNC,所以VFPD=2-1=1
   * VSPW     =>  bit[5:0]  : 9, VSYNC信号的脉冲宽度,VSPW=10-1=9
   */
  lcd_regs->lcdcon2  = (1<<24) | (271<<14) | (1<<6) | (9);
  
  /* 水平方向的时间参数
   * HBPD     =>  bit[25:19]: 1, VSYNC之后再过多长时间才能发出第1行数,HBPD=2-1
   * HOZVAL   =>  bit[18:8]: 479, HOZVAL=480-1=479
   * HFPD     =>  bit[7:0] : 1 , 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC,HFPD=2-1=1
   */
  lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);

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

#else
  lcd_regs->lcdcon2 = S3C2410_LCDCON2_VBPD(5) | \
      S3C2410_LCDCON2_LINEVAL(319) | \
      S3C2410_LCDCON2_VFPD(3) | \
      S3C2410_LCDCON2_VSPW(1);

  lcd_regs->lcdcon3 = S3C2410_LCDCON3_HBPD(10) | \
      S3C2410_LCDCON3_HOZVAL(239) | \
      S3C2410_LCDCON3_HFPD(1);

  lcd_regs->lcdcon4 = S3C2410_LCDCON4_MVAL(13) | \
      S3C2410_LCDCON4_HSPW(0);
#endif

/* 信号的极性 
   * bit[11]: 1=565 format
   * bit[10]: 0 = 根据LCD手册,其在下降沿取数据
   * bit[9] : 1 = HSYNC信号要反转(对比S3C2440手册与LCD手册的时序图)
   * bit[8] : 1 = VSYNC信号要反转,
   * bit[7] : 0 = INVVD不用反转(数据引脚低电平表示数据1)
   * bit[6] : 0 = VDEN不用反转(对比S3C2440手册与LCD手册的时序图)
   * bit[5] : 0 = INVPWREN不用反转(电源使能开关高电平有效)
   * bit[3] : 0 = PWREN信号输出使能(暂时先不使能它,到后面设置完后再打开)
   * bit[1:0] : 01,内存数据和像素点对应关系,00表示D[31:0]的高8位对应Pix1,
   *              低8位对应Pix2,01表示D[31:0]的高8位对应Pix2,低8位对应Pix1
   *              S3C2440手册第413页
   */
  lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
  1. 分配显存(frambuffer),并将地址告诉LCD控制器 ,最后启动LCD

  /* screen_base:显存的虚拟地址;smem_start:显存的物理地址。*/
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);

  /* Frame Buffer 的起始地址
   * LCDBANK  =>  bit[29:21]: ,对应视频缓存区开始地址的A[30:22]位,(4MB地址对齐)
     * LCDBASEU   =>  bit[20:0]: ,对应视频缓存区开始地址的A[21:1]位,
   */
  lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
  
  /* Frame Buffer 的结束地址
   * LCDBASEL =>  bit[20:0]: ,对应视频缓存区结束地址的A[21:1]位,
   */
  lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;

  /* Frame Buffer 的有效显示区的宽度(半字,即2字节为单位)
   * OFFSIZE   => bit[21:11]: ,不懂,取默认值
   * PAGEWIDTH => bit[10:0]: ,一行的长度(单位: 2字节) 
   */
  lcd_regs->lcdsaddr3  = (480*16/16);  
  
  /* 启动LCD */
  lcd_regs->lcdcon1 |= (1<<0); /* 使能ENVID信号,表示传输数据 */
  lcd_regs->lcdcon5 |= (1<<3); /* 使能PWREN信号 */
  *gpbdat |= 1;                /* 输出高电平, 使能背光 */  
  • 4、注册

/* 4. 注册 */
register_framebuffer(s3c_lcd);
  • 出口函数lcd_exit()

static void lcd_exit(void)
{
    unregister_framebuffer(s3c_lcd);    //注销fb
    lcd_regs->lcdcon1 &= ~(1<<0); /* 停止向LCD发送数据 */
    lcd_regs->lcdcon5 &= ~(1<<3); /* 关闭PWREN信号 */
    *gpbdat &= ~1;     /* 关闭背光 */
    //dma_free_writecombine(设备,内存长度,虚拟起始地址,起始物理地址)
    dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
    
    iounmap(lcd_regs);
    iounmap(gpbcon);
    iounmap(gpccon);
    iounmap(gpdcon);
    iounmap(gpgcon);
    
    framebuffer_release(s3c_lcd);   //释放fb
}
文章来源:https://blog.csdn.net/weixin_45534242/article/details/135728831
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。