03-LCD驱动.md 29 KB


title: Linux LCD驱动开发 tags: [embedded, linux, driver, lcd, framebuffer] created: 2026-09-17 updated: 2026-09-17

pdf_ref: "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第五十九章 Linux LCD驱动实验"

Linux LCD驱动开发

LCD硬件概述

📖 章节说明:本节 LCD 原理与 RGB 时序内容对应《指南》第二十四章《RGBLCD显示实验》(原书第五十九章仅引用之),属相邻章节补充。

LCD工作原理

LCD(Liquid Crystal Display)即液晶显示器,通过控制液晶分子的排列来实现显示。LCD面板由背光源、偏光片、液晶层、彩色滤光片和驱动电路组成。每个像素由红(R)、绿(G)、蓝(B)三个子像素组成,通过控制各子像素的透光量来混合出不同颜色。

LCD工作时,背光源提供均匀的白色光源,液晶分子在电场作用下旋转,改变光线的偏振方向。偏光片只允许特定方向的光线通过,从而控制每个像素的亮暗程度。TFT(Thin Film Transistor)LCD为每个像素配备独立的薄膜晶体管,实现像素级的精确控制。

RGB接口时序

RGB接口是并行数据传输接口,通过同步信号(HSYNC、VSYNC)和数据信号(RGB数据线)传输图像数据。关键时序参数:

参数 符号 说明 典型值(1024×600)
水平同步脉宽 hsync-len (HSPW) HSYNC高电平持续时钟周期 20
水平后肩 hback-porch (HBP) HSYNC下降沿到有效数据的时钟数 140
水平有效像素 hactive 每行有效像素个数 1024
水平前肩 hfront-porch (HFP) 有效数据结束到下一个HSYNC上升沿 160
垂直同步脉宽 vsync-len (VSPW) VSYNC高电平持续行数 3
垂直后肩 vback-porch (VBP) VSYNC下降沿到有效行的行数 20
垂直有效行 vactive 每帧有效行数 600
垂直前肩 vfront-porch (VFP) 有效行结束到下一个VSYNC上升沿 12

像素格式

格式 位宽 R G B 总线宽度 应用场景
RGB565 16bit 5bit 6bit 5bit 16bit 低成本嵌入式
RGB666 18bit 6bit 6bit 6bit 18bit 中端设备
RGB888 24bit 8bit 8bit 8bit 24bit 高质量显示
ARGB8888 32bit 8bit 8bit 8bit+Alpha 32bit GUI合成

Linux LCD子系统

LCD子系统架构

flowchart TB
    accTitle: Linux LCD Subsystem Architecture
    accDescr: Linux LCD子系统从用户空间到硬件的层次架构

    subgraph 用户空间
        app["🖥️ 应用程序"]
        fbset["⚙️ fbset工具"]
    end

    subgraph 内核空间
        vfs["📁 VFS层"]
        fbmem["📄 fbmem.c"]
        fops["🔧 fb_ops操作集"]
        driver["🖥️ LCD控制器驱动"]
        dt["📋 设备树"]
    end

    subgraph 硬件层
        lcdif["⚡ eLCDIF控制器"]
        panel["🖥️ LCD面板"]
    end

    app -->|"open/write/ioctl/mmap"| vfs
    fbset -->|"ioctl"| fbmem
    vfs --> fbmem
    fbmem --> fops
    fops --> driver
    dt -->|"解析屏幕参数"| driver
    driver --> lcdif
    lcdif -->|"RGB数据+同步信号"| panel

framebuffer设备

Framebuffer(帧缓冲)是Linux内核对显示设备的抽象。它将显示硬件虚拟为一个字符设备/dev/fbX,用户空间程序通过标准文件操作(open/read/write/ioctl/mmap)即可访问显示设备。

fb设备的核心操作集定义在drivers/video/fbdev/core/fbmem.c

static const struct file_operations fb_fops = {
    .owner          = THIS_MODULE,
    .read           = fb_read,
    .write          = fb_write,
    .unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = fb_compat_ioctl,
#endif
    .mmap           = fb_mmap,
    .open           = fb_open,
    .release        = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
    .get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
    .fsync          = fb_deferred_io_fsync,
#endif
    .llseek         = default_llseek,
};

核心数据结构

