LCD驱动--二 写lcd驱动 2.硬件相关操作

博客围绕LCD驱动入口函数展开,在上节完成部分步骤后,介绍硬件相关操作,包括配置GPIO、设置LCD控制器、分配显存并告知地址,随后进行注册。接着阐述使能LCD的方法,最后提及出口函数需释放内存、取消映射和关闭LCD,还提到调色板。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步:

  •   1) 分配一个fb_info结构体: framebuffer_alloc();
      2) 设置fb_info
      3) 设置硬件相关的操作
      4) 使能LCD,并注册fb_info: register_framebuffer()
    

在上一节中已完成1,2步,分配并设置了fb_info结构体,接下来进行硬件相关操作,使能lcd,并注册fb_info。

设置硬件相关的操作

设置硬件相关的具体操作    

  1)配置LCD引脚用于lcd

  2)根据LCD手册设置LCD控制器

  3)分配显存(framebuffer),把地址告诉LCD控制器和fb_info

LCD涉及相关引脚

GPC4 -------------- VM(数据使能)
GPC2--------------- VLINE(行同步)
GPC3----------------VFRAME(帧同步)
GPC1-----------------VCLK(时钟)
GPC8~15-------------VD[0-7]
GPD0~15-------------VD[8-23]

GPB0------------------用作keyboard(背光驱动)
GPG4------------------用作LCD_PWREN

一:配置gpio用于lcd,(gpc,gpb,gpd,gpg4-lcd_pwren)

a.地址映射
gpbcon =ioremap(0x56000010,8);                          //ioremap映射的大小以页为单位,4,/8区别不大
gpbdat = gpbcon + 1;
gpccon = ioremap(0x56000020, 4);

gpdcon = ioremap(0x56000030, 4);

gpgcon = ioremap(0x56000060, 4);

b.LCD引脚配置
*gpccon = 0xAAAAAAAA;    /* GPC 管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
*gpdcon = 0xAAAAAAAA;    /* GPD 管脚用于VD[23:8] */

*gpbcon &=~(3<<0);      
*gpbcon |=(1<<0);         /* GPB0设置为输出引脚 */
*gpbdat &=~(1<<0);        /* 关闭背光 */


  *gpgcon |=(3<<8);     //GPG4用作LCD_PWREN 

二.根据LCD手册设置LCD控制器,比如VCLK的频率等
1 .lcd控制寄存器各寄存器物理地址映射
将所以lcd寄存器打包成一个结构体,直接将结构体ioremap(需注意每个寄存器是否相差4字节,若相差大则需加入保留字节,例如0x4D000028与0x4D00004C直接相差0x28=36字节,需加入长度为9 的数组 36/4=9)

  static struct lcd_regs *lcd_regs;          //定义结构体指针

/将lcd寄存器打包成一个结构体/
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 tconsel;
};

代码:
/物理地址映射/

lcd_regs = ioremap(0x4d000000, sizeof(struct lcd_regs)); //lcd控制寄存器地址映射

2.LCD各寄存器设置

  1. /* a.lcdcon1寄存器设置lcd类型,像素时钟,使能lcd信号输出等

    bit 0 :lcd信号输出使能位
    bit[1-4] :设置bbp 16bbp=0x0C
    bit[5-6] :设置lcd类型 tft lcd设置为0b11
    bit[8-17]:设置CLKVAL; VCLK查看lcd手册为10MHZ HCLK为系统分配100MHZ
    VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P14
    10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
    CLKVAL = 4

即:lcd_regs->lcdcon1 = (0x0c<<1) | (3<<5) | (4<<8);

. 2.lcdcon2 设置垂直方向各信号的时间参数

bit[0-5] :vspw VSYNC信号的脉冲宽度, LCD手册Tvp=10, 所以VSPW=10-1=9
bit[6-13] :vfpd VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
LCD手册Tvf =2 , 所以VFPD=2-1=1
bit[14-23] :垂直宽度,多少行 LCD手册Tvd=272 ,所以LINEVAL = 272-1=271
bit[24-31] :VBPD, VSYNC脉冲后再过多长时间才能发出有效的数据
LCD手册Tvb =2 , 所以VBPD=2-1=1

即:lcd_regs->lcdcon2 = (9<<0) | (1<<6) | (271<<14) | (1<<24);

. 3.lcdcon3 设置水平方向各信号的时间参数

bit[0-7] :HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
LCD手册Thf=2, 所以HFPD=2-1=1
bit[8-18] : 水平宽度,多少列(一行多少个像素) Thd=480 HOZVAL = 480-1=479
bit[19-25] :HBPD, VSYNC之后再过多长时间才能发出第1行数据
LCD手册 Thb=2 所以HBPD=2-1=1

即:lcd_regs->lcdcon3 = (1<<0 ) | (479<<8) | (1<<19);

  1. LCDCON4 水平方向的同步信号

bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册Thp=41, 所以HSPW=41-1=40

即:lcd_regs->lcdcon4 = 40;

. 5.LCDCON5各个控制信号的极性

bit0:HWSWP=1
bit1: BSWP =0 查看2440手册P413
bit2: PWREN输出0 无
bit3:LCD_PWREN 使能lcd本身 = 0,先禁止
bit6:VDEN极性 =0 不用翻转
bit8:1 = VSYNC信号要反转,即低电平有效
bit9:1 = HSYNC信号要反转,即低电平有效
bit10:=0 在VCLK的下降沿读取数据
bit11:1 16bbp时 RGB使用565格式

即:lcd_regs->lcdcon5 =(1<<0) | (1<<8) | (1<<9) |(1<<11);*/

