Linux驱动之LCD程序编写

在上一节中,分析了内核中的LCD的驱动框架,不要被内核中复杂的调用过程把我们搞迷糊了,总结起来就是以下几点
1、分配fb_info结构体
2、设置fb_info结构体
3、注册fb_info结构体

是不是很简单,软件层面的fbmem.c不需要我们去管,当然,如果你想要去深入分析的话,剩下的硬件层面的代码就做了那么三件事情,这一节,来编写具体的内核LCD驱动,遵循上面的编程流程

先来分配一个结构体,然后去设置它,在设置的时候要注意,fb_info结构体中有一个成员是固定的,就是表示这个设备的一些常用的参数,比如ID、name、类型等,还有一些是可变的,就是每一个不同类型的设备都可能不一样的

变量

static struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpcdat;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static u32 pseudo_palette[16];	//调色板
typedef 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 int lcd_init(void)
{
 /* 1、分配一个fb_info结构体 */
 s3c_lcd = framebuffer_alloc(0,NULL);	
 /* 2、设置 */
 /* 设置固定的参数fix */
 strcpy(s3c_lcd->fix.id,"mylcd");	//设备名
 s3c_lcd->fix.smem_len   = 240*320*16/8;	//frame buffer的长度,每一个像素是16bit 2个字节,总共240*320个像素
 s3c_lcd->fix.type    = FB_TYPE_PACKED_PIXELS;	//类型,LCD设备
 s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR;	//真彩色
 s3c_lcd->fix.line_length = 240*2; 		//一行数据的长度(Byte),一行240个像素点
  
  /* 设置可变的参数 */
  s3c_lcd->var.xres    = 240;		//x方向的像素
  s3c_lcd->var.yres    = 320;		//y方向的像素
  s3c_lcd->var.xres_virtual  = 240;	//虚拟的分辨率,这里设置为和真实的一样
  s3c_lcd->var.yres_virtual  = 320;
  s3c_lcd->var.bits_per_pixel = 16;	//5  6 5 格式	
  /* RGB 565 */
  s3c_lcd->var.red.offset  = 11;	//red的偏移
  s3c_lcd->var.red.length  = 5;		//red的数据长度
   s3c_lcd->var.green.offset = 5;
 s3c_lcd->var.green.length = 6;
 s3c_lcd->var.blue.offset = 0;
 s3c_lcd->var.blue.length = 5;
  s3c_lcd->var.activate  = FB_ACTIVATE_NOW;
   /* 设置操作函数 */
 s3c_lcd->fbops    = &s3c_lcdfb_ops;
  /* 其他设置 */
 s3c_lcd->pseudo_palette  = pseudo_palette;	//调色板	(必须有)
  s3c_lcd->screen_size  = 240*320*2;		//屏幕的大小
 
   /* 3、硬件相关操作 不再多说,和裸机编程中的是一样的*/
  /* GPIO */
   gpbcon = ioremap(0x56000010, 8);
 gpbdat = gpbcon + 1;
 gpccon = ioremap(0x56000020, 4);
 gpdcon = ioremap(0x56000030, 4);
 gpgcon = ioremap(0x56000060, 4);
  *gpccon = 0xaaaaaaaa;
 *gpdcon = 0xaaaaaaaa;
  *gpbcon &= ~(3); //gpb0 输出引脚
 *gpbcon |= 1;
  *gpbdat &= ~1;
   *gpgcon |= (3 << 8); //GPG4  POWER ENABLE
 
 /* LCD控制器 */
  lcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
   /*
  17:8 CLKVAL  VCLK = HCLK / [(CLKVAL+1) x 2]
      VCLK = 100M / [(CLKVAL+1) * 2]
      VCLK = 100ns = 10M (收据手册P14)
      CLKVAL = 10 / 2 - 1 = 4
  [6:5] 11 = TFT LCD panel
  [4:1] 1100 = 16 bpp for TFT
  [0]  0 = Disable 1 = Enable
 */
 lcd_regs->lcdcon1 = (4 << 8) | (3 << 5) | (0xc << 1);
  /*
  [31:24] VBPD  VBPD+1:VSYNC信号有效之后过多久,第一行数据有效,T0-T2-T1  =4,VBPD = 3
  [23:14] LINEVAL LINEVAL+1是一行有效数据,T5 LINEVAL = T5 - 1 = 319
  [13:6] VFPD VFPD+1:一帧数据显示完之后再过多久发出垂直同步信号 T2-T5=2,VFPD = 1
  [5:0]  VSPW VSPW+1:垂直同步信号持续的时间,VSPW = T1 - 1 = 0
 */
 lcd_regs->lcdcon2 = (3 << 24) | (319 << 14) | (1 << 6) | (0 << 0);
  /*
  [25:19] HBPD + 1:水平同步信号有效之后再过多久第一个像素有效 T6-T8-T7=17 HBPD=16
  [18:8] HOZVAL + 1:水平数据持续的时间 HOZVAL = T11 - 1 = 240 -1 = 239
  [7:0] HFPD + 1:一行数据显示完之后再过多久发出水平同步信号,T8-T11=251-240=11
    HFPD = 10
 */
 lcd_regs->lcdcon3 = (16 << 19) | (239 << 8) | (10 << 0);
  /*
  [7:0] HSPW + 1:水平同步信号持续的时间 HSPW = T7 -1 = 4
 */
 lcd_regs->lcdcon4 = (4 << 0);
  /*
  [11] 1 = 5:6:5 Format
  [10] 0 在VCLK下降沿采集数据 1上升沿,使用下降沿
  [9] HSYNC信号极性 1反转,需要反转
  [8] VSYNC信号极性 1反转,需要反转
  [7] VD信号极性,1反转,不需要反转
  [6] VDEN信号极性 1反转,不需要反转
  [5] PWREN信号极性 1反转
  [4] LEND
  [3] LCD_PWREN output signal enable/disable 1 = Enable
  [2] LEND output signal enable/disable 1 = Enable
  [1] BSWP 
  [0] HWSWP 这两位是数据是否交换,我们不交换
 */
 lcd_regs->lcdcon5 = (1 << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (1 << 0);

/* 显存   返回分配的地址空间的虚拟地址 */
 s3c_lcd->screen_base =  dma_alloc_writecombine(NULL,s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL); 
  lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3 << 30);
 lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
 lcd_regs->lcdsaddr3 = 240;
 
 /* 启动LCD */
 lcd_regs->lcdcon1 |= 1; //lcd本身
 lcd_regs->lcdcon5 |= (1 << 3);
 *gpbdat |= 1; //背光

  /* 4、注册 */
 register_framebuffer(s3c_lcd);
}

