《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 18 章 Linux LCD 驱动
·
《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》
第 18 章 Linux LCD 驱动
参考:宋宝华 著,机械工业出版社,2015年版
18.1 LCD 驱动基础
18.1.1 LCD 显示原理
LCD(Liquid Crystal Display,液晶显示器)是嵌入式系统中最常用的显示设备。理解 LCD 的工作原理是编写驱动的基础:
LCD 显示系统组成:
┌─────────────────────────────────────────────────────────────┐
│ SoC(如 i.MX6ULL) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LCD 控制器(LCDIF/eLCDIF) │ │
│ │ - 从帧缓冲区(Frame Buffer)读取像素数据 │ │
│ │ - 生成 LCD 时序信号(VSYNC/HSYNC/DE/CLK) │ │
│ │ - 通过 RGB 接口输出像素数据 │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│ RGB 并行接口(R[7:0] G[7:0] B[7:0])
│ + 时序信号(VSYNC HSYNC DE CLK)
┌──────────────────────────▼──────────────────────────────────┐
│ LCD 面板 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LCD 驱动 IC(时序控制器) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 液晶矩阵(像素阵列) │ │
│ │ 分辨率:800×480,每个像素 RGB 三个子像素 │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 背光模块(LED 背光) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
18.1.2 LCD 接口类型
常见 LCD 接口类型:
1. RGB 并行接口(最常见,嵌入式系统)
信号线:R[7:0] + G[7:0] + B[7:0] + VSYNC + HSYNC + DE + CLK
总线宽度:24位(RGB888)或 18位(RGB666)或 16位(RGB565)
特点:简单直接,速度快,但引脚多
典型分辨率:480×272、800×480、1024×600
2. MIPI DSI(Mobile Industry Processor Interface Display Serial Interface)
差分串行接口,引脚少
适合高分辨率(1080P 及以上)
常用于手机、平板
3. LVDS(Low Voltage Differential Signaling)
低压差分信号,抗干扰强
适合工业显示器、车载显示
4. SPI 接口(小尺寸 LCD)
引脚极少(4线 SPI)
速度慢,适合小尺寸低分辨率 LCD(如 128×64)
典型芯片:ST7735、ILI9341
5. I2C 接口(OLED 小屏)
引脚最少(2线)
速度最慢,适合 OLED 小屏
典型芯片:SSD1306(128×64 OLED)
18.1.3 LCD 时序参数
RGB 接口 LCD 的时序参数是驱动配置的关键:
LCD 时序图(以 800×480 LCD 为例):
垂直时序(帧时序):
┌──────────────────────────────────────────────────────────────┐
│ VBP │←────────── 有效显示行(480行)──────────→│ VFP │ VSW │
│(后沿)│ │(前沿)│(同步)│
└──────────────────────────────────────────────────────────────┘
VSYNC ↑ ↑
水平时序(行时序):
┌──────────────────────────────────────────────────────────────┐
│ HBP │←────────── 有效像素(800像素)──────────→│ HFP │ HSW │
│(后沿)│ │(前沿)│(同步)│
└──────────────────────────────────────────────────────────────┘
HSYNC ↑ ↑
关键时序参数(以 4.3寸 800×480 LCD 为例):
水平参数:
HSW(HSYNC 脉冲宽度):1
HBP(水平后沿):46
HFP(水平前沿):210
HACTIVE(有效像素):800
总水平像素 = HSW + HBP + HACTIVE + HFP = 1057
垂直参数:
VSW(VSYNC 脉冲宽度):1
VBP(垂直后沿):23
VFP(垂直前沿):22
VACTIVE(有效行数):480
总垂直行数 = VSW + VBP + VACTIVE + VFP = 526
像素时钟:
PCLK = 总水平像素 × 总垂直行数 × 帧率
PCLK = 1057 × 526 × 60 ≈ 33.3 MHz
18.1.4 像素格式
常见像素格式:
RGB565(16位/像素):
┌─────────────────────────────────────────────────────────┐
│ R4 R3 R2 R1 R0 G5 G4 G3 G2 G1 G0 B4 B3 B2 B1 B0 │
│ [15:11] [10:5] [4:0] │
└─────────────────────────────────────────────────────────┘
R:5位(0~31),G:6位(0~63),B:5位(0~31)
内存占用:2字节/像素
800×480 帧缓冲区大小:800 × 480 × 2 = 768KB
RGB888(24位/像素):
┌─────────────────────────────────────────────────────────┐
│ R7..R0 │ G7..G0 │ B7..B0 │
└─────────────────────────────────────────────────────────┘
R/G/B 各 8 位(0~255)
内存占用:3字节/像素(通常填充为4字节,即 ARGB8888)
ARGB8888(32位/像素):
┌─────────────────────────────────────────────────────────┐
│ A7..A0 │ R7..R0 │ G7..G0 │ B7..B0 │
└─────────────────────────────────────────────────────────┘
A:Alpha(透明度),R/G/B 各 8 位
内存占用:4字节/像素
800×480 帧缓冲区大小:800 × 480 × 4 = 1.5MB
18.1.5 帧缓冲区(Frame Buffer)
帧缓冲区的工作原理:
CPU/GPU 写入像素数据
↓
帧缓冲区(Frame Buffer,位于 DDR 内存中)
↓ DMA 读取(LCD 控制器自动扫描)
LCD 控制器
↓ 生成时序信号
LCD 面板显示
帧缓冲区地址:
物理地址:由 LCD 控制器的 DMA 地址寄存器指定
虚拟地址:通过 mmap 映射到用户空间
双缓冲(Double Buffering):
前缓冲区(Front Buffer):LCD 控制器正在显示的缓冲区
后缓冲区(Back Buffer):CPU/GPU 正在绘制的缓冲区
垂直同步(VSync)时切换,避免画面撕裂
18.2 Linux Framebuffer 驱动
18.2.1 Framebuffer 子系统概述
Linux Framebuffer(帧缓冲)子系统提供了一个硬件无关的图形接口,通过 /dev/fb0、/dev/fb1 等设备文件向用户空间暴露显示硬件:
Linux Framebuffer 体系结构:
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ 应用程序通过 /dev/fb0 访问帧缓冲区 │
│ Qt / GTK / DirectFB / SDL / X11(fbdev 后端) │
└──────────────────────────┬──────────────────────────────────┘
│ open/read/write/mmap/ioctl
┌──────────────────────────▼──────────────────────────────────┐
│ Framebuffer 核心层 │
│ /dev/fb0 设备文件 │
│ fbmem.c:提供统一的 ioctl 接口 │
│ FBIOGET_VSCREENINFO / FBIOPUT_VSCREENINFO │
│ FBIOGET_FSCREENINFO / FBIOPAN_DISPLAY │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ LCD 控制器驱动 │
│ 实现 fb_ops 中的操作函数 │
│ 配置 LCD 时序参数 │
│ 管理帧缓冲区内存 │
└──────────────────────────┬──────────────────────────────────┘
│ 操作硬件寄存器
┌──────────────────────────▼──────────────────────────────────┐
│ LCD 控制器硬件 │
└─────────────────────────────────────────────────────────────┘
18.2.2 Framebuffer 核心数据结构
#include <linux/fb.h>
/*
* fb_info:帧缓冲设备信息(最核心的数据结构)
* 每个 LCD 控制器对应一个 fb_info
*/
struct fb_info {
int node; /* 设备编号(/dev/fbN 中的 N)*/
struct fb_var_screeninfo var; /* 可变屏幕参数 */
struct fb_fix_screeninfo fix; /* 固定屏幕参数 */
struct fb_ops *fbops; /* 帧缓冲操作函数集 */
struct device *device; /* 关联的设备 */
/* 帧缓冲内存 */
char __iomem *screen_base; /* 帧缓冲虚拟地址 */
unsigned long screen_size; /* 帧缓冲大小 */
void *par; /* 驱动私有数据 */
/* ... */
};
/*
* fb_var_screeninfo:可变屏幕参数
* 可以通过 ioctl 修改(如切换分辨率、像素格式)
*/
struct fb_var_screeninfo {
__u32 xres; /* 水平分辨率(像素)*/
__u32 yres; /* 垂直分辨率(像素)*/
__u32 xres_virtual; /* 虚拟水平分辨率(用于双缓冲)*/
__u32 yres_virtual; /* 虚拟垂直分辨率 */
__u32 xoffset; /* 水平偏移(pan display 用)*/
__u32 yoffset; /* 垂直偏移(pan display 用)*/
__u32 bits_per_pixel; /* 每像素位数(16/24/32)*/
__u32 grayscale; /* 是否灰度模式 */
/* 像素格式(各颜色分量的位域)*/
struct fb_bitfield red; /* 红色分量 */
struct fb_bitfield green; /* 绿色分量 */
struct fb_bitfield blue; /* 蓝色分量 */
struct fb_bitfield transp; /* 透明度分量 */
/* 时序参数 */
__u32 pixclock; /* 像素时钟(皮秒,ps)*/
__u32 left_margin; /* HBP(水平后沿)*/
__u32 right_margin; /* HFP(水平前沿)*/
__u32 upper_margin; /* VBP(垂直后沿)*/
__u32 lower_margin; /* VFP(垂直前沿)*/
__u32 hsync_len; /* HSW(HSYNC 脉冲宽度)*/
__u32 vsync_len; /* VSW(VSYNC 脉冲宽度)*/
__u32 sync; /* 同步极性 */
__u32 vmode; /* 视频模式 */
__u32 rotate; /* 旋转角度 */
};
/*
* fb_fix_screeninfo:固定屏幕参数
* 不能通过 ioctl 修改
*/
struct fb_fix_screeninfo {
char id[16]; /* 标识字符串 */
unsigned long smem_start; /* 帧缓冲物理地址 */
__u32 smem_len; /* 帧缓冲大小 */
__u32 type; /* 帧缓冲类型(FB_TYPE_PACKED_PIXELS)*/
__u32 visual; /* 颜色模式(FB_VISUAL_TRUECOLOR)*/
__u16 xpanstep; /* 水平 pan 步长 */
__u16 ypanstep; /* 垂直 pan 步长 */
__u16 ywrapstep; /* wrap 步长 */
__u32 line_length; /* 每行字节数(stride)*/
unsigned long mmio_start; /* 寄存器物理地址 */
__u32 mmio_len; /* 寄存器大小 */
__u32 accel; /* 硬件加速类型 */
};
/*
* fb_ops:帧缓冲操作函数集
* 驱动通过实现这些函数来响应用户空间的操作
*/
struct fb_ops {
struct module *owner;
/* 打开/关闭 */
int (*fb_open)(struct fb_info *info, int user);
int (*fb_release)(struct fb_info *info, int user);
/* 读写(通常不需要实现,使用默认的内存读写)*/
ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
size_t count, loff_t *ppos);
ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
size_t count, loff_t *ppos);
/* 检查可变参数是否合法 */
int (*fb_check_var)(struct fb_var_screeninfo *var,
struct fb_info *info);
/* 设置可变参数(应用新的分辨率/像素格式等)*/
int (*fb_set_par)(struct fb_info *info);
/* 设置颜色寄存器(调色板,用于索引颜色模式)*/
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct fb_info *info);
/* 清屏 */
int (*fb_blank)(int blank, struct fb_info *info);
/* 平移显示(双缓冲切换)*/
int (*fb_pan_display)(struct fb_var_screeninfo *var,
struct fb_info *info);
/* 填充矩形(硬件加速)*/
void (*fb_fillrect)(struct fb_info *info,
const struct fb_fillrect *rect);
/* 复制矩形(硬件加速)*/
void (*fb_copyarea)(struct fb_info *info,
const struct fb_copyarea *region);
/* 图像绘制(硬件加速)*/
void (*fb_imageblit)(struct fb_info *info,
const struct fb_image *image);
/* 内存映射 */
int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);
/* ioctl */
int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
unsigned long arg);
};
18.2.3 Framebuffer 驱动的注册流程
Framebuffer 驱动注册流程:
module_init() / probe()
↓
1. framebuffer_alloc(sizeof(priv), dev)
← 分配 fb_info + 驱动私有数据
↓
2. 设置 fb_info 参数:
info->var(分辨率、像素格式、时序)
info->fix(帧缓冲物理地址、大小)
info->fbops(操作函数集)
↓
3. 分配帧缓冲内存(DMA 一致性内存):
dma_alloc_coherent() 或 dma_alloc_wc()
↓
4. 映射帧缓冲到内核虚拟地址:
info->screen_base = ioremap(phys_addr, size)
↓
5. 初始化 LCD 控制器硬件
↓
6. register_framebuffer(info)
← 注册到内核,创建 /dev/fbN
module_exit() / remove()
↓
1. unregister_framebuffer(info)
2. 释放帧缓冲内存
3. framebuffer_release(info)
18.2.4 用户空间访问 Framebuffer
/*
* 用户空间通过 /dev/fb0 访问帧缓冲区
*/
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <string.h>
int main(void)
{
int fd;
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
long screensize;
char *fbp;
/* 打开帧缓冲设备 */
fd = open("/dev/fb0", O_RDWR);
if (fd < 0) { perror("open"); return -1; }
/* 获取固定屏幕信息 */
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
printf("帧缓冲物理地址:0x%lx\n", finfo.smem_start);
printf("每行字节数:%u\n", finfo.line_length);
/* 获取可变屏幕信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
printf("分辨率:%u × %u\n", vinfo.xres, vinfo.yres);
printf("每像素位数:%u\n", vinfo.bits_per_pixel);
printf("像素时钟:%u ps\n", vinfo.pixclock);
/* 计算帧缓冲大小 */
screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;
/* 将帧缓冲映射到用户空间(零拷贝)*/
fbp = (char *)mmap(NULL, screensize,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (fbp == MAP_FAILED) { perror("mmap"); close(fd); return -1; }
/* 在屏幕上画一个红色矩形(RGB565 格式)*/
int x, y;
for (y = 100; y < 200; y++) {
for (x = 100; x < 300; x++) {
/* RGB565:R=31(0x1F), G=0, B=0 → 0xF800 */
unsigned short *pixel = (unsigned short *)(fbp +
y * finfo.line_length + x * 2);
*pixel = 0xF800; /* 红色 */
}
}
/* 画一个蓝色矩形 */
for (y = 200; y < 300; y++) {
for (x = 100; x < 300; x++) {
/* RGB565:R=0, G=0, B=31(0x1F) → 0x001F */
unsigned short *pixel = (unsigned short *)(fbp +
y * finfo.line_length + x * 2);
*pixel = 0x001F; /* 蓝色 */
}
}
/* 清屏(填充黑色)*/
/* memset(fbp, 0, screensize); */
/* 解除映射 */
munmap(fbp, screensize);
close(fd);
return 0;
}
常用 Framebuffer ioctl 命令:
/* 获取可变屏幕信息 */
ioctl(fd, FBIOGET_VSCREENINFO, &vinfo);
/* 设置可变屏幕信息(修改分辨率、像素格式等)*/
ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo);
/* 获取固定屏幕信息 */
ioctl(fd, FBIOGET_FSCREENINFO, &finfo);
/* 平移显示(双缓冲切换)*/
vinfo.yoffset = vinfo.yres; /* 切换到后缓冲区 */
ioctl(fd, FBIOPAN_DISPLAY, &vinfo);
/* 等待垂直同步(VSync)*/
int dummy = 0;
ioctl(fd, FBIO_WAITFORVSYNC, &dummy);
/* 设置背光亮度(通过 /sys/class/backlight/)*/
/* echo 100 > /sys/class/backlight/backlight/brightness */
18.3 LCD 控制器驱动实例
18.3.1 驱动实例概述
以 i.MX6ULL 的 eLCDIF(Enhanced LCD Interface)控制器为例,实现一个完整的 LCD 控制器驱动:
i.MX6ULL eLCDIF 特性:
- 支持 RGB 并行接口(RGB565/RGB666/RGB888)
- 内置 DMA,自动从帧缓冲区读取数据
- 支持双缓冲(通过 pan display)
- 支持硬件光标
- 最大分辨率:1366×768
- 像素时钟:最高 96MHz
硬件连接(以 4.3寸 800×480 LCD 为例):
i.MX6ULL eLCDIF ←→ LCD 面板(RGB888 接口)
I2C1 ←→ 触摸屏控制器(GT911)
GPIO1_IO08 ←→ LCD 背光使能
PWM1 ←→ LCD 背光亮度控制
18.3.2 eLCDIF 寄存器定义
/* eLCDIF 关键寄存器偏移 */
#define LCDIF_CTRL 0x00 /* 控制寄存器 */
#define LCDIF_CTRL1 0x10 /* 控制寄存器1 */
#define LCDIF_CTRL2 0x20 /* 控制寄存器2 */
#define LCDIF_TRANSFER_COUNT 0x30 /* 传输计数 */
#define LCDIF_CUR_BUF 0x40 /* 当前帧缓冲地址 */
#define LCDIF_NEXT_BUF 0x50 /* 下一帧缓冲地址 */
#define LCDIF_TIMING 0x60 /* 时序寄存器 */
#define LCDIF_VDCTRL0 0x70 /* 垂直控制寄存器0 */
#define LCDIF_VDCTRL1 0x80 /* 垂直控制寄存器1 */
#define LCDIF_VDCTRL2 0x90 /* 垂直控制寄存器2 */
#define LCDIF_VDCTRL3 0xA0 /* 垂直控制寄存器3 */
#define LCDIF_VDCTRL4 0xB0 /* 垂直控制寄存器4 */
#define LCDIF_BM_ERROR_STAT 0x190 /* 总线主错误状态 */
#define LCDIF_CRC_STAT 0x1A0 /* CRC 状态 */
#define LCDIF_STAT 0x1B0 /* 状态寄存器 */
#define LCDIF_PIGEONCTRL0 0x380 /* Pigeon 控制0 */
/* CTRL 寄存器位定义 */
#define LCDIF_CTRL_RUN BIT(0) /* 运行 */
#define LCDIF_CTRL_DATA_FORMAT_16BIT BIT(3) /* 16位数据格式 */
#define LCDIF_CTRL_DATA_FORMAT_18BIT BIT(4) /* 18位数据格式 */
#define LCDIF_CTRL_DATA_FORMAT_24BIT BIT(5) /* 24位数据格式 */
#define LCDIF_CTRL_MASTER BIT(5) /* DMA 主设备模式 */
#define LCDIF_CTRL_ENABLE_PXP_HANDSHAKE BIT(6)
#define LCDIF_CTRL_WORD_LENGTH_16BIT (0 << 8)
#define LCDIF_CTRL_WORD_LENGTH_8BIT (1 << 8)
#define LCDIF_CTRL_WORD_LENGTH_18BIT (2 << 8)
#define LCDIF_CTRL_WORD_LENGTH_24BIT (3 << 8)
#define LCDIF_CTRL_LCD_DATABUS_WIDTH_16BIT (0 << 10)
#define LCDIF_CTRL_LCD_DATABUS_WIDTH_8BIT (1 << 10)
#define LCDIF_CTRL_LCD_DATABUS_WIDTH_18BIT (2 << 10)
#define LCDIF_CTRL_LCD_DATABUS_WIDTH_24BIT (3 << 10)
#define LCDIF_CTRL_BYPASS_COUNT BIT(19) /* 绕过计数 */
#define LCDIF_CTRL_VSYNC_MODE BIT(18) /* VSYNC 模式 */
#define LCDIF_CTRL_DOTCLK_MODE BIT(17) /* 点时钟模式(RGB 接口)*/
#define LCDIF_CTRL_DATA_SELECT BIT(16) /* 数据选择 */
#define LCDIF_CTRL_INPUT_DATA_SWIZZLE(x) ((x) << 14)
#define LCDIF_CTRL_CSC_DATA_SWIZZLE(x) ((x) << 12)
#define LCDIF_CTRL_CLKGATE BIT(30) /* 时钟门控 */
#define LCDIF_CTRL_SFTRST BIT(31) /* 软件复位 */
/* VDCTRL0 寄存器位定义 */
#define LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH(x) ((x) & 0x3FFFF)
#define LCDIF_VDCTRL0_HALF_LINE_MODE BIT(18)
#define LCDIF_VDCTRL0_HALF_LINE BIT(19)
#define LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20)
#define LCDIF_VDCTRL0_VSYNC_PERIOD_UNIT BIT(21)
#define LCDIF_VDCTRL0_ENABLE_POL BIT(24) /* DE 极性 */
#define LCDIF_VDCTRL0_DOTCLK_POL BIT(25) /* CLK 极性 */
#define LCDIF_VDCTRL0_HSYNC_POL BIT(26) /* HSYNC 极性 */
#define LCDIF_VDCTRL0_VSYNC_POL BIT(27) /* VSYNC 极性 */
#define LCDIF_VDCTRL0_ENABLE_PRESENT BIT(28) /* DE 信号存在 */
18.3.3 完整的 LCD 控制器驱动
/*
* imx6ull_lcdif.c —— i.MX6ULL eLCDIF LCD 控制器驱动
*
* 实现功能:
* - 初始化 eLCDIF 控制器
* - 配置 LCD 时序参数
* - 分配帧缓冲区(DMA 内存)
* - 注册 Framebuffer 设备
* - 支持双缓冲(pan display)
* - 背光控制
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
/* LCD 时序参数结构体 */
struct lcd_timing {
u32 xres; /* 水平分辨率 */
u32 yres; /* 垂直分辨率 */
u32 pixclock; /* 像素时钟(Hz)*/
u32 left_margin; /* HBP */
u32 right_margin; /* HFP */
u32 upper_margin; /* VBP */
u32 lower_margin; /* VFP */
u32 hsync_len; /* HSW */
u32 vsync_len; /* VSW */
u32 sync; /* 同步极性 */
};
/* 驱动私有数据 */
struct imx6ull_lcdif {
struct device *dev;
void __iomem *base; /* 寄存器基地址 */
struct clk *clk_pix; /* 像素时钟 */
struct clk *clk_axi; /* AXI 总线时钟 */
struct clk *clk_disp_axi; /* 显示 AXI 时钟 */
/* 帧缓冲区 */
dma_addr_t fb_phys; /* 帧缓冲物理地址 */
void *fb_virt; /* 帧缓冲虚拟地址 */
size_t fb_size; /* 帧缓冲大小 */
/* 双缓冲 */
dma_addr_t fb_phys_back; /* 后缓冲物理地址 */
void *fb_virt_back; /* 后缓冲虚拟地址 */
/* 背光 */
int bl_gpio; /* 背光使能 GPIO */
/* 中断和同步 */
int irq;
struct completion vsync_complete;
/* LCD 时序 */
struct lcd_timing timing;
/* fb_info */
struct fb_info *info;
};
/* ── 寄存器操作辅助函数 ──────────────────────────────────────── */
static inline void lcdif_write(struct imx6ull_lcdif *lcdif,
u32 reg, u32 val)
{
writel(val, lcdif->base + reg);
}
static inline u32 lcdif_read(struct imx6ull_lcdif *lcdif, u32 reg)
{
return readl(lcdif->base + reg);
}
static inline void lcdif_set(struct imx6ull_lcdif *lcdif,
u32 reg, u32 mask)
{
lcdif_write(lcdif, reg, lcdif_read(lcdif, reg) | mask);
}
static inline void lcdif_clr(struct imx6ull_lcdif *lcdif,
u32 reg, u32 mask)
{
lcdif_write(lcdif, reg, lcdif_read(lcdif, reg) & ~mask);
}
/* ── 硬件初始化 ──────────────────────────────────────────────── */
static void lcdif_reset(struct imx6ull_lcdif *lcdif)
{
/* 软件复位 */
lcdif_set(lcdif, LCDIF_CTRL, LCDIF_CTRL_SFTRST);
udelay(10);
lcdif_clr(lcdif, LCDIF_CTRL, LCDIF_CTRL_SFTRST);
lcdif_clr(lcdif, LCDIF_CTRL, LCDIF_CTRL_CLKGATE);
udelay(10);
}
static void lcdif_setup_timing(struct imx6ull_lcdif *lcdif)
{
struct lcd_timing *t = &lcdif->timing;
u32 vdctrl0, vdctrl2, vdctrl3, vdctrl4;
/* 设置像素时钟 */
clk_set_rate(lcdif->clk_pix, t->pixclock);
/* VDCTRL0:VSYNC 脉冲宽度和极性 */
vdctrl0 = LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH(t->vsync_len) |
LCDIF_VDCTRL0_VSYNC_PULSE_WIDTH_UNIT | /* 以行为单位 */
LCDIF_VDCTRL0_VSYNC_PERIOD_UNIT | /* 以行为单位 */
LCDIF_VDCTRL0_ENABLE_PRESENT; /* 使能 DE 信号 */
/* 根据同步极性设置 */
if (!(t->sync & FB_SYNC_HOR_HIGH_ACT))
vdctrl0 |= LCDIF_VDCTRL0_HSYNC_POL; /* HSYNC 低有效 */
if (!(t->sync & FB_SYNC_VERT_HIGH_ACT))
vdctrl0 |= LCDIF_VDCTRL0_VSYNC_POL; /* VSYNC 低有效 */
lcdif_write(lcdif, LCDIF_VDCTRL0, vdctrl0);
/* VDCTRL1:VSYNC 周期(总行数)*/
lcdif_write(lcdif, LCDIF_VDCTRL1,
t->vsync_len + t->upper_margin + t->yres + t->lower_margin);
/* VDCTRL2:HSYNC 脉冲宽度和水平总像素数 */
vdctrl2 = ((t->hsync_len & 0x3FFF) << 18) |
((t->hsync_len + t->left_margin + t->xres + t->right_margin) & 0x3FFFF);
lcdif_write(lcdif, LCDIF_VDCTRL2, vdctrl2);
/* VDCTRL3:水平和垂直等待计数 */
vdctrl3 = ((t->upper_margin + t->vsync_len) << 16) |
(t->hsync_len + t->left_margin);
lcdif_write(lcdif, LCDIF_VDCTRL3, vdctrl3);
/* VDCTRL4:水平有效像素数 */
vdctrl4 = (1 << 18) | t->xres; /* SYNC_SIGNALS_ON | 水平有效像素 */
lcdif_write(lcdif, LCDIF_VDCTRL4, vdctrl4);
/* 设置传输计数(分辨率)*/
lcdif_write(lcdif, LCDIF_TRANSFER_COUNT,
(t->yres << 16) | t->xres);
}
static void lcdif_setup_ctrl(struct imx6ull_lcdif *lcdif,
int bits_per_pixel)
{
u32 ctrl = LCDIF_CTRL_DOTCLK_MODE | /* RGB 点时钟模式 */
LCDIF_CTRL_BYPASS_COUNT | /* 绕过计数 */
LCDIF_CTRL_MASTER; /* DMA 主设备 */
/* 根据像素格式设置数据宽度 */
switch (bits_per_pixel) {
case 16:
ctrl |= LCDIF_CTRL_WORD_LENGTH_16BIT;
ctrl |= LCDIF_CTRL_LCD_DATABUS_WIDTH_16BIT;
break;
case 24:
case 32:
ctrl |= LCDIF_CTRL_WORD_LENGTH_24BIT;
ctrl |= LCDIF_CTRL_LCD_DATABUS_WIDTH_24BIT;
break;
}
lcdif_write(lcdif, LCDIF_CTRL, ctrl);
/* CTRL1:设置字节交换 */
lcdif_write(lcdif, LCDIF_CTRL1, 0x000F0000); /* 使能所有字节通道 */
}
/* ── 中断处理(VSync)──────────────────────────────────────────── */
static irqreturn_t lcdif_irq_handler(int irq, void *dev_id)
{
struct imx6ull_lcdif *lcdif = dev_id;
u32 stat = lcdif_read(lcdif, LCDIF_STAT);
/* 清除中断 */
lcdif_write(lcdif, LCDIF_CTRL1 + 0x8, 0x1F); /* 清除所有中断 */
/* VSync 中断 */
if (stat & BIT(9)) {
complete(&lcdif->vsync_complete);
}
return IRQ_HANDLED;
}
/* ── fb_ops 实现 ──────────────────────────────────────────────── */
static int lcdif_fb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct imx6ull_lcdif *lcdif = info->par;
/* 检查分辨率 */
if (var->xres != lcdif->timing.xres ||
var->yres != lcdif->timing.yres) {
dev_err(lcdif->dev, "不支持的分辨率:%u×%u\n",
var->xres, var->yres);
return -EINVAL;
}
/* 检查像素格式 */
switch (var->bits_per_pixel) {
case 16:
/* RGB565 */
var->red.offset = 11; var->red.length = 5;
var->green.offset = 5; var->green.length = 6;
var->blue.offset = 0; var->blue.length = 5;
var->transp.offset = 0; var->transp.length = 0;
break;
case 24:
/* RGB888 */
var->red.offset = 16; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 0; var->blue.length = 8;
var->transp.offset = 0; var->transp.length = 0;
break;
case 32:
/* ARGB8888 */
var->red.offset = 16; var->red.length = 8;
var->green.offset = 8; var->green.length = 8;
var->blue.offset = 0; var->blue.length = 8;
var->transp.offset = 24; var->transp.length = 8;
break;
default:
dev_err(lcdif->dev, "不支持的像素格式:%u bpp\n",
var->bits_per_pixel);
return -EINVAL;
}
return 0;
}
static int lcdif_fb_set_par(struct fb_info *info)
{
struct imx6ull_lcdif *lcdif = info->par;
/* 更新 fix 信息 */
info->fix.line_length = info->var.xres * info->var.bits_per_pixel / 8;
/* 重新配置硬件 */
lcdif_reset(lcdif);
lcdif_setup_ctrl(lcdif, info->var.bits_per_pixel);
lcdif_setup_timing(lcdif);
/* 设置帧缓冲地址 */
lcdif_write(lcdif, LCDIF_CUR_BUF, lcdif->fb_phys);
lcdif_write(lcdif, LCDIF_NEXT_BUF, lcdif->fb_phys);
/* 启动 LCD 控制器 */
lcdif_set(lcdif, LCDIF_CTRL, LCDIF_CTRL_RUN);
dev_info(lcdif->dev, "LCD 配置:%u×%u,%u bpp\n",
info->var.xres, info->var.yres, info->var.bits_per_pixel);
return 0;
}
static int lcdif_fb_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp,
struct fb_info *info)
{
/* 真彩色模式不需要调色板 */
if (info->var.grayscale)
return 1;
if (regno >= 256)
return 1;
/* 对于真彩色,将颜色值存入伪调色板 */
if (info->fix.visual == FB_VISUAL_TRUECOLOR && regno < 16) {
u32 v;
red >>= (16 - info->var.red.length);
green >>= (16 - info->var.green.length);
blue >>= (16 - info->var.blue.length);
v = (red << info->var.red.offset) |
(green << info->var.green.offset) |
(blue << info->var.blue.offset);
((u32 *)(info->pseudo_palette))[regno] = v;
}
return 0;
}
static int lcdif_fb_blank(int blank, struct fb_info *info)
{
struct imx6ull_lcdif *lcdif = info->par;
switch (blank) {
case FB_BLANK_UNBLANK:
/* 开启显示 */
if (gpio_is_valid(lcdif->bl_gpio))
gpio_set_value(lcdif->bl_gpio, 1);
lcdif_set(lcdif, LCDIF_CTRL, LCDIF_CTRL_RUN);
break;
case FB_BLANK_NORMAL:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
/* 关闭显示 */
if (gpio_is_valid(lcdif->bl_gpio))
gpio_set_value(lcdif->bl_gpio, 0);
lcdif_clr(lcdif, LCDIF_CTRL, LCDIF_CTRL_RUN);
break;
}
return 0;
}
static int lcdif_fb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct imx6ull_lcdif *lcdif = info->par;
dma_addr_t addr;
/* 计算新的帧缓冲起始地址 */
addr = lcdif->fb_phys +
var->yoffset * info->fix.line_length +
var->xoffset * info->var.bits_per_pixel / 8;
/* 等待 VSync(确保在帧间切换)*/
reinit_completion(&lcdif->vsync_complete);
wait_for_completion_timeout(&lcdif->vsync_complete,
msecs_to_jiffies(50));
/* 更新下一帧的缓冲区地址 */
lcdif_write(lcdif, LCDIF_NEXT_BUF, addr);
return 0;
}
static struct fb_ops lcdif_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = lcdif_fb_check_var,
.fb_set_par = lcdif_fb_set_par,
.fb_setcolreg = lcdif_fb_setcolreg,
.fb_blank = lcdif_fb_blank,
.fb_pan_display = lcdif_fb_pan_display,
.fb_fillrect = cfb_fillrect, /* 使用通用软件实现 */
.fb_copyarea = cfb_copyarea, /* 使用通用软件实现 */
.fb_imageblit = cfb_imageblit, /* 使用通用软件实现 */
};
/* ── probe 函数 ──────────────────────────────────────────────────── */
static int imx6ull_lcdif_probe(struct platform_device *pdev)
{
struct imx6ull_lcdif *lcdif;
struct fb_info *info;
struct resource *res;
struct device_node *np = pdev->dev.of_node;
struct videomode vm;
int ret;
/* 分配 fb_info 和私有数据 */
info = framebuffer_alloc(sizeof(struct imx6ull_lcdif), &pdev->dev);
if (!info)
return -ENOMEM;
lcdif = info->par;
lcdif->dev = &pdev->dev;
lcdif->info = info;
init_completion(&lcdif->vsync_complete);
/* 获取寄存器资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
lcdif->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(lcdif->base)) {
ret = PTR_ERR(lcdif->base);
goto err_fb;
}
/* 获取中断号 */
lcdif->irq = platform_get_irq(pdev, 0);
if (lcdif->irq < 0) {
ret = lcdif->irq;
goto err_fb;
}
/* 申请中断 */
ret = devm_request_irq(&pdev->dev, lcdif->irq,
lcdif_irq_handler, 0, "lcdif", lcdif);
if (ret)
goto err_fb;
/* 获取时钟 */
lcdif->clk_pix = devm_clk_get(&pdev->dev, "pix");
lcdif->clk_axi = devm_clk_get(&pdev->dev, "axi");
lcdif->clk_disp_axi = devm_clk_get(&pdev->dev, "disp_axi");
if (IS_ERR(lcdif->clk_pix) || IS_ERR(lcdif->clk_axi)) {
ret = -ENODEV;
goto err_fb;
}
/* 使能时钟 */
clk_prepare_enable(lcdif->clk_axi);
clk_prepare_enable(lcdif->clk_disp_axi);
clk_prepare_enable(lcdif->clk_pix);
/* 从设备树读取 LCD 时序参数 */
ret = of_get_videomode(np, &vm, OF_USE_NATIVE_MODE);
if (ret) {
dev_err(&pdev->dev, "读取 LCD 时序参数失败\n");
goto err_clk;
}
/* 填充时序结构体 */
lcdif->timing.xres = vm.hactive;
lcdif->timing.yres = vm.vactive;
lcdif->timing.pixclock = vm.pixelclock;
lcdif->timing.left_margin = vm.hback_porch;
lcdif->timing.right_margin = vm.hfront_porch;
lcdif->timing.upper_margin = vm.vback_porch;
lcdif->timing.lower_margin = vm.vfront_porch;
lcdif->timing.hsync_len = vm.hsync_len;
lcdif->timing.vsync_len = vm.vsync_len;
/* 获取背光 GPIO */
lcdif->bl_gpio = of_get_named_gpio(np, "backlight-gpio", 0);
if (gpio_is_valid(lcdif->bl_gpio)) {
devm_gpio_request_one(&pdev->dev, lcdif->bl_gpio,
GPIOF_OUT_INIT_LOW, "lcd-bl");
}
/* 分配帧缓冲区(双缓冲,分配两倍大小)*/
lcdif->fb_size = lcdif->timing.xres * lcdif->timing.yres * 4 * 2;
lcdif->fb_virt = dma_alloc_wc(&pdev->dev, lcdif->fb_size,
&lcdif->fb_phys, GFP_KERNEL);
if (!lcdif->fb_virt) {
dev_err(&pdev->dev, "分配帧缓冲区失败\n");
ret = -ENOMEM;
goto err_clk;
}
/* 清空帧缓冲区 */
memset(lcdif->fb_virt, 0, lcdif->fb_size);
/* 后缓冲区地址(偏移一帧)*/
lcdif->fb_phys_back = lcdif->fb_phys +
lcdif->timing.xres * lcdif->timing.yres * 4;
lcdif->fb_virt_back = lcdif->fb_virt +
lcdif->timing.xres * lcdif->timing.yres * 4;
/* 设置 fb_info 的可变参数 */
info->var.xres = lcdif->timing.xres;
info->var.yres = lcdif->timing.yres;
info->var.xres_virtual = lcdif->timing.xres;
info->var.yres_virtual = lcdif->timing.yres * 2; /* 双缓冲 */
info->var.bits_per_pixel = 32; /* ARGB8888 */
info->var.pixclock = KHZ2PICOS(lcdif->timing.pixclock / 1000);
info->var.left_margin = lcdif->timing.left_margin;
info->var.right_margin = lcdif->timing.right_margin;
info->var.upper_margin = lcdif->timing.upper_margin;
info->var.lower_margin = lcdif->timing.lower_margin;
info->var.hsync_len = lcdif->timing.hsync_len;
info->var.vsync_len = lcdif->timing.vsync_len;
/* 设置 ARGB8888 像素格式 */
info->var.red.offset = 16; info->var.red.length = 8;
info->var.green.offset = 8; info->var.green.length = 8;
info->var.blue.offset = 0; info->var.blue.length = 8;
info->var.transp.offset = 24; info->var.transp.length = 8;
/* 设置 fb_info 的固定参数 */
strcpy(info->fix.id, "imx6ull-lcdif");
info->fix.smem_start = lcdif->fb_phys;
info->fix.smem_len = lcdif->fb_size;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.line_length = lcdif->timing.xres * 4;
info->fix.accel = FB_ACCEL_NONE;
info->fix.ypanstep = 1; /* 支持垂直 pan(双缓冲)*/
/* 设置帧缓冲内存 */
info->screen_base = lcdif->fb_virt;
info->screen_size = lcdif->fb_size;
/* 设置操作函数集 */
info->fbops = &lcdif_fb_ops;
/* 分配伪调色板(真彩色模式需要)*/
info->pseudo_palette = devm_kzalloc(&pdev->dev,
sizeof(u32) * 16, GFP_KERNEL);
if (!info->pseudo_palette) {
ret = -ENOMEM;
goto err_fb_mem;
}
/* 初始化硬件 */
lcdif_reset(lcdif);
lcdif_setup_ctrl(lcdif, 32);
lcdif_setup_timing(lcdif);
/* 设置帧缓冲地址 */
lcdif_write(lcdif, LCDIF_CUR_BUF, lcdif->fb_phys);
lcdif_write(lcdif, LCDIF_NEXT_BUF, lcdif->fb_phys);
/* 注册 Framebuffer 设备 */
ret = register_framebuffer(info);
if (ret) {
dev_err(&pdev->dev, "注册 Framebuffer 失败:%d\n", ret);
goto err_fb_mem;
}
/* 启动 LCD 控制器 */
lcdif_set(lcdif, LCDIF_CTRL, LCDIF_CTRL_RUN);
/* 开启背光 */
if (gpio_is_valid(lcdif->bl_gpio))
gpio_set_value(lcdif->bl_gpio, 1);
platform_set_drvdata(pdev, info);
dev_info(&pdev->dev, "LCD 驱动加载成功:%u×%u,/dev/fb%d\n",
lcdif->timing.xres, lcdif->timing.yres, info->node);
return 0;
err_fb_mem:
dma_free_wc(&pdev->dev, lcdif->fb_size,
lcdif->fb_virt, lcdif->fb_phys);
err_clk:
clk_disable_unprepare(lcdif->clk_pix);
clk_disable_unprepare(lcdif->clk_axi);
clk_disable_unprepare(lcdif->clk_disp_axi);
err_fb:
framebuffer_release(info);
return ret;
}
static int imx6ull_lcdif_remove(struct platform_device *pdev)
{
struct fb_info *info = platform_get_drvdata(pdev);
struct imx6ull_lcdif *lcdif = info->par;
/* 关闭背光 */
if (gpio_is_valid(lcdif->bl_gpio))
gpio_set_value(lcdif->bl_gpio, 0);
/* 停止 LCD 控制器 */
lcdif_clr(lcdif, LCDIF_CTRL, LCDIF_CTRL_RUN);
/* 注销 Framebuffer */
unregister_framebuffer(info);
/* 释放帧缓冲区 */
dma_free_wc(&pdev->dev, lcdif->fb_size,
lcdif->fb_virt, lcdif->fb_phys);
/* 禁用时钟 */
clk_disable_unprepare(lcdif->clk_pix);
clk_disable_unprepare(lcdif->clk_axi);
clk_disable_unprepare(lcdif->clk_disp_axi);
/* 释放 fb_info */
framebuffer_release(info);
dev_info(&pdev->dev, "LCD 驱动已卸载\n");
return 0;
}
/* ── 设备树匹配 ──────────────────────────────────────────────────── */
static const struct of_device_id imx6ull_lcdif_of_match[] = {
{ .compatible = "fsl,imx6ul-lcdif" },
{ .compatible = "fsl,imx6ull-lcdif" },
{}
};
MODULE_DEVICE_TABLE(of, imx6ull_lcdif_of_match);
static struct platform_driver imx6ull_lcdif_driver = {
.probe = imx6ull_lcdif_probe,
.remove = imx6ull_lcdif_remove,
.driver = {
.name = "imx6ull-lcdif",
.of_match_table = imx6ull_lcdif_of_match,
},
};
module_platform_driver(imx6ull_lcdif_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("i.MX6ULL eLCDIF LCD 控制器驱动");
18.3.4 设备树配置
/* 设备树中的 LCD 配置 */
/ {
/* LCD 控制器节点 */
lcdif: lcdif@21c8000 {
compatible = "fsl,imx6ull-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";
/* 背光使能 GPIO */
backlight-gpio = <&gpio1 8 GPIO_ACTIVE_HIGH>;
/* LCD 时序参数 */
display = <&display0>;
status = "okay";
};
/* LCD 显示参数 */
display0: display {
bits-per-pixel = <32>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
/* 4.3寸 800×480 LCD 时序 */
timing0: timing0 {
clock-frequency = <33000000>; /* 33MHz 像素时钟 */
hactive = <800>; /* 水平有效像素 */
vactive = <480>; /* 垂直有效行数 */
hfront-porch = <210>; /* HFP */
hback-porch = <46>; /* HBP */
hsync-len = <1>; /* HSW */
vfront-porch = <22>; /* VFP */
vback-porch = <23>; /* VBP */
vsync-len = <1>; /* VSW */
hsync-active = <0>; /* HSYNC 低有效 */
vsync-active = <0>; /* VSYNC 低有效 */
de-active = <1>; /* DE 高有效 */
pixelclk-active = <0>; /* CLK 下降沿采样 */
};
};
};
};
18.3.5 驱动测试与验证
# 加载驱动
sudo insmod imx6ull_lcdif.ko
# 查看帧缓冲设备
ls /dev/fb*
# /dev/fb0
# 查看屏幕信息
fbset -i
# mode "800x480-60"
# # D: 33.000 MHz, H: 60.000 kHz, V: 60.000 Hz
# geometry 800 480 800 960 32
# timings 30303 46 210 23 22 1 1
# rgba 8/16,8/8,8/0,8/24
# endmode
#
# Frame buffer device information:
# Name : imx6ull-lcdif
# Address : 0x83000000
# Size : 3072000
# Type : PACKED PIXELS
# Visual : TRUECOLOR
# XPanStep : 0
# YPanStep : 1
# YWrapStep : 0
# LineLength : 3200
# Accelerator : No
# 显示测试图案
cat /dev/urandom > /dev/fb0 &
sleep 2
kill %1
# 清屏(黑色)
dd if=/dev/zero of=/dev/fb0 bs=1024 count=3000
# 使用 fbi 显示图片
fbi -d /dev/fb0 -T 1 test.jpg
# 使用 mplayer 播放视频(通过 fbdev)
mplayer -vo fbdev2:/dev/fb0 -vf scale=800:480 video.mp4
# 测试双缓冲(pan display)
# 通过 ioctl FBIOPAN_DISPLAY 切换缓冲区
# 查看内核日志
dmesg | grep lcdif
# imx6ull-lcdif 21c8000.lcdif: LCD 驱动加载成功:800×480,/dev/fb0
# 查看帧缓冲信息
cat /sys/class/graphics/fb0/name
# imx6ull-lcdif
cat /sys/class/graphics/fb0/virtual_size
# 800,960
# 背光控制(通过 sysfs)
# echo 0 > /sys/class/backlight/backlight/brightness # 关闭背光
# echo 100 > /sys/class/backlight/backlight/brightness # 最大亮度
18.3.6 常见问题与调试
# 问题一:屏幕无显示
# 检查时序参数是否正确
cat /proc/fb
# 0 imx6ull-lcdif
# 检查像素时钟
cat /sys/kernel/debug/clk/lcdif_pix/clk_rate
# 33000000 ← 33MHz
# 问题二:显示颜色错误
# 检查像素格式配置
fbset | grep rgba
# rgba 8/16,8/8,8/0,8/24 ← ARGB8888
# 问题三:屏幕闪烁
# 检查时序参数(HBP/HFP/VBP/VFP)是否符合 LCD 规格书
# 问题四:帧缓冲地址错误
# 检查 DMA 内存分配
dmesg | grep "dma_alloc"
# 使用 devmem 直接读写寄存器(调试用)
devmem 0x021c8000 32 # 读取 LCDIF_CTRL 寄存器
devmem 0x021c80B0 32 # 读取 LCDIF_VDCTRL4 寄存器
本章小结
| 章节 | 核心知识点 | 关键 API |
|---|---|---|
| 18.1 LCD驱动基础 | LCD显示系统组成;5种接口类型(RGB/MIPI DSI/LVDS/SPI/I2C);时序参数(HSW/HBP/HFP/VSW/VBP/VFP/PCLK);像素格式(RGB565/RGB888/ARGB8888);帧缓冲区原理;双缓冲机制 | 概念理解 |
| 18.2 Linux Framebuffer驱动 | Framebuffer体系结构图;fb_info/fb_var_screeninfo/fb_fix_screeninfo/fb_ops四大核心结构体;注册流程(framebuffer_alloc→register_framebuffer);用户空间访问(mmap/ioctl);常用ioctl命令 | framebuffer_alloc()、register_framebuffer()、FBIOGET_VSCREENINFO、FBIOPAN_DISPLAY |
| 18.3 LCD控制器驱动实例 | i.MX6ULL eLCDIF特性;完整寄存器定义(CTRL/VDCTRL0-4等);硬件初始化(复位/时序配置/控制配置);完整LCD控制器驱动(probe/fb_ops/中断处理/双缓冲);设备树配置(display-timings);测试命令(fbset/fbi/mplayer) | dma_alloc_wc()、cfb_fillrect()、snd_pcm_period_elapsed() |
LCD 驱动开发要点
1. 时序参数配置
严格按照 LCD 规格书配置 HSW/HBP/HFP/VSW/VBP/VFP
像素时钟 = (HSW+HBP+HACTIVE+HFP) × (VSW+VBP+VACTIVE+VFP) × 帧率
2. 帧缓冲区内存
使用 dma_alloc_wc(写合并)分配,提高写性能
双缓冲:分配两倍大小,通过 pan_display 切换
3. 像素格式
RGB565:16位,节省内存,颜色精度低
ARGB8888:32位,颜色精度高,支持透明度
4. 双缓冲实现
yres_virtual = yres × 2
ypanstep = 1
通过 FBIOPAN_DISPLAY ioctl 切换 yoffset
5. 背光控制
使用 GPIO 控制背光使能
使用 PWM 控制背光亮度
通过 /sys/class/backlight/ 接口暴露给用户空间
6. 调试方法
fbset 查看屏幕参数
cat /dev/urandom > /dev/fb0 测试显示
devmem 直接读写寄存器
dmesg 查看驱动日志
参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年
更多推荐

所有评论(0)