Linux内核将每个Framebuffer设备抽象为fb_info结构体,定义在include/linux/fb.h

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
    struct backlight_device *bl_dev;         /* 背光设备 */
    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位调色板 */
};

原书第五十九章重点关注的成员为 varfixfbopsscreen_basescreen_sizepseudo_palette

⚠️ 来源说明:下面的 fb_var_screeninfofb_fix_screeninfo 结构体定义取自内核 include/linux/fb.h原书第五十九章并未逐字段列出,此处为扩展补充。

fb_var_screeninfo存储可变的显示参数:

struct fb_var_screeninfo {
    __u32 xres;                  /* 可见分辨率-水平 */
    __u32 yres;                  /* 可见分辨率-垂直 */
    __u32 xres_virtual;          /* 虚拟分辨率-水平 */
    __u32 yres_virtual;          /* 虚拟分辨率-垂直 */
    __u32 bits_per_pixel;        /* 每像素位数 */
    __u32 grayscale;             /* 0=彩色, 1=灰度 */
    struct fb_bitfield red;      /* 红色位域 */
    struct fb_bitfield green;    /* 绿色位域 */
    struct fb_bitfield blue;     /* 蓝色位域 */
    struct fb_bitfield transp;   /* 透明度位域 */
    __u32 width;                 /* 画面宽度(mm) */
    __u32 height;                /* 画面高度(mm) */
};

fb_fix_screeninfo存储固定的显示参数:

struct fb_fix_screeninfo {
    char id[16];                 /* 标识符 */
    unsigned long smem_start;    /* 显存物理起始地址 */
    __u32 smem_len;              /* 显存长度 */
    __u32 type;                  /* FB类型 */
    __u32 visual;                /* 视觉类型 */
    __u32 line_length;           /* 每行字节数 */
    unsigned long mmio_start;    /* MMIO起始地址 */
    __u32 mmio_len;              /* MMIO长度 */
};

核心数据结构关系

flowchart LR
    accTitle: fb_info Data Structure Relationships
    accDescr: fb_info及其子结构体的关联关系

    fi["📦 fb_info"]
    var["📊 var (可变参数)"]
    fix["🔒 fix (固定参数)"]
    ops["🔧 fb_ops (操作集)"]
    screen["💾 screen_base (显存)"]

    fi --> var
    fi --> fix
    fi --> ops
    fi --> screen

    var --> res["xres × yres × bpp"]
    fix --> mem["smem_start + smem_len"]
    ops --> fbfops["fb_check_var / fb_set_par / fb_mmap"]

LCD设备树配置

display-timings配置

LCD屏幕参数通过设备树的display-timings节点配置,核心属性:

属性 说明 示例值
clock-frequency 像素时钟频率(Hz) 51200000 (51.2MHz)
hactive 水平有效像素数 1024
vactive 垂直有效行数 600
hfront-porch 水平前肩(HFP) 160
hback-porch 水平后肩(HBP) 140
hsync-len 水平同步脉宽(HSPW) 20
vfront-porch 垂直前肩(VFP) 12
vback-porch 垂直后肩(VBP) 20
vsync-len 垂直同步脉宽(VSPW) 3
hsync-active HSYNC极性(0/1) 0
vsync-active VSYNC极性(0/1) 0
de-active DE极性(0/1) 1
pixelclk-active 像素时钟极性(0/1) 0

pinctrl配置

LCD需要配置数据线和控制线两组引脚。以I.MX6ULL 24位RGB888为例:

pinctrl_lcdif_dat: lcdifdatgrp {
    fsl,pins = <
        MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x49
        MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x49
        MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x49
        MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x49
        MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x49
        MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x49
        MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x49
        MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x49
        MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x49
        MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x49
        MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x49
        MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x49
        MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x49
        MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x49
        MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x49
        MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x49
        MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x49
        MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x49
        MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x49
        MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x49
        MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x49
        MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x49
        MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x49
        MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x49
    >;
};

pinctrl_lcdif_ctrl: lcdifctrlgrp {
    fsl,pins = <
        MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x49
        MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x49
        MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x49
        MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x49
    >;
};

注意:电气属性值0x49表示降低驱动能力至R0/1,用于避免模拟开关影响信号完整性。

lcdif 控制器节点(imx6ull.dtsi)

由芯片级 imx6ull.dtsi 提供,所有 I.MX6ULL 板子共有,compatible 决定其由 mxsfb.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";
};

完整lcdif节点示例

