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
验证方法
- 加载模块后确认
/dev/fb*出现新设备(如/dev/fb1) - 使用
fbset -fb /dev/fb1查看当前显示参数 - 执行空白命令:
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替换为寄存器操作或仅打印日志来验证回调被正确调用。