jz2440_LCD驱动程序(非标准)

1. LCD 驱动原理

1.1 LCD 操作原理

在这里插入图片描述

  • LCD 屏幕上的点称为像素
  • 屏幕后面有电子枪【红绿蓝】:一边移动,一边发出颜色
  • 每来一个clk,移动一个像素
  • R、G、B 三组线确定颜色
  • 接收到 HSYNC(水平同步信号)脉冲,电子枪从最右边跳到最左边。(跳到下一行)
  • 接收到 VSYNC [垂直同步信号]从最下边跳到最上边。(跳到原点)

1.2 驱动 LCD 原理


LCD 驱动的原理如上图所示:

TFT LCD 的显示原理是 LCD 内有一个电子枪,每次会绘制一个像素点。

  • 当信号 VDEN 有效时,会根据数据线 VD 上的 RGB 颜色情况,绘制一个像素点;
  • VCLK 时钟信号,控制电子枪水平移动到下一个像素点(从左至右);
  • HSYNC 同步信号,使电子枪往下移动一行,并回到该行的起始位置;
  • VSYNC/FRAME 同步信号,则使电子枪回到第一个像素点;
  • 当我们只移动 VCLK\HSYNC\VSYNC 信号,而使 VDEN 为无效时,就会显示出黑屏;

对于 RGB 色彩的数据是存储在 SDRAM 里划出来的一块显存空间中,由 S3C2440 的 LCDCDMA 这个 LCD 专用的 DMA 控制器自动从 SDRAM 里面去读取,然后通过 VIDPRCS 寄存器 发送给 TFT LCD 屏幕。

而 VIDEO MUX 模块则控制时序等操作。

2. Linux 内核驱动程序框架

在编写驱动程序之前,我们需要先了解驱动程序的框架。驱动程序的框架参考 Linux内核 LCD 驱动程序框架_Bin Watson的博客-CSDN博客 这篇文章。

3. jz2440 LCD相关硬件分析

jz2440_LCD硬件分析_Bin Watson的博客-CSDN博客

4. 驱动程序(非标准实现)

4.1 驱动程序的入口函数

