4. 屏幕空白与恢复

任务目标

在Linux帧缓冲驱动中实现 fb_blank 回调,使上层应用通过 FBIOBLANK ioctl 控制屏幕空白(关闭显示)与恢复(重新点亮)。本案例以模拟背光控制的GPIO为例,演示回调的完整实现与验证路径。

核心实现

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fb.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>

#define DRIVER_NAME "fb_blank_demo"

struct fb_blank_dev {
    struct fb_info *fbi;
    struct gpio_desc *backlight_gpio;
};

static struct fb_blank_dev *g_dev;

static int fb_blank_demo_blank(int blank_mode, struct fb_info *info)
{
    struct fb_blank_dev *dev = platform_get_drvdata(to_platform_device(info->device));

    switch (blank_mode) {
    case FB_BLANK_UNBLANK:  /* 0: 恢复显示 */
        gpiod_set_value(dev->backlight_gpio, 1);
        dev_info(info->device, "Display unblanked (backlight ON)\n");
        break;
    case FB_BLANK_NORMAL:   /* 1: 仅关背光 */
    case FB_BLANK_VSYNC_SUSPEND:
    case FB_BLANK_HSYNC_SUSPEND:
    case FB_BLANK_POWERDOWN: /* 4: 完全关闭 */
        gpiod_set_value(dev->backlight_gpio, 0);
        dev_info(info->device, "Display blanked (backlight OFF, mode=%d)\n", blank_mode);
        break;
    default:
        return -EINVAL;
    }
    return 0;
}

static int fb_blank_demo_probe(struct platform_device *pdev)
{
    struct fb_info *fbi;
    struct fb_blank_dev *dev;
    int ret;

    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    /* 获取背光控制GPIO(设备树中定义,active-high) */
    dev->backlight_gpio = devm_gpiod_get(&pdev->dev, "backlight", GPIOD_OUT_HIGH);
    if (IS_ERR(dev->backlight_gpio)) {
        ret = PTR_ERR(dev->backlight_gpio);
        dev_err(&pdev->dev, "Failed to get backlight GPIO: %d\n", ret);
        return ret;
    }

    /* 分配帧缓冲设备 */
    fbi = framebuffer_alloc(0, &pdev->dev);
    if (!fbi)
        return -ENOMEM;

    fbi->fbops = kzalloc(sizeof(struct fb_ops), GFP_KERNEL);
    if (!fbi->fbops) {
        framebuffer_release(fbi);
        return -ENOMEM;
    }

    /* 仅注册fb_blank回调,其余操作由真实驱动提供 */
    fbi->fbops->fb_blank = fb_blank_demo_blank;
    fbi->var.xres = 800;
    fbi->var.yres = 600;
    fbi->var.bits_per_pixel = 32;
    fbi->fix.smem_len = 800 * 600 * 4;
    fbi->screen_base = (char __iomem *)devm_kzalloc(&pdev->dev,
                            fbi->fix.smem_len, GFP_KERNEL);
    if (!fbi->screen_base) {
        kfree(fbi->fbops);
        framebuffer_release(fbi);
        return -ENOMEM;
    }
    strcpy(fbi->fix.id, "blank_demo");
    fbi->fix.type = FB_TYPE_PACKED_PIXELS;
    fbi->fix.visual = FB_VISUAL_TRUECOLOR;

    platform_set_drvdata(pdev, dev);
    dev->fbi = fbi;
    g_dev = dev;

    ret = register_framebuffer(fbi);
    if (ret) {
        kfree(fbi->fbops);
        framebuffer_release(fbi);
        return ret;
    }

    dev_info(&pdev->dev, "fb_blank demo registered\n");
    return 0;
}

static void fb_blank_demo_remove(struct platform_device *pdev)
{
    struct fb_blank_dev *dev = platform_get_drvdata(pdev);

    if (dev && dev->fbi) {
        unregister_framebuffer(dev->fbi);
        kfree(dev->fbi->fbops);
        framebuffer_release(dev->fbi);
    }
}

static const struct of_device_id fb_blank_demo_of_match[] = {
    { .compatible = "demo,fb-blank" },
    { }
};
MODULE_DEVICE_TABLE(of, fb_blank_demo_of_match);

static struct platform_driver fb_blank_driver = {
    .probe = fb_blank_demo_probe,
    .remove = fb_blank_demo_remove,
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = fb_blank_demo_of_match,
    },
};
module_platform_driver(fb_blank_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Demo");
MODULE_DESCRIPTION("FB blank callback demo");

设备树节点示例

&i2c0 {
    /* 实际项目中背光GPIO可能挂在GPIO控制器下 */
};

/* 平台设备节点 */
fb_blank_demo {
    compatible = "demo,fb-blank";
    backlight-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>;
};

编译与加载

# 内核树内编译(放入drivers/video/fbdev/)
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules

# 或独立模块编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载
insmod fb_blank_demo.ko

验证方法

  1. 加载模块后确认 /dev/fb* 出现新设备(如 /dev/fb1)
  2. 使用 fbset -fb /dev/fb1 查看当前显示参数
  3. 执行空白命令:fbset -fb /dev/fb1 -blank 1 或直接调用ioctl:
# 用户态测试程序(核心片段)
#include <linux/fb.h>
#include <fcntl.h>
#include <sys/ioctl.h>

int fd = open("/dev/fb1", O_RDWR);
int blank = FB_BLANK_POWERDOWN;  /* 4: 关闭 */
ioctl(fd, FBIOBLANK, blank);      /* 屏幕应熄灭 */

blank = FB_BLANK_UNBLANK;         /* 0: 恢复 */
ioctl(fd, FBIOBLANK, blank);      /* 屏幕应重新点亮 */

通过判定标准:执行空白ioctl后,背光GPIO输出为低电平(可用万用表或逻辑分析仪测量),屏幕熄灭;执行恢复ioctl后,GPIO恢复高电平,屏幕点亮。内核日志中应出现对应的 Display blanked 与 Display unblanked 信息。

关键说明

  • 本案例仅实现 fb_blank 回调及最小帧缓冲注册,用于演示空白控制流程;真实项目中 fb_info 的 var/fix 结构需与具体显示控制器匹配,fb_ops 中还需实现 fb_setcolreg、fb_fillrect 等必要操作。
  • 背光GPIO的极性由设备树中 GPIO_ACTIVE_HIGH/LOW 决定,本示例使用高电平点亮。
  • 若平台无真实GPIO可用,可将 gpiod_set_value 替换为寄存器操作或仅打印日志来验证回调被正确调用。