/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
	
/*a.lcd控制寄存器地址映射*/
lcd_regs = ioremap(0x4d000000, sizeof(struct lcd_regs));   //lcd控制寄存器地址映射

lcd_regs->lcdcon1 = (0x0c<<1) | (3<<5) | (4<<8);
lcd_regs->lcdcon2 = (9<<0) | (1<<6) | (271<<14) | (1<<24);
lcd_regs->lcdcon3 = (1<<0 ) | (479<<8) | (1<<19);
lcd_regs->lcdcon4 = 40;
lcd_regs->lcdcon5 =(1<<0) | (1<<8) | (1<<9) |(1<<11);

三:分配显存(framebuffer),把地址告诉LCD控制器和fb_info
分配显存:
设置好了 LCD 控制器后,就会自动从显存里取一个像素的值通过 VD0~VD23 发送
出像素数据到 LCD 屏上去。然后再取下一个,周而复始,取到显存中最后一个
时(显存就那么大)就再到显存开始处取。
故而要分配显存。这个显存要让它 物理地址 连续(因为 LCD 控制器没有那么智能,所以要
连续。)。分配时不能用 kmolloc(),要用专门的函数来分配这个块显存dma_alloc_writecombine。

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);  //分配DMA缓存区给显存

 //  函数功能:动态分配DMA内存,同时可以得到分配内存的虚拟地址和物理地址 
//返回值为:申请到的DMA缓冲区的虚拟地址的首地址,若为NULL,表示分配失败,则需要使用dma_free_writecombine()释放内存,避免内存泄漏
//参数如下: 

//*dev:指针,这里填0,表示这个申请的缓冲区里没有内容

//size:分配的地址大小(字节单位)

//*handle:申请到的物理起始地址放置位置

//gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
    //GFP_ATOMIC    用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
    //GFP_KERNEL    内核内存的正常分配. 可能睡眠.
    //GFP_USER      用来为用户空间页来分配内存; 它可能睡眠.    

显存地址告诉LCD控制器

  1. a.LCDSADDR1:帧内存起始地址

    bit[0:20]: 对应显存起始地址A[1:21]
    bit[21:29]:对应显存起始地址A[22:30]
    就是 显存起始地址A[0]最低位和 A[31](最高位)不要
    即先将显存起始地址A右移一位,再将最高两位[30,31]不要
    即:
    lcd_regs->lcdsaddr1 = (s3c_lcd_info->fix.smem_start >> 1) & ~(3<<30);

  2. b.LCDSADDR2:帧结束地址

    bit[0:20]:对应显存结束地址A[1:21] (结束地址=起始地址+显存大小)
    同意结束地址的最低位A[0]也不要
    即:
    lcd_regs->lcdsaddr2 = ((s3c_lcd_info->fix.smem_start + s3c_lcd_info->screen_size)>>1) & 0x001fffff; //帧内存结束地址

  3. c.LCDSADDR3

bit[0:10]:PAGEWIDTH 宽度,2个字节为单位 (48016/16)
bit[11:21]:OFFSIZE偏移尺寸 (无偏移)
即: lcd_regs->lcdsaddr3 = (480
16/16);

代码:
	/*3.3.分配显存,并把地址告诉lcd控制器*/
	s3c_lcd_info->screen_base = dma_alloc_writecombine(NULL,s3c_lcd_info->screen_size,&s3c_lcd_info->fix.smem_start, GFP_KERNEL);  //分配显存,返回显存虚拟地址

	lcd_regs->lcdsaddr1 = (s3c_lcd_info->fix.smem_start >> 1) & ~(3<<30);              //帧内存起始地址
	lcd_regs->lcdsaddr2 = ((s3c_lcd_info->fix.smem_start + s3c_lcd_info->screen_size)>>1) & 0x001fffff; //帧内存结束地址
	lcd_regs->lcdsaddr3 = (480*16/16);                                              //宽度(一行的长度,单位:2字节“/16”)
	             
	//s3c_lcd->fix.smem_start = xxx;   显存的物理地址,由dma_alloc_writecombine分配

以上设置完后,就可以注册 framebuffer 了 :register_framebuffer(s3c_lcd_info);

使能LCD

开启LCD

  1. 控制LCDCON5允许PWREN信号,

  2. 然后控制LCDCON1输出PWREN信号,

  3. 输出GPB0高电平来开背光,

lcd_regs->lcdcon1 |=(1<<0); //输出PWREN信号,
lcd_regs->lcdcon5 |=(1<<3); //使能lcd本身
*gpbdat |= 1; //开启背光

出口函数

将以上分配的内存释放,地址去掉映射,关闭LCD等

static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd_info);
	*gpbdat &=~(1<<0);        /* 关闭背光 */
	lcd_regs->lcdcon1 &= ~(1<<0);           //关lcd
	dma_free_writecombine(NULL, s3c_lcd_info->screen_size, s3c_lcd_info->screen_base,s3c_lcd_info->fix.smem_start);
	iounmap(lcd_regs);
	iounmap(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	framebuffer_release(s3c_lcd_info);
}

调色板

static u32 pseudo_palette[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 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值