分配一个 struct fb_info 结构体,用于注册 framebuffer 时使用。并且完成该结构体的初始化工作。

  1. 分配一个 fb_info 结构体,然后往该结构体里面填充数据。

    static struct fb_info *s3c2440_info;
    
    static int __init s3c2440_lcd_init(void)
    {
    	int ret;
    
    	/* 1. 分配一个fb_info结构体 */
    	s3c2440_info = framebuffer_alloc(0, NULL);	/* 不需要额外的空间 */
    	if (!s3c2440_info) {
    		return -ENOMEM;
    	}
    
  2. 设置 fb_info 结构体的数据,先设置固定参数 fb_fix_screeninfo

    	/* 2. 设置 */
    	/* 2.1 设置固定的参数 */
    	strcpy(s3c2440_info->fix.id, "s3c2440-lcd");		/* identification string */
    	// s3c2440_info->fix.smem_start = ;	/* Start of frame buffer mem (physical address) */
    	s3c2440_info->fix.smem_len	= FRAME_BUFFER_LEN; 	/* Length of frame buffer mem */
    
    	s3c2440_info->fix.type		= FB_TYPE_PACKED_PIXELS;
    	s3c2440_info->fix.type_aux 	= 0;					/* INTERLEAVED_PLANES 才设置 */
    	s3c2440_info->fix.visual	= FB_VISUAL_TRUECOLOR;	/* 颜色模式为真彩色 */
    	
    	s3c2440_info->fix.xpanstep	= 0;
    	s3c2440_info->fix.ypanstep	= 0;
    	s3c2440_info->fix.ywrapstep = 0;
    	
    	s3c2440_info->fix.line_length = FRAME_LINE_LENGTH;	/* 一行的长度,以字节为单位 */
    

    smem_start 指定的是显存的物理地址的起始地址,我们在后面进行设置。

    smem_len 指定的是显示的物理地址的大小。

    其中 FRAME_BUFFER_LEN 等宏定义如下,我们使用的是 A043-24-TT-11 这款 LCD 屏幕,其分辨率为 480 * 272。

    #define FRAME_X_RES 		480		/* 表示屏幕一行的像素点个数 */
    #define FRAME_Y_RES			272		/* 表示屏幕一列的像素点个数 */
    
    #define FRAME_PIXEL_RED		5		/* 红色占5个位 */
    #define FRAME_PIXEL_GREE	6		/* 绿色占6个位 */
    #define FRAME_PIXEL_BLUE	5		/* 蓝色占5个位 */
    #define FRAME_PIXEL			(FRAME_PIXEL_RED + FRAME_PIXEL_GREE + FRAME_PIXEL_BLUE)
    
    #define FRAME_BUFFER_LEN  	(FRAME_X_RES * (FRAME_PIXEL/8) * FRAME_Y_RES)
    #define FRAME_LINE_LENGTH 	(FRAME_X_RES * (FRAME_PIXEL/8))
    

    再根据 jz2440 的原理图
    在这里插入图片描述
    可知,我们的 LCD 插座只接了 16 根数据线(其中红色 R1 ~ R5,绿色 G0 ~ G5,蓝色 B1~ B5)。
    也就是说我们使用的是 S3C2440A 芯片的 LCD 控制器中的 16bpp 的显示方式。
    而根据 S3C2440A 的 LCD 控制器位设置模式
    在这里插入图片描述

    我们将 红色设置为高5位、绿色设置为中间6位、蓝色设置为低5位。定义在了上方的宏 FRAME_PIXEL_RED、FRAME_PIXEL_GREE 和 FRAME_PIXEL_BLUE 中。

  3. 设置可变参数 fb_var_screeninfo

    	/* 2.2 设置可变的参数 */
    	s3c2440_info->var.xres	= FRAME_X_RES;			/* 水平分辨率 */
    	s3c2440_info->var.yres  = FRAME_Y_RES;			/* 垂直分辨率 */
    	s3c2440_info->var.xres_virtual = FRAME_X_RES;	/* 水平虚拟分辨率 */
    	s3c2440_info->var.yres_virtual = FRAME_Y_RES;	/* 垂直虚拟分辨率 */
    	s3c2440_info->var.xoffset = 0;		/* 虚拟分辨率与真实分辨率之间从偏移值 */
    	s3c2440_info->var.yoffset = 0;		/* 虚拟分辨率与真实分辨率之间从偏移值 */
    	
    	s3c2440_info->var.bits_per_pixel = FRAME_PIXEL;	/* 每个像素用多少位 */
    
    	s3c2440_info->var.red.offset   	= FRAME_PIXEL_BLUE + FRAME_PIXEL_GREE;
    	s3c2440_info->var.red.length   	= FRAME_PIXEL_RED;
    	s3c2440_info->var.green.offset 	= FRAME_PIXEL_BLUE;
    	s3c2440_info->var.green.length 	= FRAME_PIXEL_GREE;
    	s3c2440_info->var.blue.offset  	= 0;
    	s3c2440_info->var.blue.length  	= FRAME_PIXEL_BLUE;
    
    	s3c2440_info->var.activate = FB_ACTIVATE_NOW;
    	s3c2440_info->var.height = (__u32)53.856;
    	s3c2440_info->var.width  = (__u32)95.04;
    

    对于其中参数的说明参考 Linux内核 LCD 驱动程序框架_Bin Watson的博客-CSDN博客

    我们对 LCD 的操作时序进行分析:
    在这里插入图片描述

    我们可以得出:

    描述取值运算
    VBPD12 Hsync - 1
    LINEVAL271272 Hsync - 1
    VFPD12 Hsync - 1
    VSPW910 Hsync - 1
    HBPD12CLK - 1
    HOZVAL479480 - 1
    HFPD12CLK - 1
    HSPW4041CLK - 1
    VCLK10 MHz为了方便后面计算 CLKVAL 的值,我们取一个 LCD 控制器能够接受的值。

    在这里插入图片描述

    VCLK 能够取的 VCLK 的值在 5 MHz ~ 12 MHz 之间。

    于是我们就可以设置 left_margin 等变量了

    	s3c2440_info->var.pixclock  = 0;			/* pixel clock in ps (pico seconds) */
    	s3c2440_info->var.left_margin	= 1;		/* HFPD */
    	s3c2440_info->var.right_margin 	= 1;		/* HBPD */
    	s3c2440_info->var.upper_margin	= 1;		/* VFPD */
    	s3c2440_info->var.lower_margin 	= 1;		/* VBPD */
    	s3c2440_info->var.hsync_len	= 40;			/* HSPW */
    	s3c2440_info->var.vsync_len = 9;			/* VSPW */
    	s3c2440_info->var.sync 		= FB_SYNC_HOR_HIGH_ACT;		/* see FB_SYNC_*  */
    	s3c2440_info->var.vmode 	= FB_VMODE_NONINTERLACED;	/* see FB_VMODE_* */
    
  4. 接着我们在设置 fb_info 的硬件操作函数

    	/* 2.3 设置操作函数 */
    	s3c2440_info->fbops = &s3c2440_lcd_fb_ops;
    	/* 2.4 其它设置 */
    
    	s3c2440_info->pseudo_palette = pseudo_platte;
    	// s3c2440_info->screen_base = ;	/* Virtual address 显存的虚拟地址 */
    	s3c2440_info->screen_size	 = FRAME_BUFFER_LEN;	/* Amount of ioremapped VRAM or 0 */ 
    

    screen_size 指定的屏幕的显存的虚拟地址大小。

    screen_base 指定显存的虚拟地址的起始地址,我们在后面进行设置。

    s3c2440_lcd_fb_ops 的定义如下:

    static struct fb_ops s3c2440_lcd_fb_ops = {
    	.owner		= THIS_MODULE,
    	.fb_setcolreg	= s3c2440_lcd_setcolreg,
    	.fb_fillrect	= cfb_fillrect,		/* 内核实现 */
    	.fb_copyarea	= cfb_copyarea,		/* 内核实现 */
    	.fb_imageblit	= cfb_imageblit,	/* 内核实现 */
    };
    

    cfb_fillrect、cfb_copyarea、cfb_imageblit 已经由内核实现好了,它们会根据 fb_info 中设置的 fb_fix_screeninfo 和 fb_var_screeninfo 两个结构体中的信息,帮助我们往 LCD 屏幕上绘制图像。

    s3c2440_lcd_setcolreg 是一个虚假调色板:

    static u32 pseudo_platte[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 s3c2440_lcd_setcolreg(unsigned int regno, unsigned int red,
    			     unsigned int green, unsigned int blue,
    			     unsigned int transp, struct fb_info *info)
    {
    	unsigned int val;
    
    	/* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n", regno, red, green, blue); */
    	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);
    
    	((u32*)(info->pseudo_palette))[regno] = val;
    
    	return 0;
    }
    

上面我们就大致完成了 fb_info 的设置,下面是对 S3C2440 LCD 控制器的设置,同时还有要配置一些 GPIO 引脚工作为 LCD 使用的引脚模式。

  1. 配置 GPIO硬件用于 LCD

    在这里插入图片描述
    上面的原理图说明了用于驱动 LCD 屏幕的引脚设置,包括数据引脚、控制引脚。

    而要使 LCD 屏幕能够显示出来,我们还需要打开 LCD 的背光灯。背光灯的控制由 KEYBORAD 引脚来控制,其连接到 GPB0 引脚上。
    在这里插入图片描述

    	/* 3. 硬件相关的操作 */
    	/* 3.1 配置GPIO用于LCD */
    	/* LEND:GPC0 
    	 * VCLK:GPC1 
    	 * VLINE:GPC2 
    	 * VFRAME/VSYNC:GPC3 
    	 * VM:GPC4 
    	 * LCD_LPCOE/GPC5
    	 * LCD_LPCREV/GPC6
    	 * LCD_LPCREVB/GPC7
    	 * VD0~VD7:  GPC8~GPC15
    	 * VD8~VD23: GPD0~GPD15
    	 * LCD_PWREN/EINT12/GPG4
    	 * TOUT0/GPB0:KEYBOARD 背光灯
    	 */
    	gpbcon = (volatile unsigned long *)ioremap(0x56000010, 8);
    	gpbdat = gpbcon + 1;
    	gpccon = (volatile unsigned long *)ioremap(0x56000020, 4);
    	gpdcon = (volatile unsigned long *)ioremap(0x56000030, 4);
    	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 4);
    
    	*gpccon  = 0xaaaaaaaa; /* 配置为LCD引脚 */
    	*gpdcon  = 0xaaaaaaaa; /* 配置为LCD引脚 */
    	*gpbcon &= ~(3);
    	*gpbcon |= 1;
    	*gpbdat &= ~(1);		/* 输出低电平,关闭背光 */
    	
    	*gpgcon |= (3 << (4 * 2));	/* 配置为LCD_PWRDN */
    
  2. 配置 LCD 控制器

    先设置 VCLK 时钟,再设置 LCD 显色模式。并初始化 LCD 屏幕为关闭状态。

     	/* 3.2 根据LCD手册设置LCD控制器 */
    	/* 3.2.1 时序设置 */
    	lcd_regs = (struct s3c2440_lcd_regs *)ioremap(0X4D000000, sizeof(struct s3c2440_lcd_regs));
    	/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2]
    	 * 			  10MHz = 100MHz / [(CLKVAL + 1) x 2] 
    	 *            ==> CLKVAL = 4
    	 * bit[6:5] : 11 = TFT LCD panel
    	 * bit[4:1] : 1100 = 16 bpp for TFT
    	 * bit[0]	: 1 = Enable the video output and the LCD control signal.
    	 */
    	lcd_regs->lcdcon1 &= ~(0x7fff);	/* 清零 */
    	lcd_regs->lcdcon1 |= ((4 << 8) | (3 << 5) | (0x0c << 1));
    

    接着设置控制时序和设置时序极性

    	/* LCDCON2:
    	 * VBPD [31:24]    	: 1   = 2 Hsync - 1 
    	 * LINEVAL [23:14] 	: 271 = 272 - 1
    	 * VFPD [13:6]		: 1   = 2 Hsync - 1
    	 * VSPW [5:0]		: 9   = 10Hsync - 1
    	 * LCDCON3:
    	 * HBPD [25:19]		: 1	  = 2CLK - 1
    	 * HOZVAL [18:8]	: 479 = 480 - 1
    	 * HFPD [7:0]		: 1   = 2CLK - 1
    	 * LCDCON4:
    	 * HSPW [7:0]		: 40  = 41CLK - 1
    	 **/
    	lcd_regs->lcdcon2 = ((1 << 24) | (271 << 14) | (1 << 6) | (9));
    	lcd_regs->lcdcon3 = ((1 << 19) | (479 << 8) | (1));
    	lcd_regs->lcdcon4 = 40;
    	/* 3.2.2 极性设置 */
    	/* BPP24BL[12]	: 0 = LSB valid
    	 * FRM565[11] 	: 1 = 5:6:5 Format
    	 * INVVCLK[10]	: 0 = The video data is fetched at VCLK falling edge
    	 * INVVLINE[9]	: 1 = Inverted. This bit indicates the VLINE/HSYNC pulse polarity.
    	 * INVVFRAME[8]	: 1 = Inverted. This bit indicates the VFRAME/VSYNC pulse polarity.
    	 * INVVD[7]		: 0 = Normal.   This bit indicates the VD (video data) pulse polarity.
    	 * INVVDEN[6]	: 0 = Normal.   This bit indicates the VDEN signal polarity.
    	 * INVPWREN[5]	: 0 = Normal.   This bit indicates the PWREN signal polarity.
    	 * INVLEND[4]	: 0 = Normal.   This bit indicates the LEND signal polarity.
    	 * PWREN[3]		: 0 = Disable PWREN signal. LCD_PWREN output signal enable/disable.
    	 * BSWP [1]		: 0 = Swap Disable. Byte swap control bit.
    	 * HWSWP[0]		: 1 = Swap Enable.  Half-Word swap control bit.
    	 **/
    	lcd_regs->lcdcon5 = ((1 << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (1));
    
  3. 然后还需要设置 LCD 的显存,是的 LCDCDMA 可以访问到我们的显存地址。

    	/* 3.3 分配显存(Frame buffer),并告知LCD控制器 */
    	s3c2440_info->screen_base = dma_alloc_writecombine(NULL, s3c2440_info->fix.smem_len, 
    										(u32*)&s3c2440_info->fix.smem_start, GFP_KERNEL);
    	lcd_regs->lcdsaddr1 = ((s3c2440_info->fix.smem_start >> 1) & ~(3 << 30));
    	lcd_regs->lcdsaddr2 = ((s3c2440_info->fix.smem_start + 
                                s3c2440_info->fix.smem_len) >> 1) & 0x1fffff;
    	lcd_regs->lcdsaddr3 = (FRAME_X_RES * FRAME_PIXEL/16); /* 一行的长度,单位是半字,两字节 */
    

    dma_alloc_writecombine 的原型如下:

    /**
     * dma_alloc_writecombine - allocate writecombining memory for DMA
     * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
     * @size: required memory size
     * @handle: bus-specific DMA address
     *
     * Allocate some uncached, buffered memory for a device for
     * performing DMA.  This function allocates pages, and will
     * return the CPU-viewed address, and sets @handle to be the
     * device-viewed address.
     */
    void * dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
    

    返回值为显存的虚拟地址,我们正好设置在 screen_base 中。同时还会设置 &s3c2440_info->fix.smem_start 指向显存的物理地址。

设置完 S3C2440 芯片相关的寄存器配置后,我们同时也完善了 fb_info 的设置,接着就可以将其注册进 framebuffer 中了

	/* 4. 注册 */
	ret = register_framebuffer(s3c2440_info);
	if (ret < 0) {
		printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
		goto free_memory;
	}

最后是启动 LCD

	/* 5. 启动LCD */
	lcd_regs->lcdcon1 |= (1 << 0);	/* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1 << 3);	/* 使能LCD本身 */
	*gpbdat |= 1;			/* 输出高电平,开启背光 */
	
	return 0;
	
free_memory:
	framebuffer_release(s3c2440_info);
	return ret;
}