说明:原书 NXP 官方默认节点为 4.3寸 480×272 屏(clock-frequency=<9200000>hfront-porch=<8>hback-porch=<4>hsync-len=<41>vback-porch=<2>vfront-porch=<4>vsync-len=<10>bits-per-pixel=<16>),本教程需替换为 ATK7016(7寸 1024×600)参数。

&lcdif {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_lcdif_dat
                 &pinctrl_lcdif_ctrl>;
    display = <&display0>;
    status = "okay";

    display0: display {
        bits-per-pixel = <24>;       /* RGB888模式 */
        bus-width = <24>;            /* 总线宽度 */

        display-timings {
            native-mode = <&timing0>;
            timing0: timing0 {
                clock-frequency = <51200000>;  /* 51.2MHz像素时钟 */
                hactive = <1024>;              /* 水平有效像素 */
                vactive = <600>;               /* 垂直有效行 */
                hfront-porch = <160>;          /* HFP */
                hback-porch = <140>;           /* HBP */
                hsync-len = <20>;              /* HSPW */
                vback-porch = <20>;            /* VBP */
                vfront-porch = <12>;           /* VFP */
                vsync-len = <3>;               /* VSPW */

                hsync-active = <0>;            /* HSYNC低有效 */
                vsync-active = <0>;            /* VSYNC低有效 */
                de-active = <1>;               /* DE高有效 */
                pixelclk-active = <0>;         /* CLK上升沿采样 */
            };
        };
    };
};

背光节点配置

背光控制引脚为 GPIO1_IO08,复用为 PWM1_OUT,需要三处配置:引脚 pinctrl、&pwm1 节点、backlight 节点。

/* 1. GPIO1_IO08 引脚复用为 PWM1_OUT */
pinctrl_pwm1: pwm1grp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
    >;
};

/* 2. 使能 pwm1 */
&pwm1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_pwm1>;
    status = "okay";
};

/* 3. 将 PWM1 与 LCD 背光关联 */
backlight {
    compatible = "pwm-backlight";
    pwms = <&pwm1 0 5000000>;     /* PWM1, 周期5ms(200Hz) */
    brightness-levels = <0 4 8 16 32 64 128 255>;
    default-brightness-level = <6>;
    status = "okay";
};

原书说明:brightness-levels 共 8 级(0~7),对应占空比 0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%;default-brightness-level = <6> 即 50.19% 亮度。PWM 背光驱动文件为 drivers/video/backlight/pwm_bl.c,设备树绑定参考 Documentation/devicetree/bindings/video/backlight/pwm-backlight.txt

LCD驱动API

framebuffer注册函数

register_framebuffer()向内核注册Framebuffer设备:

int register_framebuffer(struct fb_info *fb_info);
参数 说明
fb_info 已初始化的fb_info结构体指针
返回值 0=成功, 负值=失败码

配套的注销函数:

int unregister_framebuffer(struct fb_info *fb_info);

fb_ops操作集实现

LCD驱动需要实现fb_ops结构体中的关键操作函数:

static struct fb_ops lcd_fb_ops = {
    .owner          = THIS_MODULE,
    .fb_check_var   = lcd_check_var,     /* 检查显示参数 */
    .fb_set_par     = lcd_set_par,       /* 设置显示参数 */
    .fb_setcolreg   = lcd_setcolreg,     /* 设置颜色寄存器 */
    .fb_blank       = lcd_blank,         /* 屏幕空白控制 */
    .fb_pan_display = lcd_pan_display,   /* 平移显示(双缓冲) */
    .fb_mmap        = lcd_mmap,          /* 显存映射 */
    .fb_fillrect    = cfb_fillrect,      /* 矩形填充 */
    .fb_copyarea    = cfb_copyarea,      /* 区域拷贝 */
    .fb_imageblit   = cfb_imageblit,     /* 图像绘制 */
};

fb_check_var — 参数校验

在应用设置新参数前调用,校验参数合法性:

static int lcd_check_var(struct fb_var_screeninfo *var,
                         struct fb_info *info)
{
    /* 检查像素时钟是否超出硬件范围 */
    if (var->pixclock < LCD_MIN_PIXCLOCK)
        return -EINVAL;

    /* 检查分辨率是否超出LCD控制器支持范围 */
    if (var->xres > LCD_MAX_XRES || var->yres > LCD_MAX_YRES)
        return -EINVAL;

