显示设备例如LCD,在Linux中用Framebuffer来表征,在/dev目录下显示设备一般表示成这样:/dev/fbX,应用程序通过访问这个设备来访问LCD,实际上应用程序通过操作显存来操作显示设备,显存由驱动程序设置。
Linux中使用fb_info结构体来表示Framebuffer设备,其内容如下:
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* 互斥锁 */
struct mutex mm_lock; /* 互斥锁,用于 fb_mmap 和 smem_*域*/
struct fb_var_screeninfo var; /* 当前可变参数 */
struct fb_fix_screeninfo fix; /* 当前固定参数 */
struct fb_monspecs monspecs; /* 当前显示器特性 */
struct work_struct queue; /* 帧缓冲事件队列 */
struct fb_pixmap pixmap; /* 图像硬件映射 */
struct fb_pixmap sprite; /* 光标硬件映射 */
struct fb_cmap cmap; /* 当前调色板 */
struct list_head modelist; /* 当前模式列表 */
struct fb_videomode *mode; /* 当前视频模式 */
#ifdef CONFIG_FB_BACKLIGHT /* 如果 LCD 支持背光的话 */
/* 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
......
struct fb_ops *fbops; /* 帧缓冲操作函数集 */
struct device *device; /* 父设备 */
struct device *dev; /* 当前 fb 设备 */
int class_flag; /* 私有 sysfs 标志 */
......
char __iomem *screen_base; /* 虚拟内存基地址(屏幕显存) */
unsigned long screen_size; /* 虚拟内存大小(屏幕显存大小) */
void *pseudo_palette; /* 伪 16 位调色板 */
......
};
其中最重要成员有:var,fix,fbops(LCD设备的操作集合),screen_base(显存基地址),screen_size(显存大小),pseudo_palette。
var结构体变量指的是显示设备的可变参数:
struct fb_var_screeninfo {
__u32 xres;/* visible resolution*//*定义屏幕一行有多少个像素点 */
__u32 yres; /*定义屏幕一列由多少个像素点 */
__u32 bits_per_pixel; /*每像素位数(多少BPP),单位为字节 */
__u32 pixclock; /* pixel clock in ps (pico seconds) */ /*像素时钟(皮秒) */
/* 行切换,从同步到绘图之间的延迟即HFPD(有效数据之后无效的像素的个数) ,对应于LCD数据手册的Hsyn的front-porch*/
__u32 left_margin; /* time from sync to picture */
/*行切换,从绘图到同步之间的延迟即HBPD(Hsyn脉冲下降沿之后的无效像素的个数) ,对应于LCD数据手册的Hsyn的back-porch*/
__u32 right_margin; /* time from picture to sync */
/*帧切换,从同步到绘图之间的延迟即VFPD(有效数据之后还要经历的无效行数(之后是下一帧数据)) ,对应于LCD数据手册的Vsyn的front-porch*/
__u32 upper_margin; /* time from sync to picture */
/*帧切换,从绘图到同步之间的延迟即VBPD(Vsyn脉冲下降沿之后还要经历的无效行数) ,对应于LCD数据手册的Vsyn的back-porch */
__u32 lower_margin;
/*水平同步的长度即HSPW(Hsyn信号的脉冲宽度),对应于LCD数据手册的Hsyn的pulse Width */
__u32 hsync_len; /* length of horizontal sync*/
/*垂直同步的长度即VSPW(Vsyn信号的脉冲宽度),对应于LCD数据手册的Vsyn的pulse Width */
__u32 vsync_len; /* length of vertical sync*/
__u32 sync; /* see FB_SYNC_**/ /* 查看宏FB_SYNC_*/
__u32 vmode; /* see FB_VMODE_**/ /* 查看宏FB_VMODE_ */
....
}
这些参数都是可以在设备树中进行修改的,具体的参数是多少要根据LCD的数据手册来进行修改,LCD的驱动程序会从设备树中读取这些参数。
fix结构体变量指的是不变参数,用户不可进行修改的参数,比如屏幕缓冲区的地址。
struct fb_fix_screeninfo {
unsigned long smem_start; /* Start of frame buffer mem physical address) */
__u32 smem_len; /*fb缓冲的长度,等于max_xres*max_yres*max_bpp/8 */
unsigned long mmio_start; /* Start of Memory Mapped I/O*/ /* 内存映射的I/O的开始位置 */
__u32 mmio_len; /* 内存映射的I/O的长度 */
....
}
var变量是驱动程序从LCD设备的设备树节点中取得,fix变量中的显存基址和显存大小由驱动程序设置。
LCD设备在Linux内核中一般被组织成一个标准的platform设备,其驱动也是一个平台设备驱动,当总线将设备和驱动匹配起来以后,probe函数会被调用,平台设备驱动的重点就是probe函数。
LCD驱动的probe函数需要完成几件事:
一、申请一个fb_info结构体,设置好其成员变量。
二、初始化SoC的LCD控制器,其实就是配置控制器的各寄存器。
三、调用register_framebuffer函数向Linux内核注册初始化好的fb_info。
各SoC厂商在编写Framebuffer设备驱动的时候,往往会对Linux内核中的fb_info结构体进行二次封装,将SoC的LCD控制器的寄存器地址信息,LCD的时钟信息,fb_info封装在一起。NXP将封装后的结构体称之为:mxsfb_info
下面来看看LCD驱动中的一些重点内容:
static int mxsfb_probe(struct platform_device* pdev)
{
struct resource *res;
struct mxsfb_info *host;
struct fb_info *fb_info;
/*从设备树中提取LCD控制器寄存器地址信息*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*为二次封装的Framebuffer结构体分配内存*/
host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info),GFP_KERNEL);
/*给fb_info分配内存空间*/
fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
/*物理地址映射成虚拟地址,此后可以通过base的值访问到LCD控制器的所有寄存器*/
host->base = devm_ioremap_resource(&pdev->dev, res);
/*通过LCD设备的设备树节点中的内容去填充fb_info结构体,主要就是设置上述的重点参数*/
ret = mxsfb_init_fbinfo(host);
/*配置LCD控制器*/
mxsfb_set_par(fb_info);
mxsfb_enable_controller(fb_info);
/*向Linux内核注册*/
ret = register_framebuffer(fb_info);
.....
}
在mxsfb_init_fbinfo函数对fb_info进行初始化的时候,其中LCD的操作函数集如下:
static struct fb_ops mxsfb_ops = {
.owner = THIS_MODULE,
.fb_check_var = mxsfb_check_var,
.fb_set_par = mxsfb_set_par,
.fb_setcolreg = mxsfb_setcolreg,
.fb_ioctl = mxsfb_ioctl,
.fb_blank = mxsfb_blank,
.fb_pan_display = mxsfb_pan_display,
.fb_mmap = mxsfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
另外,mxsfb_init_fbinfo 函数通过调用 mxsfb_init_fbinfo_dt 函数从设备树中获取到 fb_info需要的各参数信息然后对fb_info进行填充,mxsfb_init_fbinfo函数会调用 mxsfb_map_videomem 函数申请 LCD 的帧缓冲内存(也就是显存)。这些函数都在mxsfb.c文件中。
注意:
SoC厂商提供的Linux内核一般都默认开启了LCD驱动,没开启的自己配置内核来开启,也就是说LCD驱动一般都是写好的,我们要做的就是改设备树信息。
下面来看看LCD设备树信息,来明确要修改哪些信息。
首先是LCD的引脚信息:
一般自己设计的板子,LCD引脚信息会和原厂评估版一样,几乎不用修改。
屏幕参数信息,这个需要按照自己的LCD屏幕重点修改。
其中bits-per-pixel属性根据具体LCD来设置,RGB565那么一个像素16位,RGB888则为24位。
LCD像素时钟需要自己计算,下面贴出一个计算公式:
注意这里计算的是显示一帧的时间,60帧还需要乘以60。计算像素时钟需要的这些参数,也就是设备树LCD参数信息,查询设备手册可以获得。
参考资料:《正点原子Linux设备驱动开发手册》