4.2 出口函数

完成注销工作

static void __exit s3c2440_lcd_exit(void)
{
    /* 卸载驱动程序 */
	lcd_regs->lcdcon1 &= ~1;	/* 关LCD */
	*gpbdat &= ~1;				/* 关背光 */
	unregister_framebuffer(s3c2440_info);
	dma_free_writecombine(NULL, s3c2440_info->fix.smem_len, 
		s3c2440_info->screen_base, s3c2440_info->fix.smem_start);
	iounmap(lcd_regs);
	iounmap(gpgcon);
	iounmap(gpdcon);
	iounmap(gpccon);
	iounmap(gpbcon);
	framebuffer_release(s3c2440_info);
}

5. 测试

ubuntu

首先去掉 Linux 内核中原来的驱动程序

make menuconfig	# 配置内核
-> Device Drivers
	-> Graphics support
	<M> S3C2410 LCD framebuffer support # 设置成模块

重新编译内核,并且编译所有的模块:

make uImage
make modules

然后将 cfbcopyarea.ko、cfbfillrect.ko、cfbimgblt.ko 三个模块拷贝到 nfs 目录下:

cp drivers/video/cfbcopyarea.ko drivers/video/cfbfillrect.ko drivers/video/cfbimgblt.ko ~/nfs_rootfs/

开发板

将新编译的 uImage 下载到 30000000 这个地址上,然后引导 uboot 启动新的内核。

usb 1 30000000
bootm 30000000

启动内核后,挂载 ubuntu 的 nfs_rootfs,并且加载模块:

mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.137.3:/home/binwatson/nfs_rootfs /mnt
cd /mnt
insmod cfbcopyarea.ko                             
isnmod cfbfillrect.ko 
insmod cfbimgblt.ko
insmod s3c2440_lcd.ko

然后设置输出到屏幕

echo hello > /dev/tty1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值