43.Linux LCD驱动
头文件为
c
include/linux/fb.h
linux的LCD驱动主要就是打点/读点
完成上面的功能在搭配GUI库来实现丰富多彩的ui界面

我们本次需要实现linux驱动实现在LCD上打点
比如imx6ull出厂自带了evk开发版的屏幕驱动
/dev/fb0 -》 提供给上层应用程序的文件接口,可以通过对该设备的读写来控制LCD屏幕
Framebuffer设备
RGB LCD屏幕,framebuffe是一种机制,应用程序操作驱动里面的LCD显存的一种机制,因为应用程序需要通过操作显存来在LCD上显示字符,图片等信息。
通过framebuffer机制将底层的lcd抽象为/dev/fbX ,X=1,2,3...,应用程序可以通过操作/dev/fbX来操作屏幕。
我们只需要完成/dev/fbX的驱动。
对于使用lcd是非常简单的,LCD控制器已经出厂完成了,购买其他的屏幕也会有厂家写好LCD控制器,而我们只需要修改屏幕具体的参数即可
在设备树im6ull.dtsi中
c
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};
可以看到对应的屏幕驱动的适配为
"fsl,imx6ul-lcdif"
"fsl,imx28-lcdif"
全局搜索,找到其驱动文件为
c
drivers/video/fbdev/mxsfb.c
文件的最后:
c
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
{ /* sentinel */ }
};
...
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);
MODULE_DESCRIPTION("Freescale mxs framebuffer driver");
MODULE_AUTHOR("Sascha Hauer, Pengutronix");
MODULE_LICENSE("GPL");
可以看到是一个标准的platform框架驱动
而Framebuffer是linux抽象出来管理LCD屏幕的结构体为fb_info
c
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var */
struct fb_fix_screeninfo fix; /* Current fix */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current 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 */
#endif
char __iomem *screen_base; /* Virtual address */
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
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_ops
该结构体的注册函数为:
c
int register_framebuffer(struct fb_info *fb_info)
fb_info:需要上报的 fb_info。
返回值: 0,成功;负值,失败
在系统中的体现
向内核注册之后,用户态的udev/mdev(嵌入式)根据内核事件生成创建 /dev/fb0 设备文件,原理如下:
-
内核发送
uevent事件 设备注册成功后,内核会通过kobject_uevent发送一个add事件,包含设备类型、编号等信息(如MAJOR=29,MINOR=0,DEVNAME=fb0)。 -
udev规则匹配与节点创建udev守护进程监听uevent事件,根据预设规则(通常在/lib/udev/rules.d/中)处理:-
识别帧缓冲设备的主设备号(固定为
29,Linux 约定); -
根据次设备号0、1等在/dev目录下面创建字符设备文件
cmknod /dev/fb0 c 29 0其中
c表示字符设备,29是主设备号,0是次设备号。 -
注销函数:
c
int unregister_framebuffer(struct fb_info *fb_info);
fb_info 结构体的成员变量很多,我们重点关注 var、 fix、 fbops、 screen_base、 screen_size和 pseudo_palette。
mxsfb_probe 函数的主要工作内容为:
①、申请 fb_info。
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。
修改设备树将屏幕驱动
我们需要修改设备树将屏幕驱动起来
设备中中"fsl,imx28-lcdif"关键字的设备为
im6ull.dtsi中
c
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};
imx6ull-alientek.dts
c
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl>;
display = <&display0>;
status = "okay";
display0: display {
bits-per-pixel = <16>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <35500000>;
hactive = <800>;
vactive = <480>;
hfront-porch = <210>;
hback-porch = <46>;
hsync-len = <20>;
vback-porch = <23>;
vfront-porch = <22>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <1>;
};
};
};
};
这个设备树可以在480*800的屏幕上驱动,并显示一只linux企鹅
-> Device Drivers -> Graphics support
-> Bootup logo (LOGO [=y])
可以进行更改
回到设备树
c
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl>;
c
pinctrl_lcdif_dat: lcdifdatgrp {
fsl,pins = <
MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79
...
>;
};
c
pinctrl_lcdif_ctrl: lcdifctrlgrp {
fsl,pins = <
MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
>;
};
这些参数与LCD屏幕驱动所对应起来
修改值电气值 0x79 -> 0x49 因为考虑到0x79的最强驱动可能会影响网络的驱动电路
对于display0节点的具体含义,去看绑定文档 Documentatio/devicetree/bindings/fb有解释这些参数
我们需要修改其中的参数,根据自己板子的屏幕设置成相应的参数。

