本文比较长,一文讲解完lcd的知识,非常深入的讲解了lcd,开发lcd驱动。工作很多年后,很多文章是近期写的,以前记录在别的文档,所以比起大学期间,和前几年的理解,现在的理解,就是降维打击,以前很多东西学过,开发过,都不记得了,现在有空来记录一下,有些太过核心的,没有公开,私密。
目录
1、lcd接口,显示原理
2、lcd时序图
3、lcd各个时钟计算公式
4、soc寄存器设置,lcd时序设置
5、编写函数画像素点,显示画图原理
6、frambufer框架
7、新内核,带dts,frambufer框架
一、lcd接口,显示原理
在裸机篇,讲过了,如何控制x210的cpu寄存器,控制soc输出 lcd时序,可以看前面的文章。
链接地址:https://blog.csdn.net/rjszcb/article/details/141072923
LCD屏,常见的接口有(RGB, LVDS, MIPI, TTL , VGA),关于他们之间的差异,可以百度。
无论什么接口,都是转换成ttl电平,只是传输时,为了信号不衰减,转换成了mipi等等。
无论什么接口,lcd显示器面板,都是下面这种图,3个灯rgb(红绿蓝)组成一个像素点
RGB接口
这个rgb888,32位的,有些是16位的rgb565。
二、LCD时序图
VSYNC (Vertical Synchronous):帧同步信号
HSYNC (Horizontal Synchronous):行同步信号
VDEN(Video Display enable):数据有效信号
VCLK(Video Clock):像素时钟,一个时钟周期显示一个像素。(需用户配置)
VD(Video Data):像素数据,一般2个字节或者3个字节表示一个像素的值。
LEND(Line End):行结束信号。
HOZVAL(Horizontal Value):LCD屏幕的列数。(需用户配置)
LINEVAL(Line Value):LCD屏幕的行数。(需用户配置)
VSPW(Vertical Sync Pulse Width):VSYNC信号的宽度。(需用户配置)
VBPD(Vertical Back Porch Delay):VSYNC同步信号下降沿后无效行数。(需用户配置)
VFPD(Vertical Front Porch Delay):VSYNC同步信号上升沿前无效行数。(需用户配置)
HSPW(Horizontal Sync Pulse Width):HSYNC信号的宽度。(需用户配置)
HBPD(Horizontal Back Porch Delay):HSYNC同步信号下降沿后无效像素点个数。(需用户配置)
HFPD(Horizontal Front Porch Delay):HSYNC同步信号上升沿前无效像素点个数。(需用户配置)
三、soc控制lcd显示的示意图
只有设置好刷新率,时序,应用层往显存地址,写入一组数据,soc控制器一秒30帧自动刷新,就可以显示图像了。
lcd厂商,会给一份文档,关于时序的配置
来看下一份dts关于配置时序的设置。可以看下下面一个配置lcd的时序配置
ccx1k/1000 = fps 即刷新率
clock-frequency-khz = (hactive + hfront + hback + hsync) * (vactive + vfront + vback + vsync) * fps / 1000
30HZ就是 ccx1k,ccx1k/1000 = fps 即刷新率,fps 就是30帧,也就是屏幕的刷新率是30帧/s,
clock-frequency-khz就是时钟频率,74MHZ,
根据这个公式,就可以计算出各个时序
clock-frequency-khz = (hactive + hfront + hback + hsync) * (vactive + vfront + vback + vsync) * fps / 1000
hactive 就是水平分辨率
vactive 就是垂直分辨率
根据实际情况,分配前尖,后尖等时序,如果屏幕图像往左偏,往右偏,往上偏,往下偏,调整四个参数就行。
要使LCD控制器工作必须配置这些寄存器:LCDCON1,LCDCON2,LCDCON3,LCDCON4,LCDCON5,LCDSADDR1,LCDSADDR2,LCDSADDR3。
LCDCON1寄存器
CLKVAL=0x4,
PNRMODE=0x3;
BBPMODE=0x0c;
ENVID=0;
LCD手册
Dclk=9MHz~15MHz,
HCLK=100MHz,
Dclk=VCLK=HCLK/[(CLKVAL+1)x2]=100/((4+1)*2)=10MHz
所以
LCDCON1=(CLKVAL<<8) | (PNRMODE<<5) | (BBPMODE<<1) | ENVID;
LCDCON2寄存器
根据LCD手册有
VBPD_480272=(2-1),
LINEVAL_TFT_480272=(272-1),
VFPD_480272=(2-1),
VSPW_480272=(10-1)
LCDCON2 = (VBPD_480272<<24) | (LINEVAL_TFT_480272<<14) | (VFPD_480272<<6) | (VSPW_480272);
LCDCON3寄存器
根据LCD手册有
HBPD_480272=(2-1),
HOZVAL_TFT_480272=(480-1),
HFPD_480272=(2-1)
所以这个寄存器配置如下
LCDCON3 = (HBPD_480272<<19) | (HOZVAL_TFT_480272<<8) | (HFPD_480272);
LCDCON4寄存器
LCDCON4 = HSPW_480272;
根据LCD用户手册HSPW_480272=(41-1)
LCDCON5寄存器
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | (HWSWP<<1);
根据LCD手册
FORMAT8BPP_565=1,
HSYNC_INV=1,
VSYNC_INV=1,
HWSWP=1
以上主要是根据LCD手册设置了LCD控制器的时序,下面我们要设置LCD的显示缓存地址
LCDSADDR1寄存器
#define LOWER21BITS(n) ((n) & 0x1fffff)
#define LCDFRAMEBUFFER 0x30400000 //设置了一款ddr的内存地址
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2寄存器
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ (LINEVAL_TFT_480272 + 1)*(HOZVAL_TFT_480272+1)*2)>>1);
其中
LINEVAL_TFT_480272=(272-1),
HOZVAL_TFT_480272=(480-1)
LCDSADDR3寄存器
LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_480272 *2 / 2);
四、设置soc寄存器,控制LCD初始化程序
这个设置是mini2440的设置,不是x210的,只是举例,所有soc几乎都是这么设置,但是寄存器可能不一样
初始化用于LCD的引脚 grb,各个控制脚的配置,输出模式
void Lcd_Port_Init(void)
{
GPCUP = 0xffffffff; // 禁止内部上拉
GPCCON = 0xaaaaaaaa; // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
GPDUP = 0xffffffff; // 禁止内部上拉
GPDCON = 0xaaaaaaaa; // GPIO管脚用于VD[23:8]
GPBCON &= ~(GPB0_MSK); // Power enable pin
GPBCON |= GPB0_out;
GPBDAT &= ~(1<<0); // Power off
}
初始化soc LCD控制器
void Tft_Lcd_Init(void)
{
/*
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
* 选择LCD类型: TFT LCD
* 设置显示模式: 16BPP
* 先禁止LCD信号输出
* 2. LCDCON2/3/4:
* 设置控制信号的时间参数
* 设置分辨率,即行数及列数
* 现在,可以根据公式计算出显示器的频率:
* 当HCLK=100MHz时,
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
* {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
* {2x(CLKVAL+1)/(HCLK)}]
* = 60Hz
* 3. LCDCON5:
* 设置显示模式为16BPP时的数据格式: 5:6:5
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
* 半字(2字节)交换使能
*/
LCDCON1 = (CLKVAL_TFT_480272<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (VBPD_480272<<24) | (LINEVAL_TFT_480272<<14) | \
(VFPD_480272<<6) | (VSPW_480272);
LCDCON3 = (HBPD_480272<<19) | (HOZVAL_TFT_480272<<8) | (HFPD_480272);
LCDCON4 = HSPW_480272;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(HWSWP<<1);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下:
* |----PAGEWIDTH----|
* y/x 0 1 2 479
* 0 rgb rgb rgb ... rgb
* 1 rgb rgb rgb ... rgb
* 1. LCDSADDR1:
* 设置LCDBANK、LCDBASEU
* 2. LCDSADDR2:
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
* 3. LCDSADDR3:
* OFFSIZE等于0,PAGEWIDTH等于(480*2/2)
*/
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
(LINEVAL_TFT_480272+1)*(HOZVAL_TFT_480272+1)*2)>>1);
LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_480272*2/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 16;
xsize = 480;
ysize = 272;
}
void Lcd_PowerEnable(int invpwren, int pwren)
{
GPGCON = (GPGCON & (~(3<<8))) | (3<<8); // GPG4用作LCD_PWREN
GPGUP = (GPGUP & (~(1<<4))) | (1<<4); // 禁止内部上拉
LCDCON5 = (LCDCON5 & (~(1<<5))) | (invpwren<<5); // 设置LCD_PWREN的极性: 正常/反转
LCDCON5 = (LCDCON5 & (~(1<<3))) | (pwren<<3); // 设置是否输出LCD_PWREN
}
/*
* 设置LCD控制器是否输出信号
* 输入参数:
* onoff: 0 : 关闭 1 : 打开
*/
void Lcd_EnvidOnOff(int onoff)
{
if (onoff == 1)
{
LCDCON1 |= 1; // ENVID ON
}
else
{
LCDCON1 &= 0x3fffe; // ENVID Off
}
}
#include "lcddrv.h"
#include "framebuffer.h"
int main()
{
Lcd_Port_Init(); // 设置LCD引脚
Tft_Lcd_Init(); // 初始化LCD控制器
Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源
Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号
Lcd_Palette8Bit_Init(); // 初始化调色板
ClearScr(0x0);
while (1){;}
return 0;
}
四、点亮像素点,显示图片
裸机
链接地址:https://blog.csdn.net/rjszcb/article/details/141072923
应用层,显示图片
原文链接:https://blog.csdn.net/rjszcb/article/details/142423668
五、老内核,不带dts,framebuffer框架
上面介绍了裸机部分,不带framebuffer框架,设置soc寄存器的,下面介绍将这些设置,加入到frambuffer框架。
从驱动来看,fb是一个典型的字符设备,而且创建了一个类/sys/class/graphics
Framebuffer驱动程序框架,概述:分为上下两层:
fbmem.c:
承上启下
实现、注册file_operations结构体
把APP的调用向下转发到具体的硬件驱动程序
xxx_fb.c:硬件相关的驱动程序
实现、注册fb_info结构体
实现硬件操作
Framebuffer驱动程序框架主要分为两个层次:核心层和硬件相关层。下面是这两个层次的简要概述和关键组件:
1. 核心层(fbmem.c)
核心层主要负责处理与VFS(虚拟文件系统)的交互,以及将应用程序的调用转发到具体的硬件驱动程序。这一层通常包含以下组件:
file_operations结构体:定义了针对Framebuffer的特殊文件操作,例如open(), release(), ioctl(), mmap()等。这些操作处理来自用户空间的请求。
帧缓冲区管理:管理帧缓冲区的内存分配和释放。这可能包括请求内存区域、处理内存映射等。
事件处理:处理与Framebuffer相关的各种事件,如模式变化、显示关闭和打开等。
数据转发:将应用程序的调用(如读写操作)转发到具体的硬件驱动程序。
2. 硬件相关层(xxx_fb.c)
硬件相关层专注于与特定硬件的交互。这一层通常包含以下组件:
fb_info结构体:包含了Framebuffer的所有必要信息,如屏幕大小、颜色深度、帧缓冲区的物理地址等。
硬件操作:实现对硬件的具体操作,包括但不限于:
初始化和配置LCD控制器。
配置屏幕的分辨率、颜色深度等参数。
控制屏幕的开启和关闭。
数据传输:实现数据从CPU内存到Framebuffer的传输,以及可能的DMA(直接内存访问)操作。
中断处理:如果硬件支持中断,注册并实现中断服务例程,以处理例如垂直同步等事件。
注册和注销:在驱动程序加载和卸载时,注册和注销fb_info结构体。
先来看示意图。
上面这些示意图,别人画的比较好,我就不画了,由图可以看到,framebuffer驱动框架,分为核心层,设备驱动层
帧缓冲设备在Linux中也可以看做是一个完整的子系统,大体由fbmem.c和xxxfb.c组成。向上给应用程序提供完善的设备文件操作接口(即对FrameBuffer设备进行read、write、ioctl等操作),接口在Linux提供的fbmem.c文件中实现;向下提供了硬件操作的接口,只是这些接口Linux并没有提供实现,因为这要根据具体的LCD控制器硬件进行设置,所以这就是我们要做的事情了(即xxxfb.c部分的实现)。
1、LCD驱动编写基础函数
在讲之前,先简单记住几个知识点,重要的数据结构,再来深入分析驱动框架
1、dma_alloc_wc
该函数定义在include/linux/dma-mapping.h:
该函数用于申请一段DMA缓冲区,分配出来的内存会禁止cache缓存(因为DMA传输不需要CPU)。
返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败
static inline void *dma_alloc_wc(struct device *dev, size_t size,dma_addr_t *dma_addr, gfp_t gfp)
参数如下:
dev:设备指针;
size:分配的地址大小(字节单位);
dma_addr:申请到的物理起始地址;
gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠;
GFP_KERNEL 内核内存的正常分配. 可能睡眠;
GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.;
2、dma_free_wc
该函数用于释放DMA缓冲区,参数和dma_alloc_wc一样,释放内存,避免内存泄漏。
static inline void dma_free_wc(struct device *dev, size_t size,void *cpu_addr, dma_addr_t dma_addr)
3、 framebuffer_alloc
动态申请一个fb_info结构体
struct fb_info *framebuffer_alloc(size_t size, struct device *dev)
参数如下:
size:额外的内存大小;
dev:设备指针,用于初始化fb_info->device成员;
2、LCD驱动编写步骤
2.1 入口函数
在驱动入口函数中实现:
(1) 动态分配fb_info结构体;
(2) 设置fb_info
(2.1).设置固定的参数fb_info->fix;
(2.2) 设置可变的参数fb_info->var;
(2.3) 设置操作函数fb_info->fbops;
(2.4) 设置fb_info其它的成员;
(3) 设置硬件相关的操作
(3.1) 配置LCD引脚:设置GPIO端口C和GPIO端口D用于LCD;
(3.2) 根据LCD手册设置LCD控制器时序参数;
(3.3) 分配显存(framebuffer),把显存地址告诉LCD控制器和fb_info;
(4) 开启LCD,并注册fb_info
(4.1) 开启LCD
控制LCDCON5允许PWREN信号,开启背光;
控制LCDCON1使能LCD;
(4.2) 注册fb_info;
2.2 出口函数
在驱动出口函数中实现:
(1) 卸载内核中的fb_info;
(2) 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址;
(3) 释放DMA缓存地址dma_free_wc;
(4) 释放注册的fb_info;
4、LCD驱动编写
下面这种是把裸机的代码,加上frambufer的框架,来写的驱动,不是标准的驱动,所以可以看出,驱动编写,真的不难,就是裸机套上框架,但是呢,提供lcd的芯片商,和soc,驱动是不会这么写,会套上标准的gpio,request申请gpio,来设置寄存器,这个我在gpio驱动讲了。
帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数,最大允许有32个FrameBuffer,定义在/include/linux/fb.h中的FB_MAX,对应于文件系统下/dev/fb%d设备文件。
/dev/fb0就是LCD对应的设备文件,/dev/fb0是个字符设备,因此肯定有file_operations操作集,fb的file_operations操作集定义在drivers/video/fbdev/core/fbmem.c文件中
Framebuffer 相关的重要数据结构:
1、fb_info
从Framebuffer设备驱动程序结构看,该驱动主要跟fb_info结构体有关,该结构体记录了Framebuffer设备的全部信息,包括设备的设置参数、状态以及对底层硬件操作的函数指针。在Linux中,每一个Framebuffer设备都必须对应一个fb_info,fb_info在/linux/fb.h中的定义如下:(只列出重要的一些)
struct fb_info {
atomic_t count;
int node; ///次设备号
int flags;
struct mutex lock; //用于open、release、ioctrl功能的锁
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; //LCD 可变参数
struct fb_fix_screeninfo fix; //LCD 固定参数
struct fb_monspecs monspecs; /* Current Monitor specs *///LCD 显示器标准
struct work_struct queue; //帧缓冲事件队列
struct fb_pixmap pixmap; ///图像硬件 mapper
struct fb_pixmap sprite; //光标硬件 mapper
struct fb_cmap cmap; //当前颜色表
struct list_head modelist; //模式列表
struct fb_videomode *mode; //当前的显示模式
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;///对应的背光设备
/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];///背光调整
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops;///---->对底层硬件设备操作的函数指针
struct device *device; /* This is the parent *////父设备节点
struct device *dev; /* This is this fb device *////当前的帧缓冲设备
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting *///图块Blitting ?
#endif
char __iomem *screen_base; /* Virtual address *///虚拟地址
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ ///LCD IO映射的虚拟内存大小
void *pseudo_palette; /* Fake palette of 16 colors *///伪16色 颜色表
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend *////LCD 挂起或复位的状态
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
bool skip_vt_switch; /* no VT switch on suspend/resume required */
};
其中,fb_var_screeninfo和fb_fix_screeninfo两个结构体跟LCD硬件属性相关,fb_var_screeninfo代表可修改的LCD显示参数,如分辨率和像素比特数;fb_fix_screeninfo代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。另外一个非常重要的成员是fb_ops,其是LCD底层硬件操作接口集。
fb_ops硬件操作接口集包含很多接口,如设置可变参数fb_set_par、设置颜色寄存器fb_setcolreg、清屏接口fb_blank、画位图接口fb_imageblit、内存映射fb_mmap等等。
fb_info结构体在调用register_framebuffer之前完成初始化。一般来说,LCD设备属于平台设备,其初始化是在平台设备驱动的probe接口完成。而LCD设备所涉及的硬件初始化则在平台设备初始化中完成。
2、fb_fix_screeninfo
结构体主要记录用户不可以修改的控制器的参数,该结构体的定义如下:
struct fb_fix_screeninfo {
char id[16]; //字符串形式的标识符
unsigned long smem_start; /fb 缓存的开始位置
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem *///fb 缓存的长度
__u32 type; /* see FB_TYPE_* *////看FB_TYPE_* -->
__u32 type_aux; /* Interleave for interleaved Planes *///分界
__u32 visual; ///看FB_VISUAL_* -->
__u16 xpanstep; //如果没有硬件panning就赋值为0
__u16 ypanstep; //如果没有硬件panning就赋值为0
__u16 ywrapstep; //如果没有硬件ywrap就赋值为0
__u32 line_length; //一行的字节数
unsigned long mmio_start; //内存映射 IO的开始位置
/* (physical address) */
__u32 mmio_len; //内存映射 IO的长度
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 capabilities; /* see FB_CAP_* *///功能 ---FB_CAP_FOURCC--- Device supports FOURCC-based formats
__u16 reserved[2]; //为以后的兼容性保留
};
其中参数含义如下:
id:唯一标识符;
smem_start :framebuffer缓冲区物理起始位置(一般是显示控制器DMA起始地址);
smem_len :framebuffer缓冲区的的长度,单位为字节;
type :lcd类型,默认FB_TYPE_PACKED_PIXELS即可,可选参数如下;
#define FB_TYPE_PACKED_PIXELS 0 /* Packed Pixels */
#define FB_TYPE_PLANES 1 /* Non interleaved planes */
#define FB_TYPE_INTERLEAVED_PLANES 2 /* Interleaved planes */
#define FB_TYPE_TEXT 3 /* Text/attributes */
#define FB_TYPE_VGA_PLANES 4 /* EGA/VGA planes */
#define FB_TYPE_FOURCC 5 /* Type identified by a V4L2 FOURCC */
type_aux:附加类型,默认0即可;
visual:颜色设置,常用参数如下:
#define FB_VISUAL_MONO01 0 /* Monochr. 1=Black 0=White 单侧 0白色 1黑色 */
#define FB_VISUAL_MONO10 1 /* Monochr. 1=White 0=Black 单色 0黑色 1白色 */
#define FB_VISUAL_TRUECOLOR 2 /* True color 真彩 */
#define FB_VISUAL_PSEUDOCOLOR 3 /* Pseudo color (like atari) 伪彩 */
#define FB_VISUAL_DIRECTCOLOR 4 /* Direct color 直彩 */
#define FB_VISUAL_STATIC_PSEUDOCOLOR 5 /* Pseudo color readonly 只读伪彩 */
#define FB_VISUAL_FOURCC 6 /* Visual identified by a V4L2 FOURCC */
xpanstep:如果没有硬件panning赋值0;
ypanstep:如果没有硬件panning赋值0;
ywrapstep:若果没有硬件ywarp赋值0;
line_length:一行所占的字节数,例:(RGB565)240*320,那么这里就等于240*16/8;
mmio_start:内存映射IO的起始地址,用于应用层直接访问寄存器,可以不需要;
mmio_len:内存映射IO的长度,可以不需要;
accel:指明使用的芯片,用于硬件加速,默认FB_ACCEL_NONE即可,可选参数如下;:
#define FB_ACCEL_NONE 0 /* no hardware accelerator */
#define FB_ACCEL_ATARIBLITT 1 /* Atari Blitter */
#define FB_ACCEL_AMIGABLITT 2 /* Amiga Blitter */
#define FB_ACCEL_S3_TRIO64 3 /* Cybervision64 (S3 Trio64) */
#define FB_ACCEL_NCR_77C32BLT 4 /* RetinaZ3 (NCR 77C32BLT) */
......
capabilities:查看FB_CAP_;
reserved:为将来的兼容保留位;
3、fb_var_screeninfo
结构体主要记录用户可以修改的控制器的参数,比如屏幕的分辨率和每个像素的比特数等,该结构体定义如下:
struct fb_var_screeninfo { ///显示屏信息
__u32 xres; /* visible resolution*//可视区域,一行有多少个像素点
__u32 yres; ///可视区域,一列有多少个像素点
__u32 xres_virtual; /* virtual resolution*//虚拟区域,一行有多少个像素点,简单的意思就是内存中定义的区间是比较大的
__u32 yres_virtual;虚拟区域,一列有多少个像素点
__u32 xoffset; //虚拟到可见屏幕之间的行偏移
__u32 yoffset; /* resolution *//虚拟到可见屏幕之间的列偏移
__u32 bits_per_pixel; /* guess what*/ 每个像素的 bit 数,这个参数不需要自己配置,而是通过上层在调用 checkvar 函数传递 bpp 的时候赋值的
__u32 grayscale; /* 0 = color, 1 = grayscale,*////等于零就成黑白 (灰度)
/* >1 = FOURCC */
// 通过 pixel per bpp 来设定 red green 和 blue 的位置; pixel per bpp 可以通过 ioctl 设定
struct fb_bitfield red; //fb缓存的R位域
struct fb_bitfield green; /* else only length is significant *//fb缓存的G位域
struct fb_bitfield blue; //fb缓存的B位域
struct fb_bitfield transp; /* transparency *//透明度
__u32 nonstd; /* != 0 Non standard pixel format *///如果nonstd 不等于0,非标准的像素格式
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; //内存中的图像高度
__u32 width; //内存中的图像宽度
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags *////加速标志
/* Timing: All values in pixclocks, except pixclock (of course) */
///时序,这些部分就是显示器的显示方法了,和具体的液晶显示屏有关,在驱动中一般放在 具体液晶屏的配置文件,现在有dts,放在dts里了
__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_* *////---->看 FB_SYNC_*
__u32 vmode; /* see FB_VMODE_* *////---->看 FB_VMODE_*
__u32 rotate; /* angle we rotate counter clockwise */
__u32 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};
其中参数含义如下:
xres:行分辨率
yres:列分辨率
xres_virtual:行虚拟分辨率,设置和硬件一样即可;
yres_virtual:列虚拟分辨率,设置和硬件一样即可;
xoffset:行偏移,设置为0即可;
yoffset:列偏移,设置为0即可;
bits_per_pixel:每个像素用多少位,对于s3c2440不支持18位,只支持16位;
grayscale:灰度值,默认即可;
red:RGB:565对于R的offset为从最低位(右起)偏移11位,占5bit;
green:RGB:565对于G的offset为从最低位(右起)偏移5位,占6bit;
blue:RGB:565对于B的offset为从最低位(右起)偏移0位,占5bit;
transp:透明度,默认即可;
nonstd:0标准像素格式,默认即可;
activate:默认即可;
height:LCD物理高度,单位mm;
width:LCD物理宽度,单位mm;
accel_flags:默认即可,过时参数;
pixclock:像素时钟,单位皮秒;
left_margin:行切换,从同步到绘图之间的延迟;
right_margin:行切换,从绘图到同步之间的延迟;
upper_margin:帧切换,从同步到绘图之间的延迟;
lower_margin:帧切换,从绘图到同步之间的延迟;
hsync_len:水平同步的长度;
vsync_len:垂直同步的长度;
sync:参考FB_SYNC_*;
vmode:参考FB_BMODE_*,默认即可;
rotate::时针旋转的角度;
colorspace:色彩空间;
reserved:保留值;
4、fb_ops
结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:
注: fb_ops结构与file_operations 结构不同,fb_ops是底层操作的抽象,而file_operations是提供给上层系统调用的接口,可以直接调用.
1 struct fb_ops {
2 /* open/release and usage marking */
3 struct module *owner;
4 int (*fb_open)(struct fb_info *info, int user);
5 int (*fb_release)(struct fb_info *info, int user);
/*对于非线性布局的/常规内存映射无法工作的帧缓冲设备需要*/
10 ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
11 size_t count, loff_t *ppos);
12 ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
13 size_t count, loff_t *ppos);
15 /* checks var and eventually tweaks it to something supported,
16 * DO NOT MODIFY PAR */
17 int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);///检查可变参数并进行设置
19 /* set the video mode according to info->var */
20 int (*fb_set_par)(struct fb_info *info);///根据设置的值进行更新,使之有效
22 /* set color register */
23 int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green, 设置颜色寄存器
24 unsigned blue, unsigned transp, struct fb_info *info);
26 /* set color registers in batch */
27 int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);29
30 int (*fb_blank)(int blank, struct fb_info *info);///显示空白
32 /*pan显示*/
33 int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);
36 void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);///矩形填充
38 void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);///复制数据
40 void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);///图形填充
42 /* 绘制光标 */
43 int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);
45 /* 旋转显示 */
46 void (*fb_rotate)(struct fb_info *info, int angle);
48 /* 等待blit空闲 */
49 int (*fb_sync)(struct fb_info *info);
51 /* fb 特定的 ioctl */
52 int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
53 unsigned long arg);
55 /* 处理32bit compat ioctl (optional) */
56 int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
57 unsigned long arg);
59 /* fb 特定的 mmap */
60 int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
62 /* get capability given var */
63 void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
64 struct fb_var_screeninfo *var);
66 /* teardown any resources to do with this framebuffer */
67 void (*fb_destroy)(struct fb_info *info);
69 /* called at KDB enter and leave time to prepare the console */
70 int (*fb_debug_enter)(struct fb_info *info);
71 int (*fb_debug_leave)(struct fb_info *info);
72 };
5、fb_cmap
设备独立的 colormap 信息,可以通过 ioctl 的 FBIOGETCMAP 和 FBIOPUTCMAP 命令设置 colormap;
struct fb_cmap { //颜色映射表
__u32 start; /* First entry *////第一个入口
__u32 len; /* Number of entries *///入口的数字
__u16 *red; /* Red values *///红色
__u16 *green; ///绿色
__u16 *blue; ///蓝色
__u16 *transp; //透明度,允许为空
};
这些结构相互之间的关系如下所示:非常的清晰明朗。
LCD驱动编写
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
/* LCD T35参数设定 */
#define LCD_WIDTH 240 /* LCD面板的行宽 */
#define LCD_HEIGHT 320 /* LCD面板的列宽 */
#define VSPW 1 /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define VBPD 1 /* 垂直同步周期后的无效行数 */
#define LINEVAL (LCD_HEIGHT-1) /* LCD的垂直宽度-1 */
#define VFPD 1 /* 垂直同步周期前的的无效行数 */
#define CLKVAL 7 /* VCLK = HCLK / [(CLKVAL + 1) × 2] */
#define HSPW 9 /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define HBPD 19 /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define HOZVAL (LCD_WIDTH-1) /* LCD的水平宽度-1 */
#define HFPD 9 /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */
/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;
/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;
/* 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 lpcsel;
};
static volatile struct lcd_regs* lcd_regs;
//分配一个fb_info结构 初始化fb_info
s3c_lcd = framebuffer_alloc(0,0);
if (!s3c_lcd)
return -ENOMEM;
//设置fb_info(设置固定的参数fb_info->fix)
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer缓冲区的大小 每个像素两个字节
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.type_aux = 0;
s3c_lcd->fix.xpanstep = 0;
s3c_lcd->fix.ypanstep = 0;
s3c_lcd->fix.ywrapstep = 0;
s3c_lcd->fix.accel = FB_ACCEL_NONE;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色
s3c_lcd->fix.line_length = LCD_WIDTH * 2; // LCD一行所占字节数
//设置fb_info(设置可变的参数fb_info->var)
s3c_lcd->var.xres = LCD_WIDTH; // 行分辨率
s3c_lcd->var.yres = LCD_HEIGHT; // 列分辨率
s3c_lcd->var.xres_virtual = LCD_WIDTH; // 行虚拟分辨率
s3c_lcd->var.yres_virtual = LCD_HEIGHT; // 列虚拟分辨率
s3c_lcd->var.xoffset = 0; //虚拟到可见屏幕之间的行偏移
s3c_lcd->var.yoffset = 0; //虚拟到可见屏幕之间的行偏移
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.nonstd = 0;
s3c_lcd->var.activate = FB_ACTIVATE_NOW; // 使设置的值立即生效
s3c_lcd->var.accel_flags = 0;
s3c_lcd->var.vmode = FB_VMODE_NONINTERLACED;
// 设置fb_info(设置操作函数fb_info->fbops)调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/*
* 将内核单色使用bf表示
* @param chan:单色 内核中的单色都是16位
* @param bf:颜色位信息
*/
static inline unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
/*
* 调色板操作函数
* @param regno: 调色板数组元素索引
*/
static int s3c2440fb_setcolreg(unsigned regno,unsigned red, unsigned green, unsigned blue,unsigned transp, struct fb_info *info)
{
unsigned int val;
//调色板数组不能大于16
if (regno > 16)
return 1;
/* 小于16位,进行转换 */
u32 *pal = info->pseudo_palette;
/* 用red,green,blue三个颜色值构造出16色数据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);
/* 放到调色板数组中 */
pseudo_palette[regno] = val;
return 0;
}
fb_info操作函数fbops
static struct fb_ops s3c2440fb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c2440fb_setcolreg, // 调色板操作函数 设置调色板fb_info-> pseudo_palette
.fb_fillrect = cfb_fillrect, // 填充矩形 函数在drivers/video/fbdev/core/cfbfillrect.c中定义
.fb_copyarea = cfb_copyarea, // 复制数据 函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
.fb_imageblit = cfb_imageblit, // 绘制图形 函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};
设置操作函数
s3c_lcd->fbops = &s3c2440fb_ops;
/* 2.4 其他设置 */
s3c_lcd->flags = FBINFO_FLAG_DEFAULT;
s3c_lcd->pseudo_palette = pseudo_palette; // 保存调色板数组
s3c_lcd->screen_size = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer缓冲区的大小
// 时钟相关,获取lcd时钟
lcd_clk = clk_get(NULL, "lcd");
if (IS_ERR(lcd_clk)) {
printk("failed to get lcd clock source\n");
return PTR_ERR(lcd_clk);
}
clk_prepare_enable(lcd_clk); // 时钟使能
printk("got and enabled clock\n");
硬件相关操作 配置GPIO口用于LCD 接口,用于输出数据,时序,这个要看数据手册,用到哪个gpio,寄存器
gpcup = ioremap(0x56000028,4);
gpccon = ioremap(0x56000020,4);
gpdup = ioremap(0x56000038,4);
gpdcon = ioremap(0x56000030,4);
gpgcon = ioremap(0x56000060,12);
gpgdat = gpgcon + 1;
gpgup = gpgdat + 1;
// GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
*gpcup = 0xffffffff;
*gpccon = 0xaaaaaaaa;
// GPIO管脚用于VD[23:8]
*gpdup = 0xffffffff;
*gpdcon = 0xaaaaaaaa;
/* Pull - up disable */
*gpgup |= (1 << 4);
// 设置GPG4引脚为LCD_PWREN模式
*gpgcon |= (3 << 8);
设置lcd控制寄存器,在裸机部分讲过了,如何设置的,挪过来就行。所以驱动就是加了框架的裸机
根据LCD手册设置LCD控制器, 比如VCLK的频率等
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));
/* [17:8] CLKVAL
* [6:5] PNRMODE;选择显示模式
* 00 = 4 位双扫描显示模式 01 = 4 位单扫描显示模式(STN)
* 10 = 8 位单扫描显示模式 11 = TFT LCD 面板
* [4:1] BPPMODE 选择BPP(位每像素)模式 1100 = TFT 的16 BPP
* [0] ENVID LCD 视频输出和逻辑使能/禁止。
* 0 = 禁止视频输出和LCD 控制信号 1 = 允许视频输出和LCD 控制信号
*/
lcd_regs->lcdcon1 = (CLKVAL<<8)| (3<<5) | (0xC<<1); /* 16 bpp for TFT */
/* [31:24] VBPD:帧同步信号的后肩
* [23:14] LINEVAL:LCD面板的垂直尺寸
* [13:6] VFPD:帧同步信号的前肩
* [5:0] VSPW:同步信号的脉宽
*/
lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);
/* [25:19] HBPD: 行同步信号的后肩
* [18:8] HOZVAL: LCD面板的水平尺寸
* [7:0] HFPD: 行同步信号的前肩
*/
lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
lcd_regs->lcdcon4 = (HSPW);
/* [11] FRM565: 此位选择16 BPP 输出视频数据的格式 0 = 5:5:5:1 格式 1= 5:6:5 格式
* [10] STN/TFT: 此位控制VCLK 有效沿的极性
* [9] INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性 0 = 正常 1 = 反转
* [8] INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
* VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
* [0] HWSWP: STN/TFT:半字节交换控制位 0 = 交换禁止 1 = 交换使能
*/
lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));
/* 关闭PWREN信号输出 */
lcd_regs->lcdcon1 &= ~(1<<0);
/* 禁止PWREN信号 */
lcd_regs->lcdcon5 &=~(1<<3);
/* 第一位设置为1 选择输出分片率类型0:320 * 240 1:240*320 */
lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;
//设置硬件相关的操作分配显存,裸机也讲过
分配显存(framebuffer), 并把地址告诉LCD控制器 */
ret = s3c2440fb_map_video_memory(s3c_lcd);
if (ret) {
printk("failed to allocate video RAM: %d\n", ret);
// todo 这里应该进行资源释放,我这里就不释放了
return -ENOMEM;
}
/* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
* [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
*/
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
/* 存放帧结束地址[21:1] */
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
/* [21:11] OFFSIZE:表示虚拟屏偏移尺寸 即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
* [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
*/
lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;
s3c2440fb_map_video_memory这个函数原型可以看下,就是那倒设置的地址,然后申请dma映射地址。这样我们不用操作了,dma直接数据过去了。
static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
dma_addr_t map_dma;
unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);
printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);
// 第一个参数不能为空 否则会抛异常
fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,GFP_KERNEL);
if (fbinfo->screen_base) {
/* prevent initial garbage on screen */
printk("map_video_memory: clear %px:%08x\n",fbinfo->screen_base, map_size);
memset(fbinfo->screen_base, 0x00, map_size);
fbinfo->fix.smem_start = map_dma;
printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
}
else
{
printk("map_video_memory fail\n");
}
return fbinfo->screen_base ? 0 : -ENOMEM;
}
//开启LCD ,控制LCDCON5允许PWREN信号 */
lcd_regs->lcdcon5 |= (1 << 3);
/* 控制LCDCON1 LCD使能 */
lcd_regs->lcdcon1 |= (1<<0);
/* 输出高电平, 使能背光 */
*gpgdat |= 1<<4;
printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);
//注册驱动到内核
register_framebuffer(s3c_lcd);
完整的驱动注册过程
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
/* LCD T35参数设定 */
#define LCD_WIDTH 240 /* LCD面板的行宽 */
#define LCD_HEIGHT 320 /* LCD面板的列宽 */
#define VSPW 1 /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define VBPD 1 /* 垂直同步周期后的无效行数 */
#define LINEVAL (LCD_HEIGHT-1) /* LCD的垂直宽度-1 */
#define VFPD 1 /* 垂直同步周期前的的无效行数 */
#define CLKVAL 7 /* VCLK = HCLK / [(CLKVAL + 1) × 2] */
#define HSPW 9 /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define HBPD 19 /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define HOZVAL (LCD_WIDTH-1) /* LCD的水平宽度-1 */
#define HFPD 9 /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */
/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;
/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;
/* 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 lpcsel;
};
static volatile struct lcd_regs* lcd_regs;
/*
* 将内核单色使用bf表示
* @param chan:单色 内核中的单色都是16位
* @param bf:颜色位信息
*/
static inline unsigned int chan_to_field(unsigned int chan,struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
/*
* 调色板操作函数
* @param regno: 调色板数组元素索引
*/
static int s3c2440fb_setcolreg(unsigned regno,unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
unsigned int val;
//调色板数组不能大于15
if (regno >= 16)
return 1;
/* 用red,green,blue三个颜色值构造出16色数据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);
/* 放到调色板数组中 */
pseudo_palette[regno] = val;
return 0;
}
#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))
/*platform设备 */
static struct platform_device s3c_device_lcd = {
.name = "s3c2440-lcd",
.id = -1,
.num_resources = 0,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
/*
* s3c2440fb_map_video_memory():
* Allocates the DRAM memory for the frame buffer. This buffer is
* remapped into a non-cached, non-buffered, memory region to
* allow palette and pixel writes to occur without flushing the
* cache. Once this area is remapped, all virtual memory
* access to the video memory should occur at the new region.
*/
static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
dma_addr_t map_dma;
unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);
printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);
// 第一个参数不能为空 否则会抛异常
fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,GFP_KERNEL);
if (fbinfo->screen_base) {
/* prevent initial garbage on screen */
printk("map_video_memory: clear %px:%08x\n",fbinfo->screen_base, map_size);
memset(fbinfo->screen_base, 0x00, map_size);
fbinfo->fix.smem_start = map_dma;
printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
}
else
{
printk("map_video_memory fail\n");
}
return fbinfo->screen_base ? 0 : -ENOMEM;
}
/*
* fb_info操作函数fbops
*/
static struct fb_ops s3c2440fb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c2440fb_setcolreg, // 调色板操作函数 设置调色板fb_info-> pseudo_palette
.fb_fillrect = cfb_fillrect, // 填充矩形 函数在drivers/video/fbdev/core/cfbfillrect.c中定义
.fb_copyarea = cfb_copyarea, // 复制数据 函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
.fb_imageblit = cfb_imageblit, // 绘制图形 函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};
/*
* lcd驱动模块入口
*/
static int lcd_init(void)
{
int ret;
printk("lcd device registered\n");
/* 1. 初始化fb_info */
s3c_lcd = framebuffer_alloc(0,NULL);
if (!s3c_lcd)
{
return -ENOMEM;
}
printk("s3c_lcd=%px\n", s3c_lcd);
/* 2. 设置fb_info */
/* 2.1设置固定参数 */
strcpy(s3c_lcd->fix.id, "mylcd");
s3c_lcd->fix.smem_len = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer缓冲区的大小 每个像素两个字节
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.type_aux = 0;
s3c_lcd->fix.xpanstep = 0;
s3c_lcd->fix.ypanstep = 0;
s3c_lcd->fix.ywrapstep = 0;
s3c_lcd->fix.accel = FB_ACCEL_NONE;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; //真彩色
s3c_lcd->fix.line_length = LCD_WIDTH * 2; // LCD一行所占字节数
/* 2.2 设置可变参数 */
s3c_lcd->var.xres = LCD_WIDTH; // 行分辨率
s3c_lcd->var.yres = LCD_HEIGHT; // 列分辨率
s3c_lcd->var.xres_virtual = LCD_WIDTH; // 行虚拟分辨率
s3c_lcd->var.yres_virtual = LCD_HEIGHT; // 列虚拟分辨率
s3c_lcd->var.xoffset = 0; //虚拟到可见屏幕之间的行偏移
s3c_lcd->var.yoffset = 0; //虚拟到可见屏幕之间的行偏移
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.nonstd = 0;
s3c_lcd->var.activate = FB_ACTIVATE_NOW; // 使设置的值立即生效
s3c_lcd->var.accel_flags = 0;
s3c_lcd->var.vmode = FB_VMODE_NONINTERLACED;
/* 2.3 设置操作函数 */
s3c_lcd->fbops = &s3c2440fb_ops;
/* 2.4 其他设置 */
s3c_lcd->flags = FBINFO_FLAG_DEFAULT;
s3c_lcd->pseudo_palette = pseudo_palette; // 保存调色板数组
s3c_lcd->screen_size = LCD_WIDTH * LCD_HEIGHT * 2; // framebuffer缓冲区的大小
// 时钟相关,获取lcd时钟
lcd_clk = clk_get(NULL, "lcd");
if (IS_ERR(lcd_clk)) {
printk("failed to get lcd clock source\n");
return PTR_ERR(lcd_clk);
}
clk_prepare_enable(lcd_clk); // 时钟使能
printk("got and enabled clock\n");
/* 3.硬件相关操作 */
/* 3.1 配置GPIO口用于LCD */
gpcup = ioremap(0x56000028,4);
gpccon = ioremap(0x56000020,4);
gpdup = ioremap(0x56000038,4);
gpdcon = ioremap(0x56000030,4);
gpgcon = ioremap(0x56000060,12);
gpgdat = gpgcon + 1;
gpgup = gpgdat + 1;
// GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
*gpcup = 0xffffffff;
*gpccon = 0xaaaaaaaa;
// GPIO管脚用于VD[23:8]
*gpdup = 0xffffffff;
*gpdcon = 0xaaaaaaaa;
/* Pull - up disable */
*gpgup |= (1 << 4);
// 设置GPG4引脚为LCD_PWREN模式
*gpgcon |= (3 << 8);
/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));
/* [17:8] CLKVAL
* [6:5] PNRMODE;选择显示模式
* 00 = 4 位双扫描显示模式 01 = 4 位单扫描显示模式(STN)
* 10 = 8 位单扫描显示模式 11 = TFT LCD 面板
* [4:1] BPPMODE 选择BPP(位每像素)模式 1100 = TFT 的16 BPP
* [0] ENVID LCD 视频输出和逻辑使能/禁止。
* 0 = 禁止视频输出和LCD 控制信号 1 = 允许视频输出和LCD 控制信号
*/
lcd_regs->lcdcon1 = (CLKVAL<<8)| (3<<5) | (0xC<<1); /* 16 bpp for TFT */
/* [31:24] VBPD:帧同步信号的后肩
* [23:14] LINEVAL:LCD面板的垂直尺寸
* [13:6] VFPD:帧同步信号的前肩
* [5:0] VSPW:同步信号的脉宽
*/
lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);
/* [25:19] HBPD: 行同步信号的后肩
* [18:8] HOZVAL: LCD面板的水平尺寸
* [7:0] HFPD: 行同步信号的前肩
*/
lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
lcd_regs->lcdcon4 = (HSPW);
/* [11] FRM565: 此位选择16 BPP 输出视频数据的格式 0 = 5:5:5:1 格式 1= 5:6:5 格式
* [10] STN/TFT: 此位控制VCLK 有效沿的极性
* [9] INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性 0 = 正常 1 = 反转
* [8] INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
* VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
* [0] HWSWP: STN/TFT:半字节交换控制位 0 = 交换禁止 1 = 交换使能
*/
lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));
/* 关闭PWREN信号输出 */
lcd_regs->lcdcon1 &= ~(1<<0);
/* 禁止PWREN信号 */
lcd_regs->lcdcon5 &=~(1<<3);
/* 第一位设置为1 选择输出分片率类型0:320 * 240 1:240*320 */
lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;
/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
ret = s3c2440fb_map_video_memory(s3c_lcd);
if (ret) {
printk("failed to allocate video RAM: %d\n", ret);
// todo 这里应该进行资源释放,我这里就不释放了
return -ENOMEM;
}
/* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
* [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
*/
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
/* 存放帧结束地址[21:1] */
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
/* [21:11] OFFSIZE:表示虚拟屏偏移尺寸 即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
* [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
*/
lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;
/* 4.1 开启LCD */
/* 控制LCDCON5允许PWREN信号 */
lcd_regs->lcdcon5 |= (1 << 3);
/* 控制LCDCON1 LCD使能 */
lcd_regs->lcdcon1 |= (1<<0);
/* 输出高电平, 使能背光 */
*gpgdat |= 1<<4;
printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);
/* 4.2 注册 */
register_framebuffer(s3c_lcd);
return 0;
}
/*
* lcd驱动模块出口
*/
static void __exit lcd_exit(void)
{
printk("lcd device unregistered\n");
unregister_framebuffer(s3c_lcd);
/* 禁止LCD使能 */
lcd_regs->lcdcon1 &= ~(1<<0);
/* 关闭背光 */
*gpgdat &= ~(1<<4);
dma_free_wc(&s3c_device_lcd.dev, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpcup);
iounmap(gpccon);
iounmap(gpdup);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
如果内核没有配置lcd,需要配置一下。支持frambufer
编译后,安装驱动,就可以看到这么一个设备
上面的驱动就是玩的,有利于理解,当然开发时,小项目,也可以这么玩,如果是复杂的设备,比如汽车,怎么死的都不知道,是不会这么玩的。
六、标准的frambufer驱动,带dts
在Linux中应用程序最终也是通过操作RGB LCD的显存来实现在LCD上显示字符、图片等信息。在裸机中我们可以随意的分配显存,但是在Linux系统中内存的管理很严格,显存是需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。
LCD裸机例程主要分两部分:
①、获取LCD的屏幕参数。
②、根据屏幕参数信息来初始化eLCDIF接口控制器
frambufer驱动框架,其实非常简单,就是字符设备那一套,套上几个比较复杂一点的结构体,描述设备,操控设备,甚至比i2c驱动模型还简单。
不同分辨率的LCD屏幕其eLCDIF控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们就是修改设备树。比如NXP官方的设备树已经添加了LCD设备节点,只是此节点的LCD屏幕信息是打开imx6ull.dtsi,然后找到lcdif节点内容,如下所示:
imx6ull.dtsi文件中lcdif节点内容
1 lcdif: lcdif@021c8000 {
2 compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
3 reg = <0x021c8000 0x4000>;
4 interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
5 clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
6 <&clks IMX6UL_CLK_LCDIF_APB>,
7 <&clks IMX6UL_CLK_DUMMY>;
8 clock-names = "pix", "axi", "disp_axi";
9 status = "disabled";
10 };
lcdif节点信息是所有使用I.MX6ULL芯片的板子所共有的,并不是完整的lcdif节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向imx6ull-alientek-emmc.dts中的lcdif节点添加其他的属性信息。可以看出lcdif节点的compatible属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在Linux源码中搜索这两个字符串即可找到I.MX6ULL的LCD驱动文件,这个文件为drivers/video/fbdev/mxsfb.c,mxsfb.c就是I.MX6ULL的LCD驱动文件,在此文件中找到如下内容:
示例代码platform下的LCD驱动
1362 static const struct of_device_id mxsfb_dt_ids[] = {
1363 { .compatible = "fsl,imx23-lcdif",
. data = &mxsfb_devtype[0],
},
1364 { .compatible = "fsl,imx28-lcdif",
.data = &mxsfb_devtype[1],
},
1365 { /* sentinel */ }
1366 };
......
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
.shutdown = mxsfb_shutdown,
.id_table = mxsfb_devtype,
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,
.pm = &mxsfb_pm_ops,
},
};
module_platform_driver(mxsfb_driver);
这是一个标准的platform驱动,当驱动和设备匹配以后mxsfb_probe函数就会执行。在看mxsfb_probe函数之前我们先简单了解一下Linux下Framebuffer驱动的编写流程,Linux内核将所有的Framebuffer抽象为一个叫做fb_info的结构体,fb_info结构体包含了Framebuffer设备的完整属性和操作集合,因此每一个Framebuffer设备都必须有一个fb_info。换言之就是,LCD的驱动就是构建fb_info,并且向系统注册fb_info的过程。fb_info结构体定义在include/linux/fb.h文件里面,这个结构体上面我们说过了。
fb_info结构体的成员变量很多,我们重点关注
var、
fix、
fbops、
screen_base、
screen_size
pseudo_palette
申请fb_info。
②、初始化fb_info结构体中的各个成员变量。
③、初始化eLCDIF控制器。
④、使用register_framebuffer函数向Linux内核注册初始化好的fb_info。
示例代码59.1.2.4 mxsfb_probe函数
1369 static int mxsfb_probe(struct platform_device *pdev)
1370 {
1371 const struct of_device_id *of_id =of_match_device(mxsfb_dt_ids, &pdev->dev);
1373 struct resource *res;
1374 struct mxsfb_info *host;
1375 struct fb_info *fb_info;
1376 struct pinctrl *pinctrl;
1377 int irq = platform_get_irq(pdev, 0);
1378 int gpio, ret;
1379
......
1394
1395 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1396 if (!res) {
1397 dev_err(&pdev->dev, "Cannot get memory IO resource\n");
1398 return -ENODEV;
1399 }
1400
1401 host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
1402 if (!host) {
1403 dev_err(&pdev->dev, "Failed to allocate IO resource\n");
1404 return -ENOMEM;
1405 }
1406
1407 fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
1408 if (!fb_info) {
1409 dev_err(&pdev->dev, "Failed to allocate fbdev\n");
1410 devm_kfree(&pdev->dev, host);
1411 return -ENOMEM;
1412 }
1413 host->fb_info = fb_info;
1414 fb_info->par = host;
1415
1416 ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,dev_name(&pdev->dev), host);
1418 if (ret) {
1419 dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", irq, ret);
1421 ret = -ENODEV;
1422 goto fb_release;
1423 }
1424
1425 host->base = devm_ioremap_resource(&pdev->dev, res);
1426 if (IS_ERR(host->base)) {
1427 dev_err(&pdev->dev, "ioremap failed\n");
1428 ret = PTR_ERR(host->base);
1429 goto fb_release;
1430 }
......
1461
1462 fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, GFP_KERNEL);
1464 if (!fb_info->pseudo_palette) {
1465 ret = -ENOMEM;
1466 goto fb_release;
1467 }
1468
1469 INIT_LIST_HEAD(&fb_info->modelist);
1470
1471 pm_runtime_enable(&host->pdev->dev);
1472
1473 ret = mxsfb_init_fbinfo(host);
1474 if (ret != 0)
1475 goto fb_pm_runtime_disable;
1476
1477 mxsfb_dispdrv_init(pdev, fb_info);
1478
1479 if (!host->dispdrv) {
1480 pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
1481 if (IS_ERR(pinctrl)) {
1482 ret = PTR_ERR(pinctrl);
1483 goto fb_pm_runtime_disable;
1484 }
1485 }
1486
1487 if (!host->enabled) {
1488 writel(0, host->base + LCDC_CTRL);
1489 mxsfb_set_par(fb_info);
1490 mxsfb_enable_controller(fb_info);
1491 pm_runtime_get_sync(&host->pdev->dev);
1492 }
1493
1494 ret = register_framebuffer(fb_info);
1495 if (ret != 0) {
1496 dev_err(&pdev->dev, "Failed to register framebuffer\n");
1497 goto fb_destroy;
1498 }
......
1525 return ret;
1526 }
在prob函数里会去register_framebuffer,注册到内核,这个我们以前讲过的,platform模型。在前面不带dts老内核的方式下,套上了platform模型。
第1374行,host结构体指针变量,表示I.MX6ULL的LCD的主控接口,mxsfb_info结构体是NXP定义的针对I.MX系列SOC的Framebuffer设备结构体。也就是我们前面一直说的设备结构体,此结构体包含了I.MX系列SOC的Framebuffer设备详细信息,比如时钟、eLCDIF控制器寄存器基地址、fb_info等。
第1395行,从设备树中获取eLCDIF接口控制器的寄存器首地址,设备树中lcdif节点已经设置了eLCDIF寄存器首地址为0X021C8000,因此res=0X021C8000。
第1401行,给host申请内存,host为mxsfb_info类型结构体指针。
第1407行,给fb_info申请内存,也就是申请fb_info。
第1413~1414行,设置host的fb_info成员变量为fb_info,设置fb_info的par成员变量为host。通过这一步就将前面申请的host和fb_info联系在了一起。
第1416行,申请中断,中断服务函数为mxsfb_irq_handler。
第1425行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保存到host的base成员变量。因此通过访问host的base成员即可访问I.MX6ULL的整个eLCDIF寄存器。其实在mxsfb.c中已经定义了eLCDIF各个寄存器相比于基地址的偏移值,如下所示:
示例代码59.1.2.4 eLCDIF各个寄存器偏移值
67 #define LCDC_CTRL 0x00
68 #define LCDC_CTRL1 0x10
69 #define LCDC_V4_CTRL2 0x20
70 #define LCDC_V3_TRANSFER_COUNT 0x20
71 #define LCDC_V4_TRANSFER_COUNT 0x30
......
89 #define LCDC_V4_DEBUG0 0x1d0
90 #define LCDC_V3_DEBUG0 0x1f0
mxsfb_probe函数,第1462行,给fb_info中的pseudo_palette申请内存。
第1473行,调用mxsfb_init_fbinfo函数初始化fb_info,重点是fb_info的var、fix、fbops,screen_base和screen_size。其中fbops是Framebuffer设备的操作集,NXP提供的fbops为mxsfb_ops,内容如下:
示例代码59.1.2.5 mxsfb_ops操作集合
987 static struct fb_ops mxsfb_ops = {
988 .owner = THIS_MODULE,
989 .fb_check_var = mxsfb_check_var,
990 .fb_set_par = mxsfb_set_par,
991 .fb_setcolreg = mxsfb_setcolreg,
992 .fb_ioctl = mxsfb_ioctl,
993 .fb_blank = mxsfb_blank,
994 .fb_pan_display = mxsfb_pan_display,
995 .fb_mmap = mxsfb_mmap,
996 .fb_fillrect = cfb_fillrect,
997 .fb_copyarea = cfb_copyarea,
998 .fb_imageblit = cfb_imageblit,
999 };
关于mxsfb_ops里面的各个操作函数这里就不去详解的介绍了。mxsfb_init_fbinfo函数通过调用mxsfb_init_fbinfo_dt函数从设备树中获取到LCD的各个参数信息。最后,mxsfb_init_fbinfo函数会调用mxsfb_map_videomem函数申请LCD的帧缓冲内存(也就是显存)。
第1489~1490行,设置eLCDIF控制器的相应寄存器。
第1494行,最后调用register_framebuffer函数向Linux内核注册fb_info。
mxsfb.c文件很大,还有一些其他的重要函数,比如mxsfb_remove、mxsfb_shutdown等,这里我们就简单的介绍了一下mxsfb_probe函数,至于其他的函数大家自行查阅。
LCD驱动程序编写
前面已经说了,6ULL的eLCDIF接口驱动程序NXP已经编写好了,因此LCD驱动部分我们不需要去修改。我们需要做的就是按照所使用的LCD来修改设备树。重点要注意三个地方:
①、LCD所使用的IO配置。
②、LCD屏幕节点修改,修改相应的属性值,换成我们所使用的LCD屏幕参数。
③、LCD背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息。
接下来我们依次来看一下上面这两个节点改如何去修改:
1、LCD屏幕IO配置
首先要检查一下设备树中LCD所使用的IO配置,这个其实NXP都已经给我们写好了,不需要修改,不过我们还是要看一下。打开imx6ull-alientek-emmc.dts文件,在iomuxc节点中找到如下内容:
设备树LCD IO配置
1 pinctrl_lcdif_dat: lcdifdatgrp {
2 fsl,pins = <
3 MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
4 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
5 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
6 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
7 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79
8 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79
9 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79
10 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79
11 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79
12 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79
13 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79
14 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79
15 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79
16 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79
17 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79
18 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79
19 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79
20 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79
21 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79
22 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79
23 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79
24 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79
25 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79
26 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79
27 >;
28 };
29
30 pinctrl_lcdif_ctrl: lcdifctrlgrp {
31 fsl,pins = <
32 MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
33 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
34 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
35 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
36 >;
37 pinctrl_pwm1: pwm1grp {
38 fsl,pins = <
39 MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
40 >;
41 };
第2~27行,子节点pinctrl_lcdif_dat,为RGB LCD的24根数据线配置项。
第30~36行,子节点pinctrl_lcdif_ctrl,RGB LCD的4根控制线配置项,包括CLK、ENABLE、VSYNC和HSYNC。
第37~40行,子节点pinctrl_pwm1,LCD背光PWM引脚配置项。这个引脚要根据实际情况设置,LCD的背光IO尽量和半导体厂商的官方开发板一致。
注意示例代码59.3.1中默认将LCD的电气属性都设置为0X79,这里将其都改为0X49,也就是将LCD相关IO的驱动能力改为R0/1,也就是降低LCD相关IO的驱动能力。因为前面已经说了,ALPHA开发板上的LCD接口用了三个SGM3157模拟开关,为了防止模拟开关影响到网络,
2、LCD屏幕参数节点信息修改
继续在imx6ull-alientek-emmc.dts文件中找到lcdif节点,节点内容如下所示:
lcdif节点默认信息
1 &lcdif {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的IO */
4 &pinctrl_lcdif_ctrl
5 &pinctrl_lcdif_reset>;
6 display = <&display0>;
7 status = "okay";
8
9 display0: display { /* LCD属性信息 */
10 bits-per-pixel = <16>; /* 一个像素占用几个bit */
11 bus-width = <24>; /* 总线宽度 */
12
13 display-timings {
14 native-mode = <&timing0>; /* 时序信息 */
15 timing0: timing0 {
16 clock-frequency = <9200000>; /* LCD像素时钟,单位Hz */
17 hactive = <480>; /* LCD X轴像素个数 */
18 vactive = <272>; /* LCD Y轴像素个数 */
19 hfront-porch = <8>; /* LCD hfp参数 */
20 hback-porch = <4>; /* LCD hbp参数 */
21 hsync-len = <41>; /* LCD hspw参数 */
22 vback-porch = <2>; /* LCD vbp参数 */
23 vfront-porch = <4>; /* LCD vfp参数 */
24 vsync-len = <10>; /* LCD vspw参数 */
25
26 hsync-active = <0>; /* hsync数据线极性 */
27 vsync-active = <0>; /* vsync数据线极性 */
28 de-active = <1>; /* de数据线极性 */
29 pixelclk-active = <0>; /* clk数据线先极性 */
30 };
31 };
32 };
33 };
向imx6ull.dtsi文件中的lcdif节点追加的内容,我们依次来看一下示例代码的这些属性都是写什么含义。
第3行,pinctrl-0属性,LCD所使用的IO信息,这里用到了pinctrl_lcdif_dat、pinctrl_lcdif_ctrl和pinctrl_lcdif_reset这三个IO相关的节点,前两已经讲解了。pinctrl_lcdif_reset是LCD复位IO信息节点,正点原子的I.MX6U-ALPHA开发板的LCD没有用到复位IO,因此pinctrl_lcdif_reset可以删除掉。
第6行,display属性,指定LCD属性信息所在的子节点,这里为display0,下面就是display0子节点内容。
第9~32行,display0子节点,描述LCD的参数信息,第10行的bits-per-pixel属性用于指明一个像素占用的bit数,默认为16bit。将LCD配置为RGB888模式,因此一个像素点占用24bit,bits-per-pixel属性要改为24。第11行的bus-width属性用于设置数据线宽度,因为要配置为RGB888模式,因此bus-width也要设置为24。
第13~30行,这几行非常重要!因为这几行设置了LCD的时序参数信息,NXP官方的EVK开发板使用了一个4.3寸的480*272屏幕,因此这里默认是按照NXP官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就是我们重点要修改的,需要根据自己所使用的屏幕去修改。
这里以正点原子的ATK7016(7寸1024*600)屏幕为例,将imx6ull-alientek-emmc.dts文件中的lcdif节点改为如下内容:
针对ATK7016 LCD修改后的lcdif节点信息
1 &lcdif {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的IO */
4 &pinctrl_lcdif_ctrl>;
5 display = <&display0>;
6 status = "okay";
7
8 display0: display { /* LCD属性信息 */
9 bits-per-pixel = <24>; /* 一个像素占用24bit */
10 bus-width = <24>; /* 总线宽度 */
11
12 display-timings {
13 native-mode = <&timing0>; /* 时序信息 */
14 timing0: timing0 {
15 clock-frequency = <51200000>; /* LCD像素时钟,单位Hz */
16 hactive = <1024>; /* LCD X轴像素个数 */
17 vactive = <600>; /* LCD Y轴像素个数 */
18 hfront-porch = <160>; /* LCD hfp参数 */
19 hback-porch = <140>; /* LCD hbp参数 */
20 hsync-len = <20>; /* LCD hspw参数 */
21 vback-porch = <20>; /* LCD vbp参数 */
22 vfront-porch = <12>; /* LCD vfp参数 */
23 vsync-len = <3>; /* LCD vspw参数 */
24
25 hsync-active = <0>; /* hsync数据线极性 */
26 vsync-active = <0>; /* vsync数据线极性 */
27 de-active = <1>; /* de数据线极性 */
28 pixelclk-active = <0>; /* clk数据线先极性 */
29 };
30 };
31 };
32 };
3、LCD屏幕背光节点信息
LCD接口背光控制IO连接到了I.MX6U的GPIO1_IO08引脚上,GPIO1_IO08复用为PWM1_OUT,通过PWM信号来控制LCD屏幕背光的亮度,看一下如何在设备树中添加背光节点信息。
首先是GPIO1_IO08这个IO的配置,在imx6ull-alientek-emmc.dts中找到如下内容:
GPIO1_IO08引脚配置
1 pinctrl_pwm1: pwm1grp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
4 >;
5 };
imx6ull.dtsi文件中的pwm1节点
1 pwm1: pwm@02080000 {
2 compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
3 reg = <0x02080000 0x4000>;
4 interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
5 clocks = <&clks IMX6UL_CLK_PWM1>,
6 <&clks IMX6UL_CLK_PWM1>;
7 clock-names = "ipg", "per";
8 #pwm-cells = <2>;
9 };
向pwm1节点追加的内容
1 &pwm1 {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_pwm1>;
4 status = "okay";
5 };
第3行,设置pwm1所使用的IO为pinctrl_pwm1,也就是示例代码59.3.4所定义的GPIO1_IO08这个IO。
第4行,将status设置为okay。
如果背光用的其他pwm通道,比如pwm2,那么就需要仿照示例代码59.3.6的内容,向pwm2节点追加相应的内容。pwm和相关的IO已经准备好了,但是Linux系统怎么知道PWM1_OUT就是控制LCD背光的呢?因此我们还需要一个节点来将LCD背光和PWM1_OUT连接起来。这个节点就是backlight,backlight节点描述可以参考Documentation/devicetree/indings/video/backlight/pwm-backlight.txt这个文档,此文档详细讲解了backlight节点该如何去创建,这里大概总结一下:
①、节点名称要为“backlight”。
②、节点的compatible属性值要为“pwm-backlight”,因此可以通过在Linux内核中搜索“pwm-backlight”来查找PWM背光控制驱动程序,这个驱动程序文件为drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。
③、pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1,pwm频率设置为5KHz(NXP官方推荐设置)。
④、brightness-levels属性描述亮度级别,范围为0~255,0表示PWM占空比为0%,也就是亮度最低,255表示100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。
⑤、default-brightness-level属性为默认亮度级别。
根据上述5点设置backlight节点,这个NXP已经给我们设置好了,大家在imx6ull-alientek-emmc.dts文件中找到如下内容:
backlight节点内容
1 backlight {
2 compatible = "pwm-backlight";
3 pwms = <&pwm1 0 5000000>;
4 brightness-levels = <0 4 8 16 32 64 128 255>;
5 default-brightness-level = <6>;
6 status = "okay";
7 };
第3行,设置背光使用pwm1,PWM频率为200Hz。
第4行,设置背8级背光(0~7),分别为0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一些其他的背光等级值。
第5行,设置默认背光等级为6,也就是50.19%的亮度。
关于背光的设备树节点信息就讲到这里,整个的LCD设备树节点内容我们就讲完了,按照这些节点内容配置自己的开发板即可。
关于pwm驱动,放在后面介绍。
运行测试
1、编译新的设备树
上一小节我们已经配置好了设备树,所以需要输入如下命令重新编译一下设备树:
make dtbs
等待编译生成新的imx6ull-alientek-emmc.dtb设备树文件,一会要使用新的设备树启动Linux内核。
然后就可以写应用程序测试。应用程序后面一篇文章,有写怎么使用lcd,可以去看下。
2、使能Linux logo显示
Linux内核启动的时候可以选择显示小企鹅logo,只要这个小企鹅logo显示没问题那么我们的LCD驱动基本就工作正常了。这个logo显示是要配置的,不过Linux内核一般都会默认开启logo显示,但是奔着学习的目的,我们还是来看一下如何使能Linux logo显示。打开Linux内核图形化配置界面,按下路径找到对应的配置项:
-> Device Drivers
-> Graphics support
-> Bootup logo (LOGO [=y])
-> Standard black and white Linux logo
-> Standard 16-color Linux logo
-> Standard 224-color Linux logo
3、设置LCD作为终端控制台
我们一直使用SecureCRT作为Linux开发板终端,开发板通过串口和SecureCRT进行通信。现在我们已经驱动起来LCD了,所以可以设置LCD作为终端,也就是开发板使用自己的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将LCD设置为终端控制台的方法如下:
1、设置uboot中的bootargs
重启开发板,进入Linux命令行,重新设置bootargs参数的console内容,命令如下所示:
setenv bootargs ‘console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:
/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:
off’
注意红色字体部分设置console,这里我们设置了两遍console,第一次设置console=tty1,也就是设置LCD屏幕为控制台,第二遍又设置console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个console,一个是LCD,一个是串口,大家重启开发板就会发现LCD和串口都会显示Linux启动log信息。但是此时我们还不能使用LCD作为终端进行交互,因为我们的设置还未完成。
2、修改/etc/inittab文件
打开开发板根文件系统中的/etc/inittab文件,在里面加入下面这一行:
tty1::askfirst:-/bin/sh
添加完成以后的/etc/inittab文件内容如图59.4.2.1所示:
修改完成以后保存/etc/inittab并退出,然后重启开发板,重启以后开发板LCD屏幕
4、 LCD背光调节
背光设备树节点设置了8个等级的背光调节,可以设置为0~7,我们可以通过设置背光等级来实现LCD背光亮度的调节,进入如下目录:
/sys/devices/platform/backlight/backlight/backlight
当前屏幕亮度等级为6,根据前面的分析可以,这个是50%亮度。屏幕最大亮度等级为7。如果我们要修改屏幕亮度,只需要向brightness写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为7,那么可以使用如下命令:
echo 7 > brightness
输入上述命令以后就会发现屏幕亮度增大了,如果设置brightness为0的话就会关闭LCD背光,屏幕就会熄灭。
再来讲PWM背光驱动
给这个背光控制引脚输入高电平就会点亮背光,输入低电平就会关闭背光。假如我们不断的打开和关闭背光,当速度足够快的时候就不会感觉到背光关闭这个过程了。这个正好可以使用PWM来完成,PWM全称是Pulse Width Modulation,也就是脉冲宽度调制,PWM信号如图
PWM信号有两个关键的术语:频率和占空比,频率就是开关速度,把一次开关算作一个周期,那么频率就是1秒内进行了多少次开关。占空比就是一个周期内高电平时间和低电平时间的比例,一个周期内高电平时间越长占空比就越大,反之占空比就越小。占空比用百分之表示,如果一个周期内全是低电平那么占空比 就是0%,如果一个周期内全是高电平那么占空比就是100%。
我们给LCD的背光引脚输入一个PWM信号,这样就可以通过调整占空比的方式来调整LCD背光亮度了。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度。重点就在于PWM信号的产生和占空比的控制,很幸运的是,I.MX6U提供了PWM外设,因此我们可以配置PWM外设来产生PWM信号。
打开《I.MX6ULL参考手册》的第40章“Chapter 40 Pulse Width Modulation(PWM)”,I.MX6U一共有8路PWM信号,每个PWM包含一个16位的计数器和一个4 x 16的数据FIFO
关于blacklight驱动,在kernel,找到一个驱动源码,参考一下。pwm框架,这里不写。
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/slab.h>
#include <linux/mfd/wm831x/core.h>
#include <linux/mfd/wm831x/pdata.h>
#include <linux/mfd/wm831x/regulator.h>
struct wm831x_backlight_data {
struct wm831x *wm831x;
int isink_reg;
int current_brightness;
};
static int wm831x_backlight_set(struct backlight_device *bl, int brightness)
{
struct wm831x_backlight_data *data = bl_get_data(bl);
struct wm831x *wm831x = data->wm831x;
int power_up = !data->current_brightness && brightness;
int power_down = data->current_brightness && !brightness;
int ret;
if (power_up) {
/* Enable the ISINK */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_ENA, WM831X_CS1_ENA);
if (ret < 0)
goto err;
/* Enable the DC-DC */
ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
WM831X_DC4_ENA, WM831X_DC4_ENA);
if (ret < 0)
goto err;
}
if (power_down) {
/* DCDC first */
ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
WM831X_DC4_ENA, 0);
if (ret < 0)
goto err;
/* ISINK */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_DRIVE | WM831X_CS1_ENA, 0);
if (ret < 0)
goto err;
}
/* Set the new brightness */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_ISEL_MASK, brightness);
if (ret < 0)
goto err;
if (power_up) {
/* Drive current through the ISINK */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_DRIVE, WM831X_CS1_DRIVE);
if (ret < 0)
return ret;
}
data->current_brightness = brightness;
return 0;
err:
/* If we were in the middle of a power transition always shut down
* for safety.
*/
if (power_up || power_down) {
wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
wm831x_set_bits(wm831x, data->isink_reg, WM831X_CS1_ENA, 0);
}
return ret;
}
static int wm831x_backlight_update_status(struct backlight_device *bl)
{
return wm831x_backlight_set(bl, backlight_get_brightness(bl));
}
static int wm831x_backlight_get_brightness(struct backlight_device *bl)
{
struct wm831x_backlight_data *data = bl_get_data(bl);
return data->current_brightness;
}
static const struct backlight_ops wm831x_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = wm831x_backlight_update_status,
.get_brightness = wm831x_backlight_get_brightness,
};
static int wm831x_backlight_probe(struct platform_device *pdev)
{
struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
struct wm831x_pdata *wm831x_pdata = dev_get_platdata(pdev->dev.parent);
struct wm831x_backlight_pdata *pdata;
struct wm831x_backlight_data *data;
struct backlight_device *bl;
struct backlight_properties props;
int ret, i, max_isel, isink_reg, dcdc_cfg;
/* We need platform data */
if (wm831x_pdata)
pdata = wm831x_pdata->backlight;
else
pdata = NULL;
if (!pdata) {
dev_err(&pdev->dev, "No platform data supplied\n");
return -EINVAL;
}
/* Figure out the maximum current we can use */
for (i = 0; i < WM831X_ISINK_MAX_ISEL; i++) {
if (wm831x_isinkv_values[i] > pdata->max_uA)
break;
}
if (i == 0) {
dev_err(&pdev->dev, "Invalid max_uA: %duA\n", pdata->max_uA);
return -EINVAL;
}
max_isel = i - 1;
if (pdata->max_uA != wm831x_isinkv_values[max_isel])
dev_warn(&pdev->dev,
"Maximum current is %duA not %duA as requested\n",
wm831x_isinkv_values[max_isel], pdata->max_uA);
switch (pdata->isink) {
case 1:
isink_reg = WM831X_CURRENT_SINK_1;
dcdc_cfg = 0;
break;
case 2:
isink_reg = WM831X_CURRENT_SINK_2;
dcdc_cfg = WM831X_DC4_FBSRC;
break;
default:
dev_err(&pdev->dev, "Invalid ISINK %d\n", pdata->isink);
return -EINVAL;
}
/* Configure the ISINK to use for feedback */
ret = wm831x_reg_unlock(wm831x);
if (ret < 0)
return ret;
ret = wm831x_set_bits(wm831x, WM831X_DC4_CONTROL, WM831X_DC4_FBSRC,
dcdc_cfg);
wm831x_reg_lock(wm831x);
if (ret < 0)
return ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
data->wm831x = wm831x;
data->current_brightness = 0;
data->isink_reg = isink_reg;
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = max_isel;
bl = devm_backlight_device_register(&pdev->dev, "wm831x", &pdev->dev,
data, &wm831x_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = max_isel;
platform_set_drvdata(pdev, bl);
/* Disable the DCDC if it was started so we can bootstrap */
wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
backlight_update_status(bl);
return 0;
}
static struct platform_driver wm831x_backlight_driver = {
.driver = {
.name = "wm831x-backlight",
},
.probe = wm831x_backlight_probe,
};
module_platform_driver(wm831x_backlight_driver);
MODULE_DESCRIPTION("Backlight Driver for WM831x PMICs");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:wm831x-backlight");
七、应用层app测试
本来是不想写的,下一篇有介绍,在这里写下吧
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
// 宏定义
#define FBDEVICE "/dev/fb0"
#define WIDTH 1024
#define HEIGHT 600
#define WHITE 0xffffffff // test ok
#define BLACK 0x00000000
#define RED 0xffff0000
#define GREEN 0xff00ff00 // test ok
#define BLUE 0xff0000ff
#define GREENP 0x0000ff00 // 一样,说明前2个ff透明位不起作用
// 函数声明
void draw_back(unsigned int width, unsigned int height, unsigned int color);
void draw_line(unsigned int color);
// 全局变量
unsigned int *pfb = NULL;
int main(void)
{
int fd = -1, ret = -1;
struct fb_fix_screeninfo finfo = {0};
struct fb_var_screeninfo vinfo = {0};
// 第1步:打开设备
fd = open(FBDEVICE, O_RDWR);
if (fd < 0)
{
perror("open");
return -1;
}
printf("open %s success.\n", FBDEVICE);
// 第2步:获取设备的硬件信息
ret = ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("smem_start = 0x%x, smem_len = %u.\n", finfo.smem_start, finfo.smem_len);
ret = ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
if (ret < 0)
{
perror("ioctl");
return -1;
}
printf("xres = %u, yres = %u.\n", vinfo.xres, vinfo.yres);
printf("xres_virtual = %u, yres_virtual = %u.\n", vinfo.xres_virtual, vinfo.yres_virtual);
printf("bpp = %u.\n", vinfo.bits_per_pixel);
// 第3步:进行mmap,申请到显存的地址。
unsigned long len = vinfo.xres_virtual * vinfo.yres_virtual * vinfo.bits_per_pixel / 8;
printf("len = %ld\n", len);
pfb = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (NULL == pfb)
{
perror("mmap");
return -1;
}
printf("pfb = %p.\n", pfb);
draw_back(WIDTH, HEIGHT, WHITE);
draw_line(RED);
close(fd);
return 0;
}
//刷背景函数
void draw_back(unsigned int width, unsigned int height, unsigned int color)
{
unsigned int x, y;
for (y=0; y<height; y++)
{
for (x=0; x<width; x++)
{
*(pfb + y * WIDTH + x) = color;
}
}
}
//画线函数
void draw_line(unsigned int color)
{
unsigned int x, y;
for (x=50; x<600; x++)
{
*(pfb + 200 * WIDTH + x) = color;
}
}
八、驱动深入分析
有了上面的驱动代码,就可以继续分析内核实现
内核实现fbmen_init()函数:路径drivers/video/fbmem.c
负责创建graphics类、注册FB的字符设备驱动、register_framebuffer()函数提供接口给具体framebuffer驱动编写着来注册fb设备。
调用了proc文件系统,在proc下就可以看到这些fb了。
下面这个函数,可以看到,他干了嘛,很熟悉,很input一样,内核帮我们,注册好了,字符设备这一套,提供file_opration这一套。给应用层,注册到class,device。
再来驱动层注册示意图。就是注册填充一个fb_info