Linux的LCD驱动

前言

        Linux的源码中本身已经抽象出了LCD驱动的公共部分代码——drivers/video/fbmem.c,对于驱动开发人员来讲,只需要理解这部分的代码并会调用其提供的接口即可。驱动开发人员需要做的就是针对具体的SOC和LCD,设置对应的LCD参数和寄存器值即可。

       至于fbmem.c的流程已经有很多文章介绍过了,我这里就不具体介绍了,可以参考一下这篇文章:Linux Framebuffer驱动剖析之二—驱动框架、接口实现和使用。下面我就具体介绍一下怎么针对具体的SOC和LCD进行编程。

正文

这里直接给出一个LCD驱动程序编写的流程吧:

1. 分配 一个fb_info结构体:framebuffer_alloc

2. 设置
2.1 设置固定的参数
2.2 设置可变的参数
2.3 设置操作函数:和我们自己的fb_ops联系起来
2.4 其他设置:比如设置调色板

3. 硬件相关的设置
3.1 配置GPIO用于LCD
3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等
3.3 分配显存(framebuffer),并把地址告诉LCD控制器

4. 注册:register_framebuffer()

根据上面的步骤,下面我们直接给出代码再来解释:

#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>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>

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 *gpdcon;
static volatile unsigned long *gpgcon;

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

struct lcd_regs *lcd_regs;
static u32 pseudo_palette[16];

static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length; /*对应到s3c_lcd->var.red.length*/
	return chan << bf->offset; /*对应到s3c_lcd->var.red.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);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue, &info->var.blue);

	pseudo_palette[regno] = val;
	return 0;
}

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

static void lcd_init(void)
{
	/*1. 分配 一个fb_info结构体*/
	s3c_lcd = framebuffer_alloc(0, NULL);
	if (!s3c_lcd)
		return -ENOMEM;
	printk("framebuffer alloc success\n");
	
	/*2. 设置*/
	/*2.1 设置固定的参数*/
	strcpy(s3c_lcd->fix.id, "mylcd");
	s3c_lcd->fix.smem_len = 480 * 272 * 16 / 8; /*RGB565*/
	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR;
	s3c_lcd->fix.line_length = 480 * 2; /*一开始写成了320,造成了Segmentation fault*/
	
	/*2.2 设置可变的参数*/
	s3c_lcd->var.xres = 480;
	s3c_lcd->var.yres = 272;
	s3c_lcd->var.xres_virtual = 480;
	s3c_lcd->var.yres_virtual = 272;
	s3c_lcd->var.bits_per_pixel = 16;

	/*RGB565*/
	s3c_lcd->var.red.offset   = 11;
	s3c_lcd->var.red.length   = 5;

	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;
	
	
	/*2.3 设置操作函数*/
	s3c_lcd->fbops = &s3c_fb_ops;
	
	/*2.4 其他设置*/
	s3c_lcd->pseudo_palette = pseudo_palette;
	//s3c_lcd->screen_base = ; /*显存的虚拟地址*/
	s3c_lcd->screen_size = 480 * 272 * 16 / 8;
	printk("start ot ioremap\n");
	/*3. 硬件相关的设置*/
	/*3.1 配置GPIO用于LCD*/
	gpbcon = ioremap(0x56000010, 8);
	gpbdat = gpbcon + 1;

	gpccon = ioremap(0x56000020, 4);
	gpdcon = ioremap(0x56000030, 4);
	gpgcon = ioremap(0x56000060, 4);

	*gpdcon = 0xaaaaaaaa; /*设置为output : 01 = Output*/
	*gpccon = 0xaaaaaaaa;

	*gpbcon &= ~(3<<0);
	*gpbcon |= 1; /*output*/
	*gpbdat &= ~1; /*输出低电平,关闭背光灯*/

	*gpgcon &= ~(3<<8);
	*gpgcon |= (3<<8);   //GPG4  : 11 = LCD_PWRDN

	/*3.2 根据LCD手册设置LCD控制器,比如VCLK的频率等*/
	lcd_regs = ioremap(0X4D000000, sizeof(struct lcd_regs));
	printk("after ioremap the register\n");
	/* bit[17:8] :    VCLK = HCLK / [(CLKVAL+1) x 2]
	  *     (10MHz)100ns  = 100MHz / [(CLKVAL+1) x 2]
	  *                 CLKVAL = 4
	  * bit[6:5]  : 11 = TFT LCD panel
	  * bit[4:1]  : 1100 = 16 bpp for TFT
	  * bit[0]     : 0 = Disable the video output and the LCD control signal
	  */
	lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0xc<<1);

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

	
	/*水平方向的时间参数
	  *bit[25:19] : HBPD, HSYNC之后过多长时间才能发出第一个像素数据
	  *bit[18:8] : HOZVAL, 多少列数据
	  *bit[7:0] : HFPD, 发出最后一个像素后,过多久发出HSYNC
	  *LCDCON4的bit[7:0] : HSPW, HSYNC的脉冲宽度
	  */
	lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);
	lcd_regs->lcdcon4 = (40);

	/*信号的极性
        *bit[11] : 1 = 5:6:5 format
        *bit[10] : 0 = 低电平有效
        *bit[9]   : 1 = HSYNC信号要反转,即低电平有效
        *bit[8]   : 1 = VSYNC信号要反转,即低电平有效
        *bit[3]   : 0 = PWREN输出0
        *bit[1]   : 0 = BSWP
        *bit[0]   : 1 = HWSWP  2440芯片手册P413
	  */
	lcd_regs->lcdcon5 = (1<<11) | (1<<9) | (1<<8) | (1<<0);

	/*3.3 分配显存(framebuffer),并把地址告诉LCD控制器*/
	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 = 480 * 16 / 16; /* 一行的长度(单位是:2字节)*/
	
	//s3c_lcd->fix.smem_start = xxx;

	/* 启动LCD */
	lcd_regs->lcdcon5 |= (1<<3); /*使能LCD本身*/
	lcd_regs->lcdcon1 |= (1<<0); /*使能LCD控制器*/
	*gpbdat |= 1; /* 输出高电平,使能背光 */
	printk("after set the lcdconx\n");
	/*4. 注册*/
	register_framebuffer(s3c_lcd);

	return 0;
}