    /* 检查色深支持 */
    if (var->bits_per_pixel != 16 && var->bits_per_pixel != 24)
        return -EINVAL;

    return 0;
}

fb_set_par — 参数生效

将校验后的参数写入LCD控制器寄存器:

static int lcd_set_par(struct fb_info *info)
{
    struct lcd_controller *ctrl = info->par;

    /* 设置扫描方向 */
    /* 设置像素格式(RGB565/RGB888) */
    /* 配置时序参数寄存器 */
    /* 使能LCD控制器 */

    return 0;
}

fb_blank — 屏幕空白

控制LCD的电源和背光状态:

static int lcd_blank(int blank_mode, struct fb_info *info)
{
    switch (blank_mode) {
    case FB_BLANK_UNBLANK:    /* 正常显示 */
        /* 开启背光, 使能LCD */
        break;
    case FB_BLANK_NORMAL:     /* 待机(低功耗) */
    case FB_BLANK_VSYNC_SUSPEND:
    case FB_BLANK_HSYNC_SUSPEND:
    case FB_BLANK_POWERDOWN:  /* 关闭背光, 关闭LCD */
        break;
    }
    return 0;
}

fb_pan_display — 双缓冲切换

用于实现双缓冲或滚动显示:

static int lcd_pan_display(struct fb_var_screeninfo *var,
                           struct fb_info *info)
{
    /* 设置显存起始地址寄存器 */
    /* 切换显示缓冲区 */
    return 0;
}

完整源码分析

I.MX6ULL LCD控制器驱动

I.MX6ULL的LCD驱动文件位于drivers/video/fbdev/mxsfb.c,这是一个标准的platform驱动。

platform驱动注册:

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);

mxsfb_probe核心流程:

flowchart TB
    accTitle: mxsfb_probe Initialization Flow
    accDescr: LCD驱动probe函数的初始化流程

    start["🚀 probe入口"] --> get_res["📥 获取资源"]
    get_res --> alloc_host["💾 分配host结构体"]
    alloc_host --> alloc_fb["📦 分配fb_info"]
    alloc_fb --> request_irq["⚡ 申请中断"]
    request_irq --> ioremap["🗺️ 寄存器映射"]
    ioremap --> init_fbinfo["📋 初始化fb_info"]
    init_fbinfo --> init_ctrl["⚙️ 初始化控制器"]
    init_ctrl --> register["✅ 注册framebuffer"]
    register --> end["🎉 probe完成"]

probe函数关键代码解析:

static int mxsfb_probe(struct platform_device *pdev)
{
    const struct of_device_id *of_id =
            of_match_device(mxsfb_dt_ids, &pdev->dev);
    struct resource *res;
    struct mxsfb_info *host;
    struct fb_info *fb_info;
    struct pinctrl *pinctrl;
    int irq = platform_get_irq(pdev, 0);
    int gpio, ret;

    /* 1. 获取寄存器资源首地址(0x021C8000) */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENODEV;

    /* 2. 分配mxsfb_info结构体 */
    host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
    if (!host)
        return -ENOMEM;

    /* 3. 分配fb_info结构体 */
    fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
    if (!fb_info) {
        devm_kfree(&pdev->dev, host);
        return -ENOMEM;
    }
    host->fb_info = fb_info;    /* host->fb_info */
    fb_info->par = host;        /* fb_info->par 关联host */

    /* 4. 申请中断 */
    ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
                           dev_name(&pdev->dev), host);
    if (ret)
        goto fb_release;

    /* 5. 寄存器地址映射 */
    host->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(host->base)) {
        ret = PTR_ERR(host->base);
        goto fb_release;
    }

    /* 6. 分配pseudo_palette调色板 */
    fb_info->pseudo_palette = devm_kzalloc(&pdev->dev,
                                           sizeof(u32) * 16, GFP_KERNEL);
    if (!fb_info->pseudo_palette) {
        ret = -ENOMEM;
        goto fb_release;
    }

    INIT_LIST_HEAD(&fb_info->modelist);
    pm_runtime_enable(&host->pdev->dev);

    /* 7. 初始化fb_info (从设备树获取参数,并申请显存) */
    ret = mxsfb_init_fbinfo(host);
    if (ret != 0)
        goto fb_pm_runtime_disable;

    mxsfb_dispdrv_init(pdev, fb_info);

    /* 8. 配置pinctrl引脚 */
    if (!host->dispdrv) {
        pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
        if (IS_ERR(pinctrl)) {
            ret = PTR_ERR(pinctrl);
            goto fb_pm_runtime_disable;
        }
    }

    /* 9. 初始化LCD控制器寄存器 */
    if (!host->enabled) {
        writel(0, host->base + LCDC_CTRL);
        mxsfb_set_par(fb_info);
        mxsfb_enable_controller(fb_info);
        pm_runtime_get_sync(&host->pdev->dev);
    }

    /* 10. 注册framebuffer设备 */
    ret = register_framebuffer(fb_info);
    if (ret != 0)
        goto fb_destroy;

    return ret;
}

