LCD驱动的硬件编写

根据上一篇文章 分析框架列出大致编写过程

框架分析
在这里插入图片描述

2设置

查看 fb_info怎么定义
在这里插入图片描述
进入 fb.h查看定义在这里插入图片描述

2.1从中列出固定参数

在这里插入图片描述
设置fix结构体里的参数
在这里插入图片描述
a. char id[16]; /* identification string eg “TT Builtin” / 名字
在这里插入图片描述
b. unsigned long smem_start; /
Start of frame buffer mem /
/
(physical address) / 显存设置留到后面
c. __u32 smem_len; /
Length of frame buffer mem /显存长度
查看lcd的手册
在这里插入图片描述
写出 显存长度为分辨率
rgb /字节 = 24032016/8
在这里插入图片描述
d. u32 type; /* see FB_TYPE* / 根据注释查看这个宏
在这里插入图片描述
看不懂,用默认值,一般默认值支持大部分lcd
在这里插入图片描述
e. __u32 visual; /
see FB_VISUAL
* */ 可视图像
查看宏
在这里插入图片描述
单色 和真彩色 选择真彩色
在这里插入图片描述
e.
在这里插入图片描述
什么摇动 看不懂设为0

f.__u32 line_length; /* length of a line in bytes / 一行的固定长度大小(单位bytes)
一行是240个像素
16位的rgb(一个像素是8字节)
就是 240*2
在这里插入图片描述

2.2设置fb_info可变信息

从结构体中找到可变信息
在这里插入图片描述
进入进行配置
在这里插入图片描述
a. __u32 xres; /* visible resolution * x方向的分辨率 240
__u32 yres; y的分辨率320
_u32 xres_virtual; /* virtual resolution / 虚拟分辨率设置为和硬件一样就好
__u32 yres_virtual;
在这里插入图片描述
b. __u32 bits_per_pixel; /
guess what / 每个像素用多少位
为什么注释是让我猜猜 注释这么有意思的嘛
由于rgb为16 就是16位
在这里插入图片描述
c. 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 */ 透明度不用了

由于rgb里面是565 所以偏移值(offset)是11位 占据位是5位
s3c_lcd->var.red.offset = 11;
s3c_lcd->var.red.length = 5;
在这里插入图片描述

2.3设置操作函数

在这里插入图片描述
很多驱动都有就拿过来用
在这里插入图片描述

2.4其他设置

a. screen_size = 24032416/8; //显存的大小

调色板

在内存中 分配一块 调色板 从显存里面取出数据 放进调色板调色 颜色加载进LCD中
在这里插入图片描述
直接引用别人的代码吧
在这里插入图片描述
在这里插入图片描述

硬件相关的操作

在这里插入图片描述

配置GPIO用于LCD
3.1 配置GPIO用于LCD

查看原理图
在这里插入图片描述
在这里插入图片描述
设置所用到的引脚
在这里插入图片描述
用 ioremap 映射 arm32位=4 字节 GPxCON占用4字节,GPxDAT占用4字节 映射8字节
gpbdat 这里的+1 是4字节(加了一个指针长度)
在这里插入图片描述
设置引脚的功能
在这里插入图片描述

3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等

定义一个结构体 放控制lcd的寄存器
在这里插入图片描述
定义这个结构体的指针
在这里插入图片描述
ioremap(起始地址,大小)这个结构体
在这里插入图片描述
a.查看cpu的使用手册 lcd屏幕的时钟设置(CLKVAL)
在这里插入图片描述
从内核的输出信息查看 HCLK 的参数
shell 里面输入 dmesg
在这里插入图片描述
哦 原来是100兆 = HCLK (100MHz)
根据lcd手册上面写 时钟最小周期是100ns 纳秒
在这里插入图片描述
所以 VCLK = 100ns = 10兆 = 10 MHz
根据LCD手册上面的时钟值设置 VCLK = HCLK / [(CLKVAL+1) x 2] 算出 CLKVLA =4
在这里插入图片描述
b.用 TFT LCD 屏幕
在这里插入图片描述
c.设置像素用16位来表示
在这里插入图片描述
d.设置LCD的使能
在这里插入图片描述
进行a,b,c ,d 的设置
在这里插入图片描述

时间时序设置

从cpu的手册找出LCD的控制器2(LCDCON2)

在这里插入图片描述
有几个参数需要设置 搜索里面的参数 如VSPW 一帧的图片
在这里插入图片描述
在这里插入图片描述
有四个参数需要设置 VSPW VPBD LINEVAL VFPD 打开LCD 手册 查看如何设置
找到LCD的时序图
在这里插入图片描述
从时序图里面的 时间参数 在LCD手册里面找到典型值
在这里插入图片描述
在时钟图上面标出 脉冲宽度平时是高电平 有效的时候是低电平
在这里插入图片描述
另一张图片的是反的 要区分出来

垂直方向时间参数设置

a. VSYNC设置
VSYNC 一个周期是327+1 而有脉冲周期是1 从两张图对比 VSWP+1 = 1 VSWP=0
在这里插入图片描述
b. LINEVAL设置
一个垂直信号的周期 取决于有多少行 2440 有320行 = LINEVAL+1 LINEVAL = 319
在这里插入图片描述
c.VFPD设置
当到最后一行时候 过多长时间才重新开始到第一行
VFPD+1 = T2 - T5 =2 ** VFPD=1**
在这里插入图片描述
d.VSPW 设置
当垂直脉冲消失 过多长时间 才发出第一行的信号
VSPW+1 = T0 - T2 = 4 VSPW=3
在这里插入图片描述
整理完后 配置寄存器
在这里插入图片描述

水平方向的时间参数设置

