3、调色板
image-20210718175642065
- 临时调色板寄存器TPAL
如果要输出一帧单色的图像,可以在TPAL寄存器中设定这个颜色值,然后使能TPAL寄存器,这种方法可以避免修改整个调色板或帧缓冲区。
TPAL寄存器格式:
功能 | 位 | 说明 |
---|---|---|
TPALEN | [24] | 调色板寄存器使能位,0禁止,1使能; |
TPALVAL | [23:0] | 颜色值;TPALVAL[23:16]:红色TPALVAL[15:8]:绿色TPALVAL[7:0]:蓝色 |
注意:临时调色板寄存器TPAL可以用在任何显示模式下,并非只能用在8BPP模式下。
4、编写驱动
4.1 lcd.c
- 搭建整体框架
可参考内核自带的相关lcd驱动(drivers/video/),添加头文件:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/err.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 <linux/cpufreq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>
static struct fb_info *s3c_lcd;
static int lcd_init(void)
{
/* 1. 分配一个fb_info */
s3c_lcd = framebuffer_alloc(0, NULL);
/* 2. 设置 */
/* 2.1 设置固定参数 */
/* 2.2 设置可变参数 */
/* 2.3 设置操作函数 */
/* 2.4 设置其它内容 */
/* 3. 硬件相关的操作 */
/* 3.1 配置GPIO用于LCD */
/* 3.2 根据LCD手册设置LCD控制器,例如VCLK频率等 */
/* 3.3 分配显存(frambuffer),并将地址告诉LCD控制器 */
/* 4. 注册 */
register_framebuffer(s3c_lcd);
return 0;
}
static void lcd_exit(void)
{
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
-
入口函数lcd_init()
- 1、分配一个fb_info
s3c_lcd = framebuffer_alloc(0, NULL);
- 2、 设置
- 设置固定参数——fb_fix_screeninfo结构体
image-20210718213023752
/* 2.1 设置固定的参数 */ strcpy(s3c_lcd->fix.id, "mylcd"); s3c_lcd->fix.smem_len = 480*272*16/8; //屏幕分辨率480X272,16bpp/pix s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; //屏幕类型 s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* 真彩TFT */ s3c_lcd->fix.line_length = 480*2; //一行需要的存储长度=480像素X2字节
- 设置可变参数
image-20210718222829528
/* 2.2 设置可变参数 */
s3c_lcd->var.xres = 480; //X方向的分辨率
s3c_lcd->var.yres = 272; //y方向的分辨率
s3c_lcd->var.xres_virtual = 480; //X方向虚拟的分辨率
s3c_lcd->var.yres_virtual = 272; //y方向的虚拟分辨率
s3c_lcd->var.bits_per_pixel = 16; //每个像素用多少位表示
/* RGB:565 */
s3c_lcd->var.red.offset = 11; //从第11位开始
s3c_lcd->var.red.length = 5; //占5个位
s3c_lcd->var.red.msb_right = 0; //数据在offset的右边吗?默认为0,表示在左边(高位方向)。可以不需设置
s3c_lcd->var.green.offset = 5; //从第5位开始
s3c_lcd->var.green.length = 6;
s3c_lcd->var.blue.offset = 0; //从第0位开始
s3c_lcd->var.blue.length = 5;
s3c_lcd->var.activate = FB_ACTIVATE_NOW; //不明白,暂用默认值
- 设置操作函数——fbops
s3c_lcd->fbops = &s3c_lcdfb_ops;
- 在函数外定义fb_ops结构体:
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,
};
- 其它的设置
s3c_lcd->pseudo_palette = pseudo_palette; //调色板数组地址
s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd->screen_size = 480*272*16/8;
- 3、硬件相关的操作
- 配置GPIO用于LCD
image-20210718230408435
image-20210718231335391
通过原理图可知,所有使用到的引脚均要配置。然后查看原理图,找到各引脚对应的IO端口:
image-20210718230823866
image-20210718231849600
首先在函数外定义用到的IO口的寄存器指针变量:
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;
然后在函数体内映射地址:
/*配置GPIO用于LCD*/
//即使你写了仅映射4个字节,系统也还是会映射至少1页(4KB)
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] */
/* GPB0设置为输出引脚 */
*gpbcon &= ~(3);
*gpbcon |= 1;
*gpbdat &= ~1; /* 先输出低电平,使背光电源关闭 */
*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN(LCD本身电源) */
-
根据LCD手册设置LCD控制器,例如VCLK频率等
**首先**,查看S3C2440芯片手册,并设置LCD controller章节中的控制寄存器。为了方便引用,先定义一个全局结构体(lcd_regs),内容就是各寄存器的地址。即:
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;
};
**然后**,再定义一个指向该类型结构体的指针`lcd_regs`:
static volatile struct lcd_regs* lcd_regs; //所有指向寄存器的地址必须是volatile修饰的
**最后**,到`lcd.c`函数中进行地址映射,而后根据LCD数据手册设置LCD控制寄存器:
为了便于大家查看,将这3幅图重新放在这边:
LCD时间参数
LCD时间序列
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
//1.设置LCDCON1寄存器
/* CLKVAL => bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2],
* VCLK取值查看LCD手册3.5.1节的Clock cycle
* 9MHz(Typ) = 100MHz / [(CLKVAL+1) x 2]
* CLKVAL = 4(在此取整数4)
* MMODE => bit[7]:取默认值
* PRNMODE => bit[6:5]: 0b11 (TFT LCD panel)
* BPPmode => bit[4:1]: 0b1100(16 bpp for TFT)
* ENVID => bit[0] : 0b0 (先暂时禁止,需要时打开.)
*/
lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);
//2.设置LCDCON2-4寄存器
#if 1
/* 垂直方向的时间参数
* VBPD => bit[31:24]: 1, VSYNC之后再过多长时间才能发出第1行数据
* LINEVAL => bit[23:14]: 271, 所以LINEVAL=272-1=271
* VFPD => bit[13:6] : 1, 发出最后一行数据之后,再过多长时间才发出VSYNC,所以VFPD=2-1=1
* VSPW => bit[5:0] : 9, VSYNC信号的脉冲宽度,VSPW=10-1=9
*/
lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9);
/* 水平方向的时间参数
* HBPD => bit[25:19]: 1, VSYNC之后再过多长时间才能发出第1行数,HBPD=2-1
* HOZVAL => bit[18:8]: 479, HOZVAL=480-1=479
* HFPD => bit[7:0] : 1 , 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC,HFPD=2-1=1
*/
lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1);
/* 水平方向的同步信号
* HSPW => bit[7:0]: 40, HSYNC信号的脉冲宽度, HSPW=41-1=40
*/
lcd_regs->lcdcon4 = 40;
#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 = 根据LCD手册,其在下降沿取数据
* bit[9] : 1 = HSYNC信号要反转(对比S3C2440手册与LCD手册的时序图)
* bit[8] : 1 = VSYNC信号要反转,
* bit[7] : 0 = INVVD不用反转(数据引脚低电平表示数据1)
* bit[6] : 0 = VDEN不用反转(对比S3C2440手册与LCD手册的时序图)
* bit[5] : 0 = INVPWREN不用反转(电源使能开关高电平有效)
* bit[3] : 0 = PWREN信号输出使能(暂时先不使能它,到后面设置完后再打开)
* bit[1:0] : 01,内存数据和像素点对应关系,00表示D[31:0]的高8位对应Pix1,
* 低8位对应Pix2,01表示D[31:0]的高8位对应Pix2,低8位对应Pix1
* S3C2440手册第413页
*/
lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
- 分配显存(frambuffer),并将地址告诉LCD控制器 ,最后启动LCD
/* screen_base:显存的虚拟地址;smem_start:显存的物理地址。*/
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
/* Frame Buffer 的起始地址
* LCDBANK => bit[29:21]: ,对应视频缓存区开始地址的A[30:22]位,(4MB地址对齐)
* LCDBASEU => bit[20:0]: ,对应视频缓存区开始地址的A[21:1]位,
*/
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
/* Frame Buffer 的结束地址
* LCDBASEL => bit[20:0]: ,对应视频缓存区结束地址的A[21:1]位,
*/
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
/* Frame Buffer 的有效显示区的宽度(半字,即2字节为单位)
* OFFSIZE => bit[21:11]: ,不懂,取默认值
* PAGEWIDTH => bit[10:0]: ,一行的长度(单位: 2字节)
*/
lcd_regs->lcdsaddr3 = (480*16/16);
/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0); /* 使能ENVID信号,表示传输数据 */
lcd_regs->lcdcon5 |= (1<<3); /* 使能PWREN信号 */
*gpbdat |= 1; /* 输出高电平, 使能背光 */
- 4、注册
/* 4. 注册 */
register_framebuffer(s3c_lcd);
- 出口函数lcd_exit()
static void lcd_exit(void)
{
unregister_framebuffer(s3c_lcd); //注销fb
lcd_regs->lcdcon1 &= ~(1<<0); /* 停止向LCD发送数据 */
lcd_regs->lcdcon5 &= ~(1<<3); /* 关闭PWREN信号 */
*gpbdat &= ~1; /* 关闭背光 */
//dma_free_writecombine(设备,内存长度,虚拟起始地址,起始物理地址)
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); //释放fb
}