eLCDIF寄存器偏移定义:

#define LCDC_CTRL               0x00
#define LCDC_CTRL1              0x10
#define LCDC_V4_CTRL2           0x20
#define LCDC_V3_TRANSFER_COUNT  0x20
#define LCDC_V4_TRANSFER_COUNT  0x30
#define LCDC_V4_CUR_BUF         0x40
#define LCDC_V4_NEXT_BUF        0x44
#define LCDC_V4_DTDMA           0x180
#define LCDC_V4_DEBUG0          0x1d0
#define LCDC_V3_DEBUG0          0x1f0

原书提示:可对照《I.MX6ULL 参考手册》eLCDIF 章节核对这些偏移值。

mxsfb_ops操作集:

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,
};

LCD驱动初始化时序

sequenceDiagram
    accTitle: LCD Driver Initialization Sequence
    accDescr: LCD驱动从设备探测到显示就绪的调用时序

    participant P as platform总线
    participant D as mxsfb_driver
    participant F as fb_info
    participant C as eLCDIF控制器

    P->>D: 设备树匹配(mxsfb_dt_ids)
    D->>D: mxsfb_probe()
    D->>F: framebuffer_alloc()
    D->>D: devm_ioremap_resource()
    D->>F: mxsfb_init_fbinfo()
    Note over F: 从设备树解析时序参数
    D->>C: mxsfb_set_par()
    Note over C: 配置时序/像素格式/时钟
    D->>C: mxsfb_enable_controller()
    Note over C: 使能LCD控制器
    D->>D: register_framebuffer()
    Note over D: 创建/dev/fb0

实验验证

⚠️ 来源说明:原书第五十九章使用 make dtbs、Linux logo、/dev/tty1、背光 sysfs 等方式验证;本节中的 fbset 命令不属于原书内容,为扩展知识。

fbset命令

fbset工具用于查看和修改Framebuffer设备参数:

# 查看当前fb0参数
fbset -fb /dev/fb0

# 查看详细信息
fbset -fb /dev/fb0 -i

# 修改分辨率(需要驱动支持)
fbset -fb /dev/fb0 -g 1024 600 1024 600 24

测试方法

1. 显示Linux Logo

内核启动时自动显示小企鹅logo,配置选项:

Device Drivers → Graphics support → Bootup logo
  ├── Standard black and white Linux logo
  ├── Standard 16-color Linux logo
  └── Standard 224-color Linux logo

2. 输出测试字符到LCD终端

# 将字符串输出到LCD终端
echo "Hello Linux LCD!" > /dev/tty1

# 设置LCD为控制台
setenv bootargs 'console=tty1 console=ttymxc0,115200 ...'

# 修改/etc/inittab添加LCD终端
tty1::askfirst:-/bin/sh

3. 背光调节

# 查看当前背光等级
cat /sys/devices/platform/backlight/backlight/backlight/brightness

# 设置背光等级(0~7)
echo 7 > /sys/devices/platform/backlight/backlight/backlight/brightness

# 关闭背光(熄屏)
echo 0 > /sys/devices/platform/backlight/backlight/backlight/brightness

4. 禁用自动熄屏

修改内核源码drivers/tty/vt/vt.c中的blankinterval变量:

static int blankinterval = 0;  /* 原值为10*60(10分钟), 改为0禁用 */

或编写应用程序禁用:

#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd = open("/dev/tty1", O_RDWR);
    write(fd, "\033[9;0]", 8);  /* 禁用屏幕保护 */
    close(fd);
    return 0;
}

跨平台对比

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