先在 cpu手册中找到 时间参数
在这里插入图片描述
找到LCDCON3 控制寄存器3
在这里插入图片描述
LCDCON4
在这里插入图片描述
啊 又有这么多参数要设置

再从LCD上面找到 时间参数
在这里插入图片描述
通过LCD的手册 把时间的典型值标注
在这里插入图片描述
a.HSPW设置
T7是脉冲宽度 = HSPW+1 HSPW = 4
在这里插入图片描述
b.HOZVAL设置
有240列 240 = HOZAL+1 HOZAL = 239
在这里插入图片描述
c.HFPD设置
当发出最后一行的信号过多长时间才有新的信号
HFPD +1 = T8-T11 = 11 HFPD = 10
在这里插入图片描述
d.HBPD 设置
当发出最后一行最后一个像素过多久发出
HBPD +1 = T6 - T8 - T11 = 17 HBPD = 16
在这里插入图片描述
由此设置寄存器的参数
在这里插入图片描述
在这里插入图片描述

LCD的引脚设置 控制极性

在这里插入图片描述

LCD在下降沿 取数据
在这里插入图片描述
这里设置为0 INVVCLK =0

在这里插入图片描述
INVVLINE设置
由于之前的 cpu和LCD图片的有效值是相反的
在这里插入图片描述
在这里插入图片描述
于此同时 帧同步信号 也要反转 所以INVVFRAME = 1
在这里插入图片描述
设置数据使能信号INVVDEN
在这里插入图片描述
两个信号是相同的不用反转
在这里插入图片描述
INVVDEN = 0

INVPWREN电源使能设置

判断出是高电平的有效
在这里插入图片描述
在这里插入图片描述
INVPWREN = 0

选择显存的方式
在这里插入图片描述

在这里插入图片描述

LCD分配显存

参考2440自带的代码
在这里插入图片描述
在这里插入图片描述
用dma_free_writecombine(设备,大小,物理地址,标记flag) 分配内存 之前的虚拟地址没有设备 由于返回值是虚拟地址现在设置

s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
在这里插入图片描述

设置完虚拟地址,把虚拟地址告诉寄存器

LCDSADDR1
在这里插入图片描述
寄存器的29 ~ 0 位 放 显存物理地址(s3c_lcd->fix.smem_start)的30 ~ 1 位
显存地址右移一位 再与上 取反的(3左移30位 )
在这里插入图片描述
LCDSADDR2
在这里插入图片描述
对於单列扫描 寄存器20 ~ 0 位的值等于结束地址的 21 ~ 1 位
结束地址 = 开始地址+显存大小
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
在这里插入图片描述
LCDSADDR3
在这里插入图片描述
PAGEWIDTH设置
一行占据的大小(单位半字节)
一行240 像素16位 半字节16位 所以是 240*16/16

最后启动 LCD的电源
在这里插入图片描述

设置出口函数

在这里插入图片描述

完整代码

#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 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_ops s3c_lcdfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};


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;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];


/* from pxafb.c */
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;
}

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

	/* 2. 设置 */
	/* 2.1 设置固定的参数 */
	strcpy(s3c_lcd->fix.id, "mylcd");
	s3c_lcd->fix.smem_len = 240*320*16/8;
	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
	s3c_lcd->fix.line_length = 240*2;
	
	/* 2.2 设置可变的参数 */
	s3c_lcd->var.xres           = 240;
	s3c_lcd->var.yres           = 320;
	s3c_lcd->var.xres_virtual   = 240;
	s3c_lcd->var.yres_virtual   = 320;
	s3c_lcd->var.bits_per_pixel = 16;

	/* RGB:565 */
	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_lcdfb_ops;
	
	/* 2.4 其他的设置 */
	s3c_lcd->pseudo_palette = pseudo_palette;
	//s3c_lcd->screen_base  = ;  /* 显存的虚拟地址 */ 
	s3c_lcd->screen_size   = 240*324*16/8;

	/* 3. 硬件相关的操作 */
	/* 3.1 配置GPIO用于LCD */
	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 */
	
	/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
	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);

#if 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  = (3<<24) | (319<<14) | (1<<6) | (0<<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 = (16<<19) | (239<<8) | (10<<0);

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

#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 = 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控制器 */
	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*16/16);  /* 一行的长度(单位: 2字节) */	
	
	//s3c_lcd->fix.smem_start = xxx;  /* 显存的物理地址 */
	/* 启动LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 输出高电平, 使能背光 */		

	/* 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(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");



makefile 源码

KERN_DIR = /work/system/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= lcd.o

进行编译

1mak emenuconfig 进入内核去掉原来的驱动程序

设备驱动
在这里插入图片描述
图形支持
在这里插入图片描述
把原来的LCD驱动设置为M 因为我们自己的驱动程序里面 有三个函数 用到原来的驱动文件
在这里插入图片描述

编译模块 把上面的三个函数对应的文件编译为.ko 加载进去

make uImage

在这里插入图片描述
这时候 uImage 已经去掉了原来的驱动 可以拷贝到 windos里面
在这里插入图片描述
进行烧写 这时候 发现 控制台能正确输入输出 但是 LCD没有亮
由于要得到 三个.ko文件 make modules

编译自己的 lcd.ko

用nfs 挂载开发版
mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.31.51:/work/nfs_root /mnt
装载自己的 lcd.ko文件
在这里插入图片描述
这时候 发现 之前的三个驱动文件 没有拿过来
进入 内核的driver里面
在这里插入图片描述
拷贝出来
在这里插入图片描述
在开发版上面装载这三个驱动
在这里插入图片描述
再装载 LCD的驱动
在这里插入图片描述
这时候 开发板也亮了

测试

echo hello > /dev/tty1
输出文字
在这里插入图片描述
随便把文件放进显存中 所以会花屏
看见lcd花屏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章