static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd);
	lcd_regs->lcdcon1 &= ~(1<<0); /*关掉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(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	iounmap(gpbcon);
	framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");

1、分配 一个fb_info结构体

直接就调用framebuffer_alloc()函数来分配一个struct fb_info结构体就好了,没什么好讲的

2、设置LCD的的固定和可变参数

        这两个结构体的参数,根据注释和已有的LCD驱动代码,我们是可以一个一个进行填充的。这里重点讲一下s3c_lcd->fix.smem_len(framebuffer大小),根据我们的4.3寸的LCD手册,像素是480*272的,且像素的格式是RGB565,,也就是16bit,所以framebuffer的大小就应该是480*272*16/8(单位:byte)。同理,s3c_lcd->fix.line_length代表每列的长度,单位是byte,因为像素为480*272,每个像素的大小为2byte,所以每列长度为480*2(单位:byte)。

固定参数结构体:
struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
	__u32 accel;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 reserved[3];		/* Reserved for future compatibility */
};

s3c_lcd->var.bits_per_pixel顾名思义就是每个像素有多少bit,我们这里是RGB565,也就是16 bit。假如每个像素格式如下:

RRRRRGGGGGGBBBBB

 很明显,R的offset为从最低位(右起)偏移11位,占5bit,所以可变参数的设置就为:

s3c_lcd->var.red.offset   = 11;
s3c_lcd->var.red.length   = 5;

 同理,G和B的设置也就很容易了。

可变参数结构体:
struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what			*/
	__u32 grayscale;		/* != 0 Graylevels instead of colors */

	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* transparency			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* (OBSOLETE) see fb_info.flags */

	/* Timing: All values in pixclocks, except pixclock (of course) */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 rotate;			/* angle we rotate counter clockwise */
	__u32 reserved[5];		/* Reserved for future compatibility */
};

3、操作函数的设置

我是直接参考现有的LCD驱动:drivers/video/s3c2410fb.c。唯一需要我们自己改动的就是调色板的函数,后面再说