static void lcd_exit(void)
{
 unregister_framebuffer(s3c_lcd);
 lcd_regs->lcdcon1 &= ~1; //lcd本身
 *gpbdat &= ~1;    //背光
  dma_free_writecombine(NULL,s3c_lcd->fix.smem_len, s3c_lcd->screen_base,s3c_lcd->fix.smem_start);
 iounmap(lcd_regs);
  iounmap(gpbcon);
 iounmap(gpbcon);
 iounmap(gpdcon);
  iounmap(gpgcon);
 framebuffer_release(s3c_lcd);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

上面对LCD控制器的基本设置就已经完成了,LCD的时序部分请看数据手册**“液晶屏”**,具体的对比如下
在这里插入图片描述
fb_info的设置中有一项函数的设置

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

这里这个调色板的函数必须设置,简单介绍一下调色板的概念
framebuffer(显存)就是在内存中划分的一块内存空间,里边存放的是LCD的显示数据,LCD控制器获取显存中的数据然后通过RGB数据线把数据发送给LCD设备进行显示,我们这款LCD的分辨率是240320,每一个像素的格式是565,那么显存的大小就是24032016/8个字节,引入调色板的概念就是为了减少显存所占内存空间的大小,每一个像素分配一个字节,那么显存所占的总大小就是240320*1个字节,那么,每一个像素在显存中的这个字节并不是真实的数据,而是作为索引,从调色板中获取真实的565格式的数据发给LCD控制器。

在这里的设置的调色板是一个假的调色板,不是很重要

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;
  val  = chan_to_field(red, &info->var.red);   //把RGB合并为一个565的数据,根据每一个颜色的位起始和长度
  val |= chan_to_field(green, &info->var.green);
  val |= chan_to_field(blue, &info->var.blue);
  pseudo_palette[regno] = val; 	//放入调色板
 return 0;
 }

结构体中的其他三个函数没有设置,因为这三个函数是其他文件里的函数,等会要加载的
程序已经编写完了,接下来就来测试。

测试
make menuconfig 去掉原来的驱动程序
make uImage
make modules 编译模块,包括LCD所依赖的模块
使用新内核启动

挂载网络文件系统 已经制作好了一个根文件系统备用 /work/nfs_root/fs_mini_mdev_my
mv arch/arm/boot/uImage /work/nfs_root/fs_mini_mdev_my/uImage_nolcd
mv drivers/video/cfg*.ko /work/nfs_root/fs_mini_mdev_my
mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.1.101:/work/nfs_root/fs_mini_mdev_my /mnt
启动之后
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
在这里插入图片描述
cat lcd.ko > /dev/fb0 出现花屏
echo hello > /dev/tty1 出现hello

问题
insmod lcd.ko出现段错误,根据网上的解决方案,配置内核去掉FRAMEBUFFER_CONSOLE,然后装载lcd.ko,确实不再出现段错误,但是,LCD屏幕显示不了hello,试过多次,无果,
于是尝试另一种方法,把lcd.c编译进内核中,原来内核中使用的LCD的驱动文件是drivers/video/s3c2410fb.c,
在drivers/video/Makefile中去掉s3c2410ffb.c的编译,添加lcd.c的编译条件
在这里插入图片描述
在同目录下的Kconfig中添加配置
在这里插入图片描述
选择编译,这个时候要记得选择配置内核LCD支持,因为驱动文件以及被换成了我们自己的,不用再以模块的形式加载,
同时选择上面去掉的FRAMEBUFFER_CONSOLE,编译
在这里插入图片描述
使用新内核,启动,即可看到效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值