《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_VSCREENINFOFBIOPAN_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年

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