4、 假的调色板

       为什么这里要说是假的调色板呢?因为我这里的LCD驱动程序实际上并没用到调色板这个东西,但是为了兼容,也模仿程序加了一个。至于什么是调色板,可以参考一下我以前的文章:S3C2440芯片的LCD控制器,里面有一小节降到了调色板的概念。

看一下设置调色板的函数:

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);
	val |= chan_to_field(green, &info->var.green);
	val |= chan_to_field(blue, &info->var.blue);

	pseudo_palette[regno] = val;
	return 0;
}

       函数的参数中有红蓝绿3种颜色,然后经过chan_to_field()函数的“调色”后,赋值给val,然后就放到“调色板“pseudo_palette中。我们再看一下chan_to_field函数中是怎么”调色“的。

static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length; /*对应到s3c_lcd->var.red.length*/
	return chan << bf->offset; /*对应到s3c_lcd->var.red.offset*/
}

       其实也很简单,一个像素点的格式是RGB565,在设置fb_info结构体的可变参数时,我们曾经设置过RGB 3色的长度和偏移,这里就是从chan(表示R、G或者B的一种)得到一个像素点中R或者G或者B的值。最后将RGB拼凑成val放到pseudo_palette中。

5、配置GPIO用于LCD

       每个板子LCD相关的GPIO都不一样,所以设置肯定各不相同,不过道理是一样的,根据硬件原理图,将LCD所用到的GPIO都ioremap一下,然后就根据芯片手册赋相关的值,使其成为输出还是输入引脚。

6、配置LCD控制器

       一般来说,开发板是通过LCD控制器来控制LCD扫描的频率、垂直方向和水平方向的各种时间参数,所以我们还需要根据芯片手册来设置LCD控制器的各个寄存器的值,达到我们的要求。由于LCD控制器有很多寄存器,这里我就不一一列举了,下面示范一下怎么通过读芯片手册,来设置LCD控制器的寄存器。(下面的设置是根据3.5寸的LCD屏幕手册来设置的,上面的代码参数设置是4.3寸的屏幕,所以数值会有差异

下面看一下寄存器LCDCON2(垂直方向参数)的各个bit的设置

S3C2440芯片手册中的时序图:

 

下面是LCD手册展示的时序图: 

 LCD参考手册中的时序的具体参考值如下:

所以结合LCD参考手册中的时序图和具体的时序值,我们可以反推出LCDCON2中各个bit的值:

VBPD + 1 = T0 - T2 - T0 = 327 - 322 - 1 = 4,所以 VBPD = 3

LINEVAL + 1 = T5 = 320,所以LINEVAL = 319

VFPD + 1 = T2 - T5 = 2,所以VFPD = 1

VSPW + 1 = T1 = 1,所以VSPW = 0

 ******************************************************************************************************************

下面看一下设置水平方向参数的寄存器LCDCON3和LCDCON4:

下面是S3C2440芯片手册中的水平方向参数的时序图: 

LCD手册中的水平参数的时序图:

LCD手册中水平方向参数的具体值:

所以结合LCD手册,我们可以得到LCDCON3寄存器各个bit的值:

HBPD + 1 = T6 - T8 - T7 = 273 - 251 - 5 = 17,所以HBPD = 16

HOZVAL + 1 = T11 = 240,所以HOZVAL = 239

HFPD + 1 = T8 - T11 = 251 - 240 = 11,所以HFPD = 10

HSPW + 1 = T7 = 5.所以HSPW = 4

7、分配显存

       一般来说,配置高点的电子设备都有单独的显存来做显示的工作,不过我的开发板配置较低,需要从内存中直接划分一块出来做显存用。Linux已经为我们提供了一些接口函数,比如这里我们可以直接调用dma_alloc_writecombine()来分配一块由我们指定大小的内存,如果分配成功就会返回物理地址和虚拟地址的首地址。这个函数的作用可以参考一下这篇文章:、、dma_alloc_writecombine 和mmap函数。然后就是通过设置LCDADDRx寄存器,将显存的起始地址和大小告诉LCD控制器。

8、注册framebuffer

其实在Linux的整个LCD框架下编程,我们需要做的工作,也就是设置一些硬件相关的参数,这些工作都做完后,就用register_framebuffer()函数直接将设置好的fb_info结构体注册进系统。

  • 6
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值