c
display0: display {
bits-per-pixel = <32>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <51200000>;
hactive = <1024>;
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vback-porch = <20>;
vfront-porch = <1>;
vsync-len = <3>;
//四根数据线 有效值 根据屏幕驱动时序图
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <1>;
};
};
};
修改完成编译重启开发板
可以看到logo
1.操作该驱动
c
echo hello world > /dev/tty1
可以看到屏幕打印了字符串hello world
我们可以将Linux默认输出为屏幕
c
vi etc/inittab
修改
c
#etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
tty1:askfrist:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
重启,修改bootargs
c
setenv bootargs 'console=ttymxc0,115200 rw root=/dev/nfs nfsroot=192.168.3.15:/home/alientek/Desktop/nfs/rootfs ip=192.168.3.55:192.168.3.15:192.168.3.1:255.255.255.0::eth0:off'
改为
c
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=ttymxc0,115200 改为 console=tty1 console=ttymxc0,115200
2.背光
依靠PWM控制亮度,一般测试屏幕的时候直接将背光引脚
c
cd /sys/devices/platform/backlight/backlight/backlight
ls
actual_brightness device subsystem
bl_power max_brightness type
brightness power uevent
echo 5 > brightness
echo 1 > brightness
echo 7 > brightness
对应设备树内容
c
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <7>;
status = "okay";
};
同时修改屏幕休眠时间
driver/tty/vt/vt.c
c
static int blankinterval = 10*60;
修改为0即可
修改重新编译内核,重新加载zImage
后面想使用显示文字、图片、UI界面 可以搜索关键字linux fb编程 、 mmap、QT
简单示例
前面说了设备注册后会在/dev下面创建一个framebuffer的对外接口,即/dev/fb0,我们就可以直接使用简单的应用程序进行控制
c
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
static int fd_fb; //帧缓冲设备文件描述符
static struct fb_var_screeninfo var; /* Current var *///存储屏幕可变信息的结构体(分辨率、像素格式等)
static int screen_size; //屏幕缓冲区总大小(字节)
static unsigned char *fb_base; //映射到用户空间的帧缓冲内存首地址
static unsigned int line_width; //屏幕一行的字节数
static unsigned int pixel_width; //每个像素占用的字节数
/**********************************************************************
* 函数名称: lcd_put_pixel
* 功能描述: 在LCD指定位置上输出指定颜色(描点)支持不同的像素格式(8/16/32 位):
* 输入参数: x坐标,y坐标,颜色
* 输出参数: 无
* 返 回 值: 会
***********************************************************************/
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch (var.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
int main(int argc, char **argv)
{
int i;
fd_fb = open("/dev/fb0", O_RDWR); //以读写模式打开 /dev/fb0 设备,获取文件描述符
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) //获取屏幕信息如分辨率 xres/yres、像素位深 bits_per_pixel 等,保存到var变量中
{
printf("can't get var\n");
return -1;
}
//通过获得的变脸计算相关参数
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
//内存映射:将帧缓冲设备的内存映射到用户空间,使得应用程序可以直接通过指针操作显示内存,无需频繁的 read/write 系统调用。
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1)
{
printf("can't mmap\n");
return -1;
}
/* 清屏: 全部设为白色 使用 memset 将整个帧缓冲填充为白色(0xff) */
memset(fb_base, 0xff, screen_size);
/* 随便设置出100个为红色 */
for (i = 0; i < 100; i++)
lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);
//资源释放:
munmap(fb_base , screen_size); //解除内存映射
close(fd_fb); //关闭设备文件
return 0;
}