特性 I.MX6ULL STM32F7 RK3568
LCD控制器 eLCDIF LTDC VOP
最大分辨率 1366×768 1024×1024 4K×2K
数据接口 RGB888/RGB565 RGB888/RGB565 RGB/MIPI-DSI/eDP
显存管理 内核CMA 外部SDRAM 内核CMA
驱动文件 mxsfb.c stm32-ltdc.c rockchip_vop.c
设备树节点 lcdif@021c8000 ltdc@40016800 vop@fe040000
时钟配置 LCDIF_PIX + LCDIF_APB LTDC_CLK DCLK_VOP
背光控制 PWM + backlight节点 PWM + backlight节点 PWM + backlight节点

关键差异分析

  • I.MX6ULL:eLCDIF控制器较为简单,仅支持RGB并行接口,驱动代码在mxsfb.c
  • STM32F7:LTDC控制器支持层叠显示(Layer),硬件支持Alpha混合
  • RK3568:VOP控制器功能最强大,支持多路显示输出,兼容MIPI-DSI/HDMI/eDP

面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。

Q1: Framebuffer设备的本质是什么?用户空间如何访问?

:Framebuffer是Linux内核对显示设备的字符设备抽象,设备节点为/dev/fbX。用户空间通过标准文件操作访问:

  • open():打开fb设备
  • ioctl():获取/设置显示参数(如FBIOPUT_VSCREENINFO)
  • mmap():将显存映射到用户空间,直接读写像素数据
  • read()/write():读写显存内容

核心是fb_info结构体,包含可变参数(var)、固定参数(fix)、操作集(fbops)和显存指针(screen_base)。

Q2: LCD的RGB接口中HBP、HFP、HSPW分别是什么含义?如何计算像素时钟?

  • HBP(Horizontal Back Porch):HSYNC下降沿到有效像素数据开始的时钟周期数
  • HFP(Horizontal Front Porch):有效像素数据结束到下一个HSYNC上升沿的时钟周期数
  • HSPW(HSYNC Pulse Width):HSYNC高电平持续的时钟周期数
  • VBP/VFP/VSPW:垂直方向的对应参数(单位为行)

像素时钟计算公式:

Pixel_Clock = (HBP + Hactive + HFP) × (VBP + Vactive + VFP) × Refresh_Rate

例如1024×600@60Hz:(140 + 1024 + 160) × (20 + 600 + 12) × 60 ≈ 51.2MHz

Q3: fb_info中的var和fix有什么区别?各自包含哪些关键信息?

  • var(fb_var_screeninfo):可变参数,应用程序可修改。包含分辨率(xres/yres)、虚拟分辨率(xres_virtual/yres_virtual)、色深(bits_per_pixel)、RGB位域等
  • fix(fb_fix_screeninfo):固定参数,由驱动设置,应用程序只读。包含显存物理地址(smem_start)、显存长度(smem_len)、每行字节数(line_length)、MMIO地址等

驱动在fb_check_var()中校验var参数,在fb_set_par()中将参数写入硬件寄存器。

Q4: LCD驱动的probe函数中,为什么需要同时分配host结构体和fb_info?

  • fb_info:是Linux Framebuffer框架要求的标准结构体,包含框架所需的所有信息(var、fix、fbops、显存等)
  • host(如mxsfb_info):是具体LCD控制器的私有数据结构,包含硬件相关的特殊信息(寄存器基地址、时钟、中断号、平台设备指针等)
  • 两者通过fb_info->parhost->fb_info相互关联
  • 这种设计分离了框架通用数据和硬件私有数据,是Linux驱动的通用模式

Q5: 如何解决LCD自动熄屏问题?有几种方案?

:Linux内核默认10分钟后熄屏(blankinterval = 10*60)。原书给出三种解决方案:

  1. 按键唤醒(最简单):原书第五十八章已将开发板 KEY 注册为回车键,按回车即可唤醒屏幕
  2. 修改内核源码:将drivers/tty/vt/vt.cblankinterval改为0,重新编译内核
  3. 应用程序方式:编写程序向/dev/tty1写入\033[9;0]转义序列禁用屏幕保护,并加入/etc/init.d/rcS实现开机自启动

补充(扩展):也可通过 make menuconfig 关闭 DPMS(Display Power Management Signaling)。原书推荐方案3,无需重新编译内核、部署灵活。


内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第59章 Linux LCD驱动实验(可参考第24章 RGBLCD显示实验)