低成本物联网设备Linux使用ST7789-tft FrameBuffer驱动分析及添加全流程
本文介绍了一个完整的Linux SPI TFT显示驱动实现,针对ST7789控制器芯片。该驱动采用四层架构设计:应用层、FrameBuffer子系统层、SPI驱动层和硬件层。主要内容包括: 驱动核心数据结构与接口实现 定义了st7789_data私有数据结构,包含设备状态、显存和硬件资源 实现了设备探测、初始化、SPI通信等基础接口 提供FrameBuffer操作集(fb_ops)完整实现 SPI
·
一、驱动架构和核心数据结构
1.1 系统架构概述
Linux SPI TFT显示系统架构: 应用层(用户空间) ├── Logo显示应用 ├── GUI应用(Qt/DirectFB) └── 命令行工具 FrameBuffer子系统层 ├── /dev/fb0设备文件 ├── fb_ops操作集 └── fb_info结构体 SPI显示驱动层 ├── Linux SPI子系统接口 ├── ST7789寄存器控制 ├── DMA传输支持(可选) └── 背光/电源管理 硬件层 ├── SPI总线(CLK, MOSI, CS, DC, RST) ├── ST7789控制器 ├── 320×240 TFT面板 └── 背光LED 数据流: 应用 → FrameBuffer → SPI驱动 → ST7789 → TFT面板 写显存 调用 SPI传输 解析 显示
1.2 主要接口实现清单
/* drivers/video/fbdev/st7789_fb.c */
// 1. 设备探测与初始化接口
static int st7789_probe(struct spi_device *spi);
static int st7789_remove(struct spi_device *spi);
// 2. FrameBuffer操作集 (struct fb_ops)
static struct fb_ops st7789_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = st7789_check_var, // 检查变量参数
.fb_set_par = st7789_set_par, // 设置参数
.fb_setcolreg = st7789_setcolreg, // 设置颜色寄存器
.fb_blank = st7789_blank, // 空白显示
.fb_fillrect = st7789_fillrect, // 矩形填充(加速)
.fb_copyarea = st7789_copyarea, // 区域复制(加速)
.fb_imageblit = st7789_imageblit, // 图像位块传输
.fb_mmap = NULL, // 内存映射(SPI设备通常不支持)
.fb_ioctl = st7789_ioctl, // IOCTL控制
};
// 3. 硬件控制接口
static void st7789_write_cmd(struct st7789_data *priv, u8 cmd);
static void st7789_write_data(struct st7789_data *priv, u8 data);
static void st7789_write_buf(struct st7789_data *priv,
const u8 *buf, size_t len);
static void st7789_set_addr_window(struct st7789_data *priv,
u16 x0, u16 y0, u16 x1, u16 y1);
static void st7789_update_display(struct st7789_data *priv);
// 4. 显存管理接口
static int st7789_alloc_framebuffer(struct st7789_data *priv);
static void st7789_free_framebuffer(struct st7789_data *priv);
static void st7789_flush_framebuffer(struct st7789_data *priv);
// 5. 电源管理接口
static void st7789_power_on(struct st7789_data *priv);
static void st7789_power_off(struct st7789_data *priv);
static int st7789_suspend(struct device *dev);
static int st7789_resume(struct device *dev);
// 6. 背光控制接口
static void st7789_set_brightness(struct st7789_data *priv, u8 brightness);
// 7. Logo显示专用接口
static void st7789_display_logo(struct fb_info *info);
static int st7789_prepare_logo(struct st7789_data *priv);
// 8. 设备树支持接口
#ifdef CONFIG_OF
static const struct of_device_id st7789_of_match[];
#endif
1.3 驱动数据结构设计
/**
* @struct st7789_data
* @brief ST7789驱动私有数据结构
*
* 存储驱动状态、硬件资源引用和显存信息。
*/
struct st7789_data {
struct spi_device *spi; /**< SPI设备指针 */
struct device *dev; /**< 设备指针 */
struct fb_info *fb_info; /**< FrameBuffer信息 */
/* GPIO引脚 */
struct gpio_desc *reset_gpio; /**< 复位引脚 */
struct gpio_desc *dc_gpio; /**< 数据/命令选择引脚 */
struct gpio_desc *bl_gpio; /**< 背光控制引脚 */
/* 背光控制 */
struct backlight_device *bl_dev; /**< 背光设备 */
u8 brightness; /**< 当前亮度 (0-255) */
/* 显存管理 */
dma_addr_t fb_dma_addr; /**< 显存DMA地址 */
void *fb_virt_addr; /**< 显存虚拟地址 */
size_t fb_size; /**< 显存大小 */
/* 显示状态 */
bool powered_on; /**< 电源状态 */
bool sleeping; /**< 睡眠状态 */
u8 rotation; /**< 屏幕旋转 (0, 90, 180, 270) */
/* 同步保护 */
struct mutex lock; /**< 驱动锁,保护并发访问 */
struct work_struct update_work; /**< 显存更新工作队列 */
/* Logo支持 */
bool logo_displayed; /**< Logo已显示标志 */
const u8 *logo_data; /**< Logo数据指针 */
u32 logo_size; /**< Logo数据大小 */
};
/**
* @struct st7789_display_timing
* @brief 显示时序配置
*
* 配置ST7789的显示时序参数。
*/
struct st7789_display_timing {
u16 width; /**< 水平分辨率 */
u16 height; /**< 垂直分辨率 */
u16 hsync_len; /**< 水平同步长度 */
u16 vsync_len; /**< 垂直同步长度 */
u16 left_margin; /**< 左边界 */
u16 right_margin; /**< 右边界 */
u16 upper_margin; /**< 上边界 */
u16 lower_margin; /**< 下边界 */
};
二、SPI通信和硬件初始化
2.1 SPI通信基础函数
/**
* @brief 写入命令到ST7789
* @param priv 驱动私有数据
* @param cmd 命令字节
*
* 设置DC引脚为命令模式,通过SPI发送命令字节。
* 采用策略模式封装SPI命令写入流程。
*/
static void st7789_write_cmd(struct st7789_data *priv, u8 cmd)
{
int ret;
struct spi_transfer xfer = {
.tx_buf = &cmd,
.len = 1,
};
struct spi_message msg;
/* 设置为命令模式 */
gpiod_set_value(priv->dc_gpio, 0);
/* 准备SPI消息 */
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
/* 发送命令 */
ret = spi_sync(priv->spi, &msg);
if (ret < 0)
dev_err(priv->dev, "Failed to send command 0x%02x: %d\n", cmd, ret);
}
/**
* @brief 写入数据到ST7789
* @param priv 驱动私有数据
* @param data 数据字节
*
* 设置DC引脚为数据模式,通过SPI发送数据字节。
*/
static void st7789_write_data(struct st7789_data *priv, u8 data)
{
int ret;
struct spi_transfer xfer = {
.tx_buf = &data,
.len = 1,
};
struct spi_message msg;
/* 设置为数据模式 */
gpiod_set_value(priv->dc_gpio, 1);
/* 准备SPI消息 */
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
/* 发送数据 */
ret = spi_sync(priv->spi, &msg);
if (ret < 0)
dev_err(priv->dev, "Failed to send data 0x%02x: %d\n", data, ret);
}
/**
* @brief 设置显示地址窗口
* @param priv 驱动私有数据
* @param x0 起始X坐标
* @param y0 起始Y坐标
* @param x1 结束X坐标
* @param y1 结束Y坐标
*
* 设置ST7789的显示区域,后续的RAMWR操作将在此区域内进行。
* 这是FrameBuffer更新的关键函数。
*/
static void st7789_set_addr_window(struct st7789_data *priv,
u16 x0, u16 y0, u16 x1, u16 y1)
{
/* 设置列地址范围 */
st7789_write_cmd(priv, ST7789_CASET);
st7789_write_data16(priv, x0);
st7789_write_data16(priv, x1);
/* 设置行地址范围 */
st7789_write_cmd(priv, ST7789_RASET);
st7789_write_data16(priv, y0);
st7789_write_data16(priv, y1);
/* 准备内存写操作 */
st7789_write_cmd(priv, ST7789_RAMWR);
}
2.2 硬件初始化和配置函数
/**
* @brief 硬件复位ST7789
* @param priv 驱动私有数据
*
* 通过复位引脚执行硬件复位序列。
* 采用命令模式封装复位操作。
*/
static void st7789_hard_reset(struct st7789_data *priv)
{
if (!priv->reset_gpio)
return;
/* 复位序列 */
gpiod_set_value(priv->reset_gpio, 0);
msleep(10);
gpiod_set_value(priv->reset_gpio, 1);
msleep(120); /* ST7789需要至少120ms启动时间 */
}
/**
* @brief 初始化ST7789显示控制器
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 配置ST7789的所有必要寄存器,设置显示模式、颜色格式等。
* 采用建造者模式分步骤构建硬件初始化序列。
*/
static int st7789_init_display(struct st7789_data *priv)
{
dev_info(priv->dev, "Initializing ST7789 display\n");
/* 1. 硬件复位 */
st7789_hard_reset(priv);
/* 2. 退出睡眠模式 */
st7789_write_cmd(priv, ST7789_SLPOUT);
msleep(120);
/* 3. 设置颜色接口格式为16位RGB565 */
st7789_write_cmd(priv, ST7789_COLMOD);
st7789_write_data(priv, COLMOD_16BIT);
/* 4. 设置内存访问控制 */
st7789_write_cmd(priv, ST7789_MADCTL);
/* 默认配置:MY=0, MX=0, MV=0, ML=0, BGR=1, MH=0 */
st7789_write_data(priv, MADCTL_BGR);
/* 5. 设置显示反转关 */
st7789_write_cmd(priv, ST7789_INVOFF);
/* 6. 设置显示方向(根据rotation参数) */
switch (priv->rotation) {
case 90:
st7789_write_cmd(priv, ST7789_MADCTL);
st7789_write_data(priv, MADCTL_MX | MADCTL_MV | MADCTL_BGR);
break;
case 180:
st7789_write_cmd(priv, ST7789_MADCTL);
st7789_write_data(priv, MADCTL_MY | MADCTL_MX | MADCTL_BGR);
break;
case 270:
st7789_write_cmd(priv, ST7789_MADCTL);
st7789_write_data(priv, MADCTL_MY | MADCTL_MV | MADCTL_BGR);
break;
default: /* 0度 */
st7789_write_cmd(priv, ST7789_MADCTL);
st7789_write_data(priv, MADCTL_BGR);
break;
}
/* 7. 设置显示区域(全屏) */
st7789_set_addr_window(priv, 0, 0,
ST7789_WIDTH - 1,
ST7789_HEIGHT - 1);
/* 8. 退出空闲模式 */
st7789_write_cmd(priv, ST7789_IDMOFF);
/* 9. 打开正常显示 */
st7789_write_cmd(priv, ST7789_NORON);
msleep(10);
/* 10. 打开显示 */
st7789_write_cmd(priv, ST7789_DISPON);
msleep(100);
priv->powered_on = true;
dev_info(priv->dev, "ST7789 display initialized successfully\n");
return 0;
}
2.3 显存管理函数
/**
* @brief 分配FrameBuffer内存
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 分配DMA兼容的显存,用于存储要显示的图像数据。
* 采用资源管理器模式确保内存正确分配和释放。
*/
static int st7789_alloc_framebuffer(struct st7789_data *priv)
{
size_t fb_size;
/* 计算显存大小:320×240×2字节(RGB565) */
fb_size = ST7789_WIDTH * ST7789_HEIGHT * (ST7789_BPP / 8);
dev_info(priv->dev, "Allocating framebuffer: %zu bytes\n", fb_size);
/* 分配DMA兼容内存 */
priv->fb_virt_addr = dma_alloc_coherent(priv->dev, fb_size,
&priv->fb_dma_addr,
GFP_KERNEL | GFP_DMA);
if (!priv->fb_virt_addr) {
dev_err(priv->dev, "Failed to allocate framebuffer memory\n");
return -ENOMEM;
}
priv->fb_size = fb_size;
/* 清空显存(黑色) */
memset(priv->fb_virt_addr, 0, fb_size);
dev_info(priv->dev, "Framebuffer allocated at virt=%p, dma=%pad\n",
priv->fb_virt_addr, &priv->fb_dma_addr);
return 0;
}
/**
* @brief 刷新显存到显示控制器
* @param priv 驱动私有数据
*
* 将系统内存中的显存数据通过SPI传输到ST7789显示控制器。
* 这是性能关键函数,需要优化传输效率。
*/
static void st7789_flush_framebuffer(struct st7789_data *priv)
{
u16 *fb = (u16 *)priv->fb_virt_addr;
u8 *line_buffer;
int x, y;
int line_len;
if (!priv->powered_on)
return;
/* 每行像素数 × 每像素字节数(RGB565为2字节) */
line_len = ST7789_WIDTH * 2;
/* 分配行缓冲区 */
line_buffer = kmalloc(line_len, GFP_KERNEL);
if (!line_buffer) {
dev_err(priv->dev, "Failed to allocate line buffer\n");
return;
}
/* 设置全屏地址窗口 */
st7789_set_addr_window(priv, 0, 0,
ST7789_WIDTH - 1,
ST7789_HEIGHT - 1);
/* 逐行传输数据(优化SPI传输效率) */
for (y = 0; y < ST7789_HEIGHT; y++) {
u16 *line_start = fb + y * ST7789_WIDTH;
/* 转换为字节序列(保持RGB565格式) */
for (x = 0; x < ST7789_WIDTH; x++) {
u16 pixel = line_start[x];
line_buffer[x * 2] = (pixel >> 8) & 0xFF; /* 高字节 */
line_buffer[x * 2 + 1] = pixel & 0xFF; /* 低字节 */
}
/* 传输一行数据 */
st7789_write_buf(priv, line_buffer, line_len);
}
kfree(line_buffer);
}
三、FrameBuffer操作集实现
3.1 FrameBuffer操作集函数
/**
* @brief 检查FrameBuffer变量参数
* @param var 变量参数
* @param info FrameBuffer信息
* @return 成功返回0,失败返回错误码
*
* 验证应用程序请求的显示参数是否受硬件支持。
* 采用验证器模式确保参数有效性。
*/
static int st7789_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "check_var: xres=%u, yres=%u, bpp=%u\n",
var->xres, var->yres, var->bits_per_pixel);
/* 检查分辨率 */
if (var->xres > ST7789_WIDTH || var->yres > ST7789_HEIGHT) {
dev_err(priv->dev, "Unsupported resolution: %ux%u\n",
var->xres, var->yres);
return -EINVAL;
}
/* 检查颜色深度 */
if (var->bits_per_pixel != ST7789_BPP) {
dev_err(priv->dev, "Unsupported bpp: %u (must be %u)\n",
var->bits_per_pixel, ST7789_BPP);
return -EINVAL;
}
/* 设置支持的参数 */
var->xres = var->xres_virtual = ST7789_WIDTH;
var->yres = var->yres_virtual = ST7789_HEIGHT;
/* 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;
/* 显示时序(近似值) */
var->pixclock = 10000000; /* 10MHz像素时钟(近似)*/
var->left_margin = 10;
var->right_margin = 10;
var->upper_margin = 10;
var->lower_margin = 10;
var->hsync_len = 10;
var->vsync_len = 10;
/* 非隔行扫描 */
var->vmode = FB_VMODE_NONINTERLACED;
return 0;
}
/**
* @brief 设置FrameBuffer参数
* @param info FrameBuffer信息
* @return 成功返回0,失败返回错误码
*
* 应用新的显示参数,可能需要重新配置硬件。
*/
static int st7789_set_par(struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "set_par called\n");
/* 对于SPI TFT,通常参数是固定的 */
info->fix.line_length = info->var.xres * (info->var.bits_per_pixel / 8);
return 0;
}
/**
* @brief 设置颜色寄存器
* @param regno 寄存器编号
* @param red 红色分量
* @param green 绿色分量
* @param blue 蓝色分量
* @param transp 透明度分量
* @param info FrameBuffer信息
* @return 成功返回0
*
* ST7789使用硬件调色板,但FrameBuffer接口需要此函数。
*/
static int st7789_setcolreg(unsigned regno, unsigned red,
unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct st7789_data *priv = info->par;
/* ST7789使用RGB565硬件格式,无需软件调色板 */
if (regno >= 16) /* 只支持16个调色板条目 */
return -EINVAL;
/* 对于伪调色板模式,存储转换后的RGB565值 */
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
u32 *pal = info->pseudo_palette;
u32 val;
/* 将24位RGB转换为RGB565 */
red >>= 8;
green >>= 8;
blue >>= 8;
val = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
if (regno < 16)
pal[regno] = val;
}
return 0;
}
3.2 硬件加速图形操作
/**
* @brief 填充矩形区域
* @param info FrameBuffer信息
* @param rect 矩形区域信息
*
* 硬件加速的矩形填充操作,直接操作显存而不需要SPI传输。
* 采用策略模式优化不同大小的矩形填充。
*/
static void st7789_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct st7789_data *priv = info->par;
u16 *dst, *dst_start;
u32 color = rect->color;
int x, y;
/* 边界检查 */
if (rect->dx >= ST7789_WIDTH || rect->dy >= ST7789_HEIGHT)
return;
if (rect->dx + rect->width > ST7789_WIDTH)
rect->width = ST7789_WIDTH - rect->dx;
if (rect->dy + rect->height > ST7789_HEIGHT)
rect->height = ST7789_HEIGHT - rect->dy;
mutex_lock(&priv->lock);
dst_start = (u16 *)priv->fb_virt_addr;
/* 将颜色转换为RGB565格式(从伪调色板) */
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
if (color < 16) /* 使用伪调色板 */
color = info->pseudo_palette[color];
}
/* 逐行填充矩形 */
for (y = 0; y < rect->height; y++) {
dst = dst_start + (rect->dy + y) * ST7789_WIDTH + rect->dx;
/* 使用memset16优化填充(如果可用) */
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
if (rect->width >= 4) {
u32 color32 = color | (color << 16);
u32 *dst32 = (u32 *)dst;
int width32 = rect->width / 2;
/* 32位填充 */
for (x = 0; x < width32; x++)
dst32[x] = color32;
/* 处理剩余像素 */
if (rect->width & 1)
dst[rect->width - 1] = color;
} else
#endif
{
/* 标准16位填充 */
for (x = 0; x < rect->width; x++)
dst[x] = color;
}
}
/* 标记需要更新显示的区域 */
if (rect->width > 0 && rect->height > 0)
st7789_schedule_update(priv);
mutex_unlock(&priv->lock);
}
/**
* @brief 复制显示区域
* @param info FrameBuffer信息
* @param area 区域信息
*
* 硬件加速的区域复制操作,用于窗口移动等操作。
* 实现memmove风格的复制,处理重叠区域。
*/
static void st7789_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
struct st7789_data *priv = info->par;
u16 *src, *dst;
int dx = area->dx;
int dy = area->dy;
int sx = area->sx;
int sy = area->sy;
int width = area->width;
int height = area->height;
int y;
/* 边界检查 */
if (dx >= ST7789_WIDTH || dy >= ST7789_HEIGHT ||
sx >= ST7789_WIDTH || sy >= ST7789_HEIGHT)
return;
if (dx + width > ST7789_WIDTH)
width = ST7789_WIDTH - dx;
if (dy + height > ST7789_HEIGHT)
height = ST7789_HEIGHT - dy;
if (sx + width > ST7789_WIDTH)
width = ST7789_WIDTH - sx;
if (sy + height > ST7789_HEIGHT)
height = ST7789_HEIGHT - sy;
mutex_lock(&priv->lock);
src = (u16 *)priv->fb_virt_addr;
dst = (u16 *)priv->fb_virt_addr;
/* 检查重叠情况,决定复制方向 */
if (dy > sy || (dy == sy && dx > sx)) {
/* 正向复制(从下到上) */
for (y = height - 1; y >= 0; y--) {
memmove(dst + (dy + y) * ST7789_WIDTH + dx,
src + (sy + y) * ST7789_WIDTH + sx,
width * sizeof(u16));
}
} else {
/* 反向复制(从上到下) */
for (y = 0; y < height; y++) {
memmove(dst + (dy + y) * ST7789_WIDTH + dx,
src + (sy + y) * ST7789_WIDTH + sx,
width * sizeof(u16));
}
}
/* 调度显示更新 */
if (width > 0 && height > 0)
st7789_schedule_update(priv);
mutex_unlock(&priv->lock);
}
3.3 IOCTL控制接口
/**
* @brief IOCTL控制接口
* @param info FrameBuffer信息
* @param cmd 控制命令
* @param arg 命令参数
* @return 成功返回0,失败返回错误码
*
* 实现自定义IOCTL命令,扩展驱动功能。
*/
static int st7789_ioctl(struct fb_info *info, unsigned int cmd,
unsigned long arg)
{
struct st7789_data *priv = info->par;
int ret = 0;
switch (cmd) {
case FBIOGET_CON2FBMAP: /* 获取控制台到FrameBuffer映射 */
/* SPI TFT通常不用于控制台,返回空映射 */
break;
case FBIOPUT_CON2FBMAP:
break;
case FBIOBLANK: /* 空白显示控制 */
ret = st7789_blank(arg, info);
break;
/* 自定义命令 */
case 0x4600: /* 设置旋转 */
if (arg <= 270) {
mutex_lock(&priv->lock);
priv->rotation = arg;
st7789_init_display(priv); /* 重新初始化显示 */
mutex_unlock(&priv->lock);
} else {
ret = -EINVAL;
}
break;
case 0x4601: /* 获取旋转 */
ret = put_user(priv->rotation, (unsigned int __user *)arg);
break;
case 0x4602: /* 设置亮度 */
if (arg <= 255) {
mutex_lock(&priv->lock);
st7789_set_brightness(priv, arg);
mutex_unlock(&priv->lock);
} else {
ret = -EINVAL;
}
break;
case 0x4603: /* 立即刷新显示 */
mutex_lock(&priv->lock);
st7789_flush_framebuffer(priv);
mutex_unlock(&priv->lock);
break;
default:
ret = -ENOTTY; /* 不支持的命令 */
}
return ret;
}
/* FrameBuffer操作集定义 */
static struct fb_ops st7789_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = st7789_check_var,
.fb_set_par = st7789_set_par,
.fb_setcolreg = st7789_setcolreg,
.fb_blank = st7789_blank,
.fb_fillrect = st7789_fillrect,
.fb_copyarea = st7789_copyarea,
.fb_imageblit = st7789_imageblit,
.fb_mmap = NULL,
.fb_ioctl = st7789_ioctl,
};
四、硬件初始化和配置函数
4.1 硬件复位和初始化序列
/**
* @brief 执行硬件复位序列
* @param priv 驱动私有数据
*
* 通过复位GPIO引脚控制硬件复位时序。
* 遵循ST7789数据手册的复位时序要求。
*/
static void st7789_hardware_reset(struct st7789_data *priv)
{
if (!priv->reset_gpio) {
dev_warn(priv->dev, "No reset GPIO specified, using software reset\n");
st7789_write_cmd_optimized(priv, ST7789_SWRESET);
msleep(150);
return;
}
dev_dbg(priv->dev, "Performing hardware reset\n");
/* 复位序列 */
gpiod_set_value(priv->reset_gpio, 0);
msleep(10);
gpiod_set_value(priv->reset_gpio, 1);
msleep(120); /* ST7789需要至少120ms启动时间 */
/* 等待复位完成 */
usleep_range(5000, 10000);
dev_dbg(priv->dev, "Hardware reset completed\n");
}
/**
* @brief 发送初始化命令序列
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 发送ST7789显示控制器的初始化命令序列。
* 配置显示参数、颜色模式、扫描方向等。
*/
static int st7789_send_init_sequence(struct st7789_data *priv)
{
int ret = 0;
dev_info(priv->dev, "Sending ST7789 initialization sequence\n");
/* 1. 退出睡眠模式 */
st7789_write_cmd_optimized(priv, ST7789_SLPOUT);
msleep(120);
/* 2. 设置接口像素格式为16位RGB565 */
st7789_write_cmd_optimized(priv, ST7789_COLMOD);
st7789_write_data_optimized(priv, 0x55); /* RGB565 */
/* 3. 配置内存访问控制 */
st7789_write_cmd_optimized(priv, ST7789_MADCTL);
st7789_write_data_optimized(priv, priv->madctl_val);
/* 4. 配置帧率控制 */
st7789_write_cmd_optimized(priv, 0xB2); /* PORCH设置 */
st7789_write_data_optimized(priv, 0x0C);
st7789_write_data_optimized(priv, 0x0C);
st7789_write_data_optimized(priv, 0x00);
st7789_write_data_optimized(priv, 0x33);
st7789_write_data_optimized(priv, 0x33);
/* 5. 配置门控控制 */
st7789_write_cmd_optimized(priv, 0xB7);
st7789_write_data_optimized(priv, 0x35); /* VGH=13.26V, VGL=-10.43V */
/* 6. 配置VCOM电压 */
st7789_write_cmd_optimized(priv, 0xBB);
st7789_write_data_optimized(priv, 0x19); /* VCOM=0.725V */
/* 7. 配置LCM控制 */
st7789_write_cmd_optimized(priv, 0xC0);
st7789_write_data_optimized(priv, 0x2C); /* LCM=标准模式 */
/* 8. 配置VDV和VRH */
st7789_write_cmd_optimized(priv, 0xC2);
st7789_write_data_optimized(priv, 0x01); /* VDV和VRH命令使能 */
/* 9. 配置VRH电压 */
st7789_write_cmd_optimized(priv, 0xC3);
st7789_write_data_optimized(priv, 0x12); /* VRH=4.6V */
/* 10. 配置VDV电压 */
st7789_write_cmd_optimized(priv, 0xC4);
st7789_write_data_optimized(priv, 0x20); /* VDV=0V */
/* 11. 配置VCOM偏移 */
st7789_write_cmd_optimized(priv, 0xC6);
st7789_write_data_optimized(priv, 0x0F); /* VCOM偏移=0V */
/* 12. 配置电源控制 */
st7789_write_cmd_optimized(priv, 0xD0);
st7789_write_data_optimized(priv, 0xA4);
st7789_write_data_optimized(priv, 0xA1);
/* 13. 配置伽马校正 */
st7789_write_cmd_optimized(priv, 0xE0); /* 正极伽马校正 */
st7789_write_data_optimized(priv, 0xD0);
st7789_write_data_optimized(priv, 0x04);
st7789_write_data_optimized(priv, 0x0D);
st7789_write_data_optimized(priv, 0x11);
st7789_write_data_optimized(priv, 0x13);
st7789_write_data_optimized(priv, 0x2B);
st7789_write_data_optimized(priv, 0x3F);
st7789_write_data_optimized(priv, 0x54);
st7789_write_data_optimized(priv, 0x4C);
st7789_write_data_optimized(priv, 0x18);
st7789_write_data_optimized(priv, 0x0D);
st7789_write_data_optimized(priv, 0x0B);
st7789_write_data_optimized(priv, 0x1F);
st7789_write_data_optimized(priv, 0x23);
st7789_write_cmd_optimized(priv, 0xE1); /* 负极伽马校正 */
st7789_write_data_optimized(priv, 0xD0);
st7789_write_data_optimized(priv, 0x04);
st7789_write_data_optimized(priv, 0x0C);
st7789_write_data_optimized(priv, 0x11);
st7789_write_data_optimized(priv, 0x13);
st7789_write_data_optimized(priv, 0x2C);
st7789_write_data_optimized(priv, 0x3F);
st7789_write_data_optimized(priv, 0x44);
st7789_write_data_optimized(priv, 0x51);
st7789_write_data_optimized(priv, 0x2F);
st7789_write_data_optimized(priv, 0x1F);
st7789_write_data_optimized(priv, 0x1F);
st7789_write_data_optimized(priv, 0x20);
st7789_write_data_optimized(priv, 0x23);
/* 14. 退出空闲模式 */
st7789_write_cmd_optimized(priv, ST7789_IDMOFF);
/* 15. 打开正常显示模式 */
st7789_write_cmd_optimized(priv, ST7789_NORON);
msleep(10);
/* 16. 清除显示内存 */
st7789_set_addr_window(priv, 0, 0, priv->width - 1, priv->height - 1);
for (int i = 0; i < priv->width * priv->height; i++) {
st7789_write_data16(priv, 0x0000); /* 黑色 */
}
/* 17. 打开显示 */
st7789_write_cmd_optimized(priv, ST7789_DISPON);
msleep(100);
dev_info(priv->dev, "ST7789 initialization sequence completed\n");
return ret;
}
/**
* @brief 配置屏幕旋转参数
* @param priv 驱动私有数据
*
* 根据旋转角度配置MADCTL寄存器和显示偏移。
*/
static void st7789_set_rotation(struct st7789_data *priv)
{
u8 madctl = MADCTL_BGR; /* 默认BGR顺序 */
switch (priv->rotation) {
case 90:
madctl |= MADCTL_MV | MADCTL_MX;
priv->swap_xy = true;
priv->invert_x = false;
priv->invert_y = false;
priv->offset_x = 0;
priv->offset_y = 0;
break;
case 180:
madctl |= MADCTL_MY | MADCTL_MX;
priv->swap_xy = false;
priv->invert_x = true;
priv->invert_y = true;
priv->offset_x = 0;
priv->offset_y = 80; /* 对于320x240屏幕,Y偏移80像素 */
break;
case 270:
madctl |= MADCTL_MV | MADCTL_MY;
priv->swap_xy = true;
priv->invert_x = true;
priv->invert_y = false;
priv->offset_x = 80; /* 对于240x320屏幕,X偏移80像素 */
priv->offset_y = 0;
break;
default: /* 0度 */
madctl |= MADCTL_RGB; /* RGB顺序 */
priv->swap_xy = false;
priv->invert_x = false;
priv->invert_y = false;
priv->offset_x = 0;
priv->offset_y = 80; /* 对于320x240屏幕,Y偏移80像素 */
break;
}
priv->madctl_val = madctl;
dev_info(priv->dev, "Rotation set to %d degrees (MADCTL=0x%02x)\n",
priv->rotation, madctl);
}
/**
* @brief 配置显示偏移
* @param priv 驱动私有数据
*
* 设置ST7789的显示偏移寄存器。
*/
static void st7789_set_display_offset(struct st7789_data *priv)
{
if (priv->offset_x > 0 || priv->offset_y > 0) {
st7789_write_cmd_optimized(priv, 0x37); /* VSCSAD - 垂直滚动开始地址 */
st7789_write_data16(priv, priv->offset_y);
dev_dbg(priv->dev, "Display offset set to X=%d, Y=%d\n",
priv->offset_x, priv->offset_y);
}
}
4.2 电源管理函数
/**
* @brief 打开显示电源
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 执行完整的电源开启序列,包括稳压器、背光和显示控制器。
*/
static int st7789_power_on(struct st7789_data *priv)
{
int ret = 0;
if (priv->powered_on)
return 0;
dev_info(priv->dev, "Powering on display\n");
mutex_lock(&priv->lock);
/* 1. 使能电源稳压器(如果存在) */
if (priv->vdd_reg) {
ret = regulator_enable(priv->vdd_reg);
if (ret) {
dev_err(priv->dev, "Failed to enable VDD regulator: %d\n", ret);
goto unlock;
}
msleep(10);
}
if (priv->vcc_reg) {
ret = regulator_enable(priv->vcc_reg);
if (ret) {
dev_err(priv->dev, "Failed to enable VCC regulator: %d\n", ret);
goto disable_vdd;
}
msleep(10);
}
/* 2. 硬件复位 */
st7789_hardware_reset(priv);
/* 3. 配置旋转参数 */
st7789_set_rotation(priv);
/* 4. 发送初始化序列 */
ret = st7789_send_init_sequence(priv);
if (ret)
goto disable_regulators;
/* 5. 配置显示偏移 */
st7789_set_display_offset(priv);
/* 6. 使能背光 */
if (priv->bl_dev) {
ret = backlight_update_status(priv->bl_dev);
if (ret)
dev_warn(priv->dev, "Failed to update backlight status: %d\n", ret);
} else if (priv->bl_gpio) {
gpiod_set_value(priv->bl_gpio, 1);
}
/* 7. 更新显示状态 */
priv->powered_on = true;
priv->sleeping = false;
/* 8. 强制刷新显示 */
st7789_update_full(priv);
mutex_unlock(&priv->lock);
dev_info(priv->dev, "Display powered on successfully\n");
return 0;
disable_regulators:
if (priv->vcc_reg)
regulator_disable(priv->vcc_reg);
disable_vdd:
if (priv->vdd_reg)
regulator_disable(priv->vdd_reg);
unlock:
mutex_unlock(&priv->lock);
return ret;
}
/**
* @brief 关闭显示电源
* @param priv 驱动私有数据
*
* 执行完整的电源关闭序列,进入最低功耗状态。
*/
static void st7789_power_off(struct st7789_data *priv)
{
if (!priv->powered_on)
return;
dev_info(priv->dev, "Powering off display\n");
mutex_lock(&priv->lock);
/* 1. 关闭背光 */
if (priv->bl_gpio)
gpiod_set_value(priv->bl_gpio, 0);
/* 2. 进入睡眠模式 */
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
/* 3. 禁用电源稳压器 */
if (priv->vcc_reg)
regulator_disable(priv->vcc_reg);
if (priv->vdd_reg)
regulator_disable(priv->vdd_reg);
/* 4. 更新显示状态 */
priv->powered_on = false;
priv->sleeping = true;
mutex_unlock(&priv->lock);
dev_info(priv->dev, "Display powered off\n");
}
/**
* @brief 系统挂起回调
* @param dev 设备指针
* @return 成功返回0
*
* 当系统进入挂起状态时调用。
*/
static int st7789_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_info(dev, "Suspending display\n");
/* 取消所有待处理的工作 */
cancel_delayed_work_sync(&priv->dirty_work);
cancel_work_sync(&priv->update_work);
/* 关闭显示电源 */
st7789_power_off(priv);
return 0;
}
/**
* @brief 系统恢复回调
* @param dev 设备指针
* @return 成功返回0
*
* 当系统从挂起状态恢复时调用。
*/
static int st7789_resume(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_info(dev, "Resuming display\n");
/* 重新打开显示电源 */
st7789_power_on(priv);
return 0;
}
/**
* @brief 运行时挂起回调
* @param dev 设备指针
* @return 成功返回0
*
* 当设备进入运行时挂起状态时调用。
*/
static int st7789_runtime_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_dbg(dev, "Runtime suspending display\n");
if (priv->powered_on && !priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
priv->sleeping = true;
}
return 0;
}
/**
* @brief 运行时恢复回调
* @param dev 设备指针
* @return 成功返回0
*
* 当设备从运行时挂起状态恢复时调用。
*/
static int st7789_runtime_resume(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_dbg(dev, "Runtime resuming display\n");
if (priv->powered_on && priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_SLPOUT);
msleep(120);
st7789_write_cmd_optimized(priv, ST7789_DISPON);
priv->sleeping = false;
}
return 0;
}
/* 电源管理操作集 */
static const struct dev_pm_ops st7789_pm_ops = {
.suspend = st7789_suspend,
.resume = st7789_resume,
.poweroff = st7789_suspend,
.restore = st7789_resume,
.runtime_suspend = st7789_runtime_suspend,
.runtime_resume = st7789_runtime_resume,
};
4.3 背光控制函数
/**
* @brief 更新背光状态
* @param bl_dev 背光设备
* @return 成功返回0
*
* 背光子系统的回调函数,当亮度改变时调用。
*/
static int st7789_backlight_update_status(struct backlight_device *bl_dev)
{
struct st7789_data *priv = bl_get_data(bl_dev);
int brightness = bl_dev->props.brightness;
/* 检查显示状态 */
if (bl_dev->props.power != FB_BLANK_UNBLANK ||
bl_dev->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
mutex_lock(&priv->lock);
/* 更新背光亮度 */
if (priv->bl_gpio) {
/* GPIO背光控制 */
gpiod_set_value(priv->bl_gpio, brightness > 0 ? 1 : 0);
} else {
/* PWM背光控制(如果支持) */
dev_dbg(priv->dev, "Backlight brightness: %d\n", brightness);
}
priv->brightness = brightness;
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 获取背光亮度
* @param bl_dev 背光设备
* @return 当前亮度值
*
* 背光子系统的回调函数,返回当前亮度。
*/
static int st7789_backlight_get_brightness(struct backlight_device *bl_dev)
{
struct st7789_data *priv = bl_get_data(bl_dev);
return priv->brightness;
}
/**
* @brief 初始化背光控制
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 根据设备树配置初始化背光控制。
*/
static int st7789_init_backlight(struct st7789_data *priv)
{
struct backlight_properties props = {
.type = BACKLIGHT_RAW,
.max_brightness = 255,
.brightness = 255,
.power = FB_BLANK_UNBLANK,
};
struct device *dev = priv->dev;
struct backlight_device *bl_dev;
int ret;
/* 尝试获取GPIO背光控制 */
priv->bl_gpio = devm_gpiod_get_optional(dev, "backlight",
GPIOD_OUT_LOW);
if (IS_ERR(priv->bl_gpio)) {
ret = PTR_ERR(priv->bl_gpio);
dev_warn(dev, "Failed to get backlight GPIO: %d\n", ret);
priv->bl_gpio = NULL;
}
if (priv->bl_gpio) {
/* GPIO背光控制可用 */
gpiod_set_value(priv->bl_gpio, 1);
dev_info(dev, "Using GPIO backlight control\n");
return 0;
}
/* 尝试获取PWM背光控制 */
priv->pwm = devm_pwm_get_optional(dev, NULL);
if (IS_ERR(priv->pwm)) {
ret = PTR_ERR(priv->pwm);
dev_warn(dev, "Failed to get PWM backlight: %d\n", ret);
priv->pwm = NULL;
}
if (priv->pwm) {
/* PWM背光控制可用 */
ret = pwm_apply_state(priv->pwm, &priv->pwm_state);
if (ret) {
dev_warn(dev, "Failed to apply PWM state: %d\n", ret);
priv->pwm = NULL;
} else {
dev_info(dev, "Using PWM backlight control\n");
return 0;
}
}
/* 如果没有GPIO或PWM背光,注册背光子系统设备 */
bl_dev = devm_backlight_device_register(dev, "st7789-bl",
dev, priv,
&st7789_backlight_ops,
&props);
if (IS_ERR(bl_dev)) {
ret = PTR_ERR(bl_dev);
dev_warn(dev, "Failed to register backlight device: %d\n", ret);
return ret;
}
priv->bl_dev = bl_dev;
backlight_update_status(bl_dev);
dev_info(dev, "Using backlight subsystem\n");
return 0;
}
五、FrameBuffer操作集实现
5.1 FrameBuffer操作集定义
/**
* @brief 检查显示变量参数
* @param var 变量参数结构
* @param info FrameBuffer信息
* @return 成功返回0,失败返回错误码
*
* 验证应用程序请求的显示参数是否受硬件支持。
*/
static int st7789_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "check_var: xres=%u, yres=%u, bpp=%u\n",
var->xres, var->yres, var->bits_per_pixel);
/* 检查分辨率 */
if (var->xres > priv->width || var->yres > priv->height) {
dev_err(priv->dev, "Unsupported resolution: %ux%u (max: %ux%u)\n",
var->xres, var->yres, priv->width, priv->height);
return -EINVAL;
}
/* 检查颜色深度 */
if (var->bits_per_pixel != 16 && var->bits_per_pixel != 32) {
dev_err(priv->dev, "Unsupported bpp: %u (must be 16 or 32)\n",
var->bits_per_pixel);
return -EINVAL;
}
/* 设置支持的参数 */
var->xres = var->xres_virtual = priv->width;
var->yres = var->yres_virtual = priv->height;
/* RGB565格式配置(16bpp) */
if (var->bits_per_pixel == 16) {
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;
} else { /* RGB888格式配置(32bpp) */
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;
}
/* 显示时序参数 */
var->pixclock = 10000000; /* 10MHz像素时钟 */
var->left_margin = 10;
var->right_margin = 10;
var->upper_margin = 10;
var->lower_margin = 10;
var->hsync_len = 10;
var->vsync_len = 10;
var->sync = 0;
var->vmode = FB_VMODE_NONINTERLACED;
var->rotate = priv->rotation;
return 0;
}
/**
* @brief 设置显示参数
* @param info FrameBuffer信息
* @return 成功返回0,失败返回错误码
*
* 应用新的显示参数,可能需要重新配置硬件。
*/
static int st7789_set_par(struct fb_info *info)
{
struct st7789_data *priv = info->par;
int ret;
dev_dbg(priv->dev, "set_par called\n");
mutex_lock(&priv->lock);
/* 重新配置硬件 */
if (priv->powered_on) {
st7789_set_rotation(priv);
st7789_write_cmd_optimized(priv, ST7789_MADCTL);
st7789_write_data_optimized(priv, priv->madctl_val);
st7789_set_display_offset(priv);
}
/* 更新FrameBuffer参数 */
info->fix.line_length = info->var.xres * (info->var.bits_per_pixel / 8);
info->fix.visual = (info->var.bits_per_pixel == 16) ?
FB_VISUAL_TRUECOLOR : FB_VISUAL_TRUECOLOR;
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 设置颜色寄存器
* @param regno 寄存器编号
* @param red 红色分量
* @param green 绿色分量
* @param blue 蓝色分量
* @param transp 透明度分量
* @param info FrameBuffer信息
* @return 成功返回0
*
* 支持伪调色板模式的颜色寄存器设置。
*/
static int st7789_setcolreg(unsigned regno, unsigned red,
unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct st7789_data *priv = info->par;
/* ST7789使用硬件调色板,但支持伪调色板 */
if (regno >= 16)
return -EINVAL;
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
u32 *pal = info->pseudo_palette;
u32 val;
/* 将24/32位RGB转换为RGB565或RGB888 */
red >>= 8;
green >>= 8;
blue >>= 8;
if (info->var.bits_per_pixel == 16) {
val = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
} else {
val = (red << 16) | (green << 8) | blue;
if (info->var.transp.length > 0)
val |= (transp << 24);
}
pal[regno] = val;
dev_dbg(priv->dev, "setcolreg[%d] = 0x%08x\n", regno, val);
}
return 0;
}
/**
* @brief 空白显示控制
* @param blank_mode 空白模式
* @param info FrameBuffer信息
* @return 成功返回0
*
* 控制显示器的电源状态,支持多种省电模式。
*/
static int st7789_blank(int blank_mode, struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "blank: mode=%d\n", blank_mode);
mutex_lock(&priv->lock);
switch (blank_mode) {
case FB_BLANK_UNBLANK: /* 正常显示 */
if (priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_SLPOUT);
msleep(120);
priv->sleeping = false;
}
st7789_write_cmd_optimized(priv, ST7789_DISPON);
if (priv->bl_dev)
backlight_update_status(priv->bl_dev);
break;
case FB_BLANK_NORMAL: /* 正常空白(背光关) */
if (priv->bl_dev) {
struct backlight_device *bl = priv->bl_dev;
bl->props.brightness = 0;
backlight_update_status(bl);
}
break;
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN: /* 完全关闭 */
if (priv->bl_dev) {
struct backlight_device *bl = priv->bl_dev;
bl->props.brightness = 0;
backlight_update_status(bl);
}
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
priv->sleeping = true;
break;
default:
mutex_unlock(&priv->lock);
return -EINVAL;
}
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 填充矩形区域
* @param info FrameBuffer信息
* @param rect 矩形区域信息
*
* 硬件加速的矩形填充操作。
*/
static void st7789_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct st7789_data *priv = info->par;
u32 color = rect->color;
int x = rect->dx;
int y = rect->dy;
int width = rect->width;
int height = rect->height;
/* 边界检查 */
if (x >= priv->width || y >= priv->height)
return;
if (x + width > priv->width)
width = priv->width - x;
if (y + height > priv->height)
height = priv->height - y;
mutex_lock(&priv->lock);
/* 从伪调色板获取颜色值 */
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
if (color < 16)
color = info->pseudo_palette[color];
}
/* 填充矩形到显存 */
if (info->var.bits_per_pixel == 16) {
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 color16 = (u16)color;
for (int j = 0; j < height; j++) {
u16 *line = fb + (y + j) * priv->width + x;
for (int i = 0; i < width; i++) {
line[i] = color16;
}
}
} else {
u32 *fb = (u32 *)priv->fb_virt_addr;
for (int j = 0; j < height; j++) {
u32 *line = fb + (y + j) * priv->width + x;
for (int i = 0; i < width; i++) {
line[i] = color;
}
}
}
/* 标记脏区域 */
if (info->fbdefio) {
struct fb_deferred_io *fbdefio = info->fbdefio;
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
mutex_unlock(&priv->lock);
}
/**
* @brief 复制显示区域
* @param info FrameBuffer信息
* @param area 区域信息
*
* 硬件加速的区域复制操作。
*/
static void st7789_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
struct st7789_data *priv = info->par;
int dx = area->dx;
int dy = area->dy;
int sx = area->sx;
int sy = area->sy;
int width = area->width;
int height = area->height;
/* 边界检查 */
if (dx >= priv->width || dy >= priv->height ||
sx >= priv->width || sy >= priv->height)
return;
if (dx + width > priv->width)
width = priv->width - dx;
if (dy + height > priv->height)
height = priv->height - dy;
if (sx + width > priv->width)
width = priv->width - sx;
if (sy + height > priv->height)
height = priv->height - sy;
mutex_lock(&priv->lock);
/* 执行内存复制 */
if (info->var.bits_per_pixel == 16) {
u16 *fb = (u16 *)priv->fb_virt_addr;
for (int j = 0; j < height; j++) {
u16 *src = fb + (sy + j) * priv->width + sx;
u16 *dst = fb + (dy + j) * priv->width + dx;
memmove(dst, src, width * sizeof(u16));
}
} else {
u32 *fb = (u32 *)priv->fb_virt_addr;
for (int j = 0; j < height; j++) {
u32 *src = fb + (sy + j) * priv->width + sx;
u32 *dst = fb + (dy + j) * priv->width + dx;
memmove(dst, src, width * sizeof(u32));
}
}
/* 标记脏区域 */
if (info->fbdefio) {
struct fb_deferred_io *fbdefio = info->fbdefio;
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
mutex_unlock(&priv->lock);
}
/**
* @brief 图像位块传输
* @param info FrameBuffer信息
* @param image 图像信息
*
* 将单色位图图像绘制到FrameBuffer。
*/
static void st7789_imageblit(struct fb_info *info,
const struct fb_image *image)
{
struct st7789_data *priv = info->par;
const u8 *src = image->data;
u32 fgcolor = image->fg_color;
u32 bgcolor = image->bg_color;
int x = image->dx;
int y = image->dy;
int width = image->width;
int height = image->height;
int bpp = image->depth;
/* 边界检查 */
if (x >= priv->width || y >= priv->height)
return;
if (x + width > priv->width)
width = priv->width - x;
if (y + height > priv->height)
height = priv->height - y;
mutex_lock(&priv->lock);
/* 从伪调色板获取颜色值 */
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
if (fgcolor < 16)
fgcolor = info->pseudo_palette[fgcolor];
if (bgcolor < 16)
bgcolor = info->pseudo_palette[bgcolor];
}
/* 绘制图像 */
if (info->var.bits_per_pixel == 16) {
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 fg16 = (u16)fgcolor;
u16 bg16 = (u16)bgcolor;
if (bpp == 1) {
/* 1bpp位图 */
for (int j = 0; j < height; j++) {
u16 *line = fb + (y + j) * priv->width + x;
const u8 *src_line = src + j * ((width + 7) / 8);
for (int i = 0; i < width; i++) {
int byte_idx = i / 8;
int bit_idx = 7 - (i % 8);
u8 byte = src_line[byte_idx];
line[i] = (byte & (1 << bit_idx)) ? fg16 : bg16;
}
}
}
}
/* 标记脏区域 */
if (info->fbdefio) {
struct fb_deferred_io *fbdefio = info->fbdefio;
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
mutex_unlock(&priv->lock);
}
/* FrameBuffer操作集定义 */
static struct fb_ops st7789_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = st7789_check_var,
.fb_set_par = st7789_set_par,
.fb_setcolreg = st7789_setcolreg,
.fb_blank = st7789_blank,
.fb_fillrect = st7789_fillrect,
.fb_copyarea = st7789_copyarea,
.fb_imageblit = st7789_imageblit,
.fb_mmap = NULL, /* SPI设备通常不支持mmap */
.fb_ioctl = st7789_ioctl,
.fb_pan_display = NULL,
};
六、设备树支持与驱动探测
6.1 设备树绑定文档
/**
* @file st7789_bindings.txt
* @brief ST7789设备树绑定文档
*
* 描述ST7789 SPI TFT显示控制器的设备树绑定。
*/
/*
* ST7789 SPI TFT LCD Controller
*
* Required properties:
* - compatible: Must be one of:
* "sitronix,st7789v"
* "sitronix,st7789"
* "fbtft,st7789v"
* - reg: SPI chip select number
* - spi-max-frequency: Maximum SPI clock frequency in Hz
* - width: Display width in pixels (e.g., 320)
* - height: Display height in pixels (e.g., 240)
* - buswidth: Data bus width (must be 8)
*
* Optional properties:
* - reset-gpios: GPIO specifier for reset pin
* - dc-gpios: GPIO specifier for data/command pin
* - led-gpios: GPIO specifier for backlight control
* - vdd-supply: VDD power supply regulator
* - vcc-supply: VCC power supply regulator
* - rotate: Display rotation (0, 90, 180, 270)
* - bgr: Boolean, enable BGR color filter order
* - fps: Target frame rate (1-60)
* - debug: Boolean, enable debug output
*
* Example:
*
* &spi0 {
* status = "okay";
*
* display@0 {
* compatible = "sitronix,st7789v";
* reg = <0>;
* spi-max-frequency = <40000000>;
* width = <320>;
* height = <240>;
* buswidth = <8>;
* reset-gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
* dc-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
* led-gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;
* vdd-supply = <&display_vdd>;
* vcc-supply = <&display_vcc>;
* rotate = <90>;
* bgr;
* fps = <30>;
* debug;
* };
* };
*/
6.2 设备树解析函数
/**
* @brief 从设备树解析显示参数
* @param dev 设备指针
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 解析设备树节点中的显示配置参数。
*/
static int st7789_parse_dt(struct device *dev, struct st7789_data *priv)
{
struct device_node *np = dev->of_node;
int ret;
if (!np) {
dev_err(dev, "No device tree node found\n");
return -EINVAL;
}
/* 解析显示尺寸 */
ret = of_property_read_u32(np, "width", &priv->width);
if (ret) {
dev_warn(dev, "No width specified, using default %d\n",
ST7789_DEFAULT_WIDTH);
priv->width = ST7789_DEFAULT_WIDTH;
}
ret = of_property_read_u32(np, "height", &priv->height);
if (ret) {
dev_warn(dev, "No height specified, using default %d\n",
ST7789_DEFAULT_HEIGHT);
priv->height = ST7789_DEFAULT_HEIGHT;
}
/* 解析旋转角度 */
ret = of_property_read_u32(np, "rotate", &priv->rotation);
if (ret) {
priv->rotation = 0; /* 默认0度 */
} else if (priv->rotation > 270) {
dev_warn(dev, "Invalid rotation %d, using 0\n", priv->rotation);
priv->rotation = 0;
}
/* 解析BGR顺序 */
if (of_property_read_bool(np, "bgr")) {
priv->bgr = true;
} else {
priv->bgr = false;
}
/* 解析帧率 */
ret = of_property_read_u32(np, "fps", &priv->fps);
if (ret) {
priv->fps = ST7789_DEFAULT_FPS;
} else if (priv->fps < 1 || priv->fps > 60) {
dev_warn(dev, "Invalid fps %d, using default %d\n",
priv->fps, ST7789_DEFAULT_FPS);
priv->fps = ST7789_DEFAULT_FPS;
}
/* 解析调试标志 */
if (of_property_read_bool(np, "debug")) {
priv->debug = true;
} else {
priv->debug = false;
}
/* 解析总线宽度 */
ret = of_property_read_u32(np, "buswidth", &priv->buswidth);
if (ret) {
priv->buswidth = 8; /* 默认8位 */
}
if (priv->buswidth != 8) {
dev_err(dev, "Only 8-bit buswidth is supported\n");
return -EINVAL;
}
/* 解析SPI最大频率 */
ret = of_property_read_u32(np, "spi-max-frequency", &priv->spi_speed);
if (ret) {
dev_warn(dev, "No spi-max-frequency specified, using %d Hz\n",
ST7789_SPI_SPEED);
priv->spi_speed = ST7789_SPI_SPEED;
}
dev_info(dev, "DT parsed: %dx%d, rotate=%d, fps=%d, bgr=%s\n",
priv->width, priv->height, priv->rotation, priv->fps,
priv->bgr ? "yes" : "no");
return 0;
}
/**
* @brief 获取GPIO资源
* @param dev 设备指针
* @param priv 驱动私有数据
* @return 成功返回0
*
* 从设备树获取GPIO引脚配置。
*/
static int st7789_get_gpios(struct device *dev, struct st7789_data *priv)
{
struct device_node *np = dev->of_node;
/* 获取复位GPIO */
priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(priv->reset_gpio)) {
int ret = PTR_ERR(priv->reset_gpio);
dev_warn(dev, "Failed to get reset GPIO: %d\n", ret);
priv->reset_gpio = NULL;
} else if (priv->reset_gpio) {
dev_info(dev, "Using GPIO for reset control\n");
}
/* 获取数据/命令选择GPIO */
priv->dc_gpio = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
if (IS_ERR(priv->dc_gpio)) {
int ret = PTR_ERR(priv->dc_gpio);
dev_err(dev, "Failed to get DC GPIO: %d\n", ret);
return ret;
}
dev_info(dev, "DC GPIO acquired successfully\n");
/* 获取背光GPIO(可选) */
priv->bl_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(priv->bl_gpio)) {
int ret = PTR_ERR(priv->bl_gpio);
dev_warn(dev, "Failed to get backlight GPIO: %d\n", ret);
priv->bl_gpio = NULL;
} else if (priv->bl_gpio) {
dev_info(dev, "Using GPIO for backlight control\n");
}
return 0;
}
/**
* @brief 获取电源稳压器
* @param dev 设备指针
* @param priv 驱动私有数据
* @return 成功返回0
*
* 从设备树获取电源稳压器配置。
*/
static int st7789_get_regulators(struct device *dev, struct st7789_data *priv)
{
int ret;
/* 获取VDD稳压器(逻辑电压,通常3.3V) */
priv->vdd_reg = devm_regulator_get_optional(dev, "vdd");
if (IS_ERR(priv->vdd_reg)) {
ret = PTR_ERR(priv->vdd_reg);
if (ret != -ENODEV) {
dev_warn(dev, "Failed to get VDD regulator: %d\n", ret);
}
priv->vdd_reg = NULL;
} else {
ret = regulator_enable(priv->vdd_reg);
if (ret) {
dev_warn(dev, "Failed to enable VDD regulator: %d\n", ret);
priv->vdd_reg = NULL;
} else {
dev_info(dev, "VDD regulator enabled\n");
}
}
/* 获取VCC稳压器(模拟电压,通常5V-18V) */
priv->vcc_reg = devm_regulator_get_optional(dev, "vcc");
if (IS_ERR(priv->vcc_reg)) {
ret = PTR_ERR(priv->vcc_reg);
if (ret != -ENODEV) {
dev_warn(dev, "Failed to get VCC regulator: %d\n", ret);
}
priv->vcc_reg = NULL;
} else {
ret = regulator_enable(priv->vcc_reg);
if (ret) {
dev_warn(dev, "Failed to enable VCC regulator: %d\n", ret);
priv->vcc_reg = NULL;
} else {
dev_info(dev, "VCC regulator enabled\n");
}
}
/* 如果没有指定稳压器,可能需要延时以确保电源稳定 */
if (!priv->vdd_reg && !priv->vcc_reg) {
dev_info(dev, "No regulators specified, assuming always-on power\n");
msleep(100); /* 等待电源稳定 */
}
return 0;
}
6.3 驱动探测函数
/**
* @brief SPI设备探测函数
* @param spi SPI设备
* @return 成功返回0,失败返回错误码
*
* 当SPI设备匹配时调用,执行驱动的完整初始化。
*/
static int st7789_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct st7789_data *priv;
struct fb_info *info;
int ret;
dev_info(dev, "ST7789 SPI TFT display driver probing\n");
/* 1. 分配私有数据结构 */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(dev, "Failed to allocate private data\n");
return -ENOMEM;
}
/* 2. 初始化私有数据结构 */
priv->spi = spi;
priv->dev = dev;
priv->fps = ST7789_DEFAULT_FPS;
mutex_init(&priv->lock);
INIT_DELAYED_WORK(&priv->dirty_work, st7789_dirty_work_handler);
INIT_WORK(&priv->update_work, st7789_update_work_handler);
/* 3. 设置SPI参数 */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi->max_speed_hz = ST7789_SPI_SPEED;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(dev, "Failed to setup SPI: %d\n", ret);
return ret;
}
/* 4. 解析设备树参数 */
if (dev->of_node) {
ret = st7789_parse_dt(dev, priv);
if (ret)
return ret;
} else {
/* 非设备树平台使用默认值 */
priv->width = ST7789_DEFAULT_WIDTH;
priv->height = ST7789_DEFAULT_HEIGHT;
priv->rotation = 0;
priv->spi_speed = ST7789_SPI_SPEED;
priv->fps = ST7789_DEFAULT_FPS;
}
/* 5. 获取GPIO资源 */
ret = st7789_get_gpios(dev, priv);
if (ret)
return ret;
/* 6. 获取电源稳压器 */
ret = st7789_get_regulators(dev, priv);
if (ret)
return ret;
/* 7. 初始化背光控制 */
ret = st7789_init_backlight(priv);
if (ret && ret != -ENODEV) {
dev_warn(dev, "Backlight initialization failed: %d\n", ret);
/* 继续执行,背光不是必需的 */
}
/* 8. 分配FrameBuffer信息结构 */
info = framebuffer_alloc(sizeof(*priv), dev);
if (!info) {
dev_err(dev, "Failed to allocate framebuffer info\n");
ret = -ENOMEM;
goto err_free_regulators;
}
info->par = priv;
info->fbops = &st7789_fb_ops;
info->flags = FBINFO_FLAG_DEFAULT |
FBINFO_HWACCEL_FILLRECT |
FBINFO_HWACCEL_COPYAREA |
FBINFO_HWACCEL_IMAGEBLIT;
priv->fb_info = info;
/* 9. 设置FrameBuffer固定参数 */
strlcpy(info->fix.id, "ST7789", sizeof(info->fix.id));
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.type_aux = 0;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.xpanstep = 0;
info->fix.ypanstep = 0;
info->fix.ywrapstep = 0;
info->fix.accel = FB_ACCEL_NONE;
info->fix.smem_len = priv->width * priv->height * 2; /* RGB565 */
/* 10. 设置FrameBuffer可变参数 */
info->var.xres = priv->width;
info->var.yres = priv->height;
info->var.xres_virtual = priv->width;
info->var.yres_virtual = priv->height;
info->var.bits_per_pixel = 16; /* RGB565 */
info->var.grayscale = 0;
info->var.nonstd = 0;
info->var.activate = FB_ACTIVATE_NOW;
info->var.height = -1;
info->var.width = -1;
info->var.rotate = priv->rotation;
/* RGB565格式 */
info->var.red.offset = 11;
info->var.red.length = 5;
info->var.green.offset = 5;
info->var.green.length = 6;
info->var.blue.offset = 0;
info->var.blue.length = 5;
info->var.transp.offset = 0;
info->var.transp.length = 0;
/* 11. 分配显存 */
ret = st7789_alloc_framebuffer(priv);
if (ret) {
dev_err(dev, "Failed to allocate framebuffer memory\n");
goto err_free_fb_info;
}
info->fix.smem_start = priv->fb_dma_addr;
info->screen_base = priv->fb_virt_addr;
info->screen_size = priv->fb_size;
info->fix.line_length = priv->width * 2;
/* 12. 分配伪调色板 */
info->pseudo_palette = devm_kzalloc(dev, sizeof(u32) * 16, GFP_KERNEL);
if (!info->pseudo_palette) {
ret = -ENOMEM;
goto err_free_fb_mem;
}
/* 13. 配置旋转参数 */
st7789_set_rotation(priv);
/* 14. 初始化显示硬件 */
ret = st7789_power_on(priv);
if (ret) {
dev_err(dev, "Failed to power on display\n");
goto err_free_palette;
}
/* 15. 注册FrameBuffer设备 */
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(dev, "Failed to register framebuffer: %d\n", ret);
goto err_power_off;
}
/* 16. 初始化deferred IO */
st7789_init_defio(info);
/* 17. 设置SPI设备私有数据 */
spi_set_drvdata(spi, priv);
/* 18. 添加到全局设备列表 */
mutex_lock(&st7789_list_lock);
list_add(&priv->list, &st7789_devices);
mutex_unlock(&st7789_list_lock);
/* 19. 创建设备属性文件 */
ret = sysfs_create_group(&dev->kobj, &st7789_attr_group);
if (ret) {
dev_warn(dev, "Failed to create sysfs attributes: %d\n", ret);
}
/* 20. 创建调试接口 */
if (priv->debug) {
st7789_create_debugfs(priv);
}
/* 21. 显示启动信息 */
dev_info(dev, "ST7789 display initialized: %dx%d, %d bpp, rotate=%d\n",
priv->width, priv->height, info->var.bits_per_pixel,
priv->rotation);
dev_info(dev, "Framebuffer registered as /dev/fb%d\n", info->node);
return 0;
err_power_off:
st7789_power_off(priv);
err_free_palette:
devm_kfree(dev, info->pseudo_palette);
err_free_fb_mem:
st7789_free_framebuffer(priv);
err_free_fb_info:
framebuffer_release(info);
err_free_regulators:
/* 稳压器由devm管理,会自动清理 */
return ret;
}
/**
* @brief SPI设备移除函数
* @param spi SPI设备
* @return 成功返回0
*
* 当设备被移除或驱动卸载时调用,执行清理操作。
*/
static int st7789_remove(struct spi_device *spi)
{
struct st7789_data *priv = spi_get_drvdata(spi);
struct fb_info *info = priv->fb_info;
dev_info(&spi->dev, "Removing ST7789 display driver\n");
/* 1. 从全局设备列表移除 */
mutex_lock(&st7789_list_lock);
list_del(&priv->list);
mutex_unlock(&st7789_list_lock);
/* 2. 移除调试接口 */
if (priv->debug) {
st7789_remove_debugfs(priv);
}
/* 3. 移除设备属性文件 */
sysfs_remove_group(&spi->dev.kobj, &st7789_attr_group);
/* 4. 清理deferred IO */
if (info->fbdefio) {
st7789_cleanup_defio(info);
}
/* 5. 取消所有待处理的工作 */
cancel_delayed_work_sync(&priv->dirty_work);
cancel_work_sync(&priv->update_work);
/* 6. 注销FrameBuffer设备 */
unregister_framebuffer(info);
/* 7. 关闭显示电源 */
st7789_power_off(priv);
/* 8. 释放FrameBuffer信息结构 */
framebuffer_release(info);
/* 9. 释放显存 */
st7789_free_framebuffer(priv);
/* 10. 销毁互斥锁 */
mutex_destroy(&priv->lock);
dev_info(&spi->dev, "ST7789 display driver removed\n");
return 0;
}
6.4 设备树匹配表
/**
* @brief 设备树兼容性匹配表
*
* 定义与驱动兼容的设备树节点。
*/
static const struct of_device_id st7789_of_match[] = {
{ .compatible = "sitronix,st7789v", .data = NULL },
{ .compatible = "sitronix,st7789", .data = NULL },
{ .compatible = "sitronix,st7789v2", .data = NULL },
{ .compatible = "sitronix,st7789v3", .data = NULL },
{ .compatible = "fbtft,st7789v", .data = NULL },
{ .compatible = "waveshare,st7789v", .data = NULL },
{ .compatible = "adafruit,st7789", .data = NULL },
{},
};
MODULE_DEVICE_TABLE(of, st7789_of_match);
/**
* @brief SPI设备ID表
*
* 定义与驱动兼容的SPI设备ID。
*/
static const struct spi_device_id st7789_id[] = {
{ "st7789v", 0 },
{ "st7789", 0 },
{ "st7789v2", 0 },
{ "st7789v3", 0 },
{ "sitronix,st7789v", 0 },
{ "fbtft-st7789v", 0 },
{},
};
MODULE_DEVICE_TABLE(spi, st7789_id);
/**
* @brief SPI驱动结构
*
* 定义ST7789 SPI驱动。
*/
static struct spi_driver st7789_driver = {
.driver = {
.name = "st7789",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(st7789_of_match),
.pm = &st7789_pm_ops,
},
.id_table = st7789_id,
.probe = st7789_probe,
.remove = st7789_remove,
.shutdown = st7789_remove, /* 关机时也执行移除操作 */
};
七、系统接口和调试功能
7.1 Sysfs属性接口
/**
* @brief 显示旋转属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前显示旋转角度。
*/
static ssize_t rotation_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
return scnprintf(buf, PAGE_SIZE, "%d\n", priv->rotation);
}
/**
* @brief 显示旋转属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs设置显示旋转角度。
*/
static ssize_t rotation_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
unsigned int rotation;
int ret;
ret = kstrtouint(buf, 0, &rotation);
if (ret)
return ret;
if (rotation > 270) {
dev_err(dev, "Invalid rotation value: %u\n", rotation);
return -EINVAL;
}
mutex_lock(&priv->lock);
if (priv->rotation != rotation) {
priv->rotation = rotation;
st7789_set_rotation(priv);
/* 更新FrameBuffer变量 */
if (priv->fb_info) {
priv->fb_info->var.rotate = rotation;
}
/* 重新配置显示 */
if (priv->powered_on) {
st7789_write_cmd_optimized(priv, ST7789_MADCTL);
st7789_write_data_optimized(priv, priv->madctl_val);
st7789_set_display_offset(priv);
st7789_update_full(priv);
}
dev_info(dev, "Rotation changed to %d degrees\n", rotation);
}
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 背光亮度属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前背光亮度。
*/
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
return scnprintf(buf, PAGE_SIZE, "%d\n", priv->brightness);
}
/**
* @brief 背光亮度属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs设置背光亮度。
*/
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
unsigned int brightness;
int ret;
ret = kstrtouint(buf, 0, &brightness);
if (ret)
return ret;
if (brightness > 255) {
dev_err(dev, "Invalid brightness value: %u\n", brightness);
return -EINVAL;
}
mutex_lock(&priv->lock);
priv->brightness = brightness;
/* 更新背光状态 */
if (priv->bl_dev) {
priv->bl_dev->props.brightness = brightness;
backlight_update_status(priv->bl_dev);
} else if (priv->bl_gpio) {
gpiod_set_value(priv->bl_gpio, brightness > 0 ? 1 : 0);
}
mutex_unlock(&priv->lock);
dev_dbg(dev, "Brightness changed to %d\n", brightness);
return count;
}
/**
* @brief 帧率属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前帧率。
*/
static ssize_t fps_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
return scnprintf(buf, PAGE_SIZE, "%d\n", priv->fps);
}
/**
* @brief 帧率属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs设置帧率。
*/
static ssize_t fps_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
unsigned int fps;
int ret;
ret = kstrtouint(buf, 0, &fps);
if (ret)
return ret;
if (fps < 1 || fps > 60) {
dev_err(dev, "Invalid fps value: %u (must be 1-60)\n", fps);
return -EINVAL;
}
mutex_lock(&priv->lock);
if (priv->fps != fps) {
priv->fps = fps;
/* 更新deferred IO延迟 */
if (priv->fb_info && priv->fb_info->fbdefio) {
priv->fb_info->fbdefio->delay = HZ / fps;
}
dev_info(dev, "FPS changed to %d\n", fps);
}
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 电源状态属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前电源状态。
*/
static ssize_t power_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
const char *status;
if (priv->powered_on) {
status = priv->sleeping ? "standby" : "on";
} else {
status = "off";
}
return scnprintf(buf, PAGE_SIZE, "%s\n", status);
}
/**
* @brief 电源状态属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs控制电源状态。
*/
static ssize_t power_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
int ret;
if (sysfs_streq(buf, "on")) {
ret = st7789_power_on(priv);
if (ret)
return ret;
} else if (sysfs_streq(buf, "off")) {
st7789_power_off(priv);
} else if (sysfs_streq(buf, "standby")) {
mutex_lock(&priv->lock);
if (priv->powered_on && !priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
priv->sleeping = true;
}
mutex_unlock(&priv->lock);
} else {
dev_err(dev, "Invalid power state: %s\n", buf);
return -EINVAL;
}
return count;
}
/**
* @brief 诊断信息属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取诊断信息。
*/
static ssize_t diagnostic_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
ssize_t count = 0;
mutex_lock(&priv->lock);
count += scnprintf(buf + count, PAGE_SIZE - count,
"ST7789 Diagnostic Information\n");
count += scnprintf(buf + count, PAGE_SIZE - count,
"============================\n");
count += scnprintf(buf + count, PAGE_SIZE - count,
"Resolution: %dx%d\n", priv->width, priv->height);
count += scnprintf(buf + count, PAGE_SIZE - count,
"Rotation: %d degrees\n", priv->rotation);
count += scnprintf(buf + count, PAGE_SIZE - count,
"Framebuffer: %zu bytes\n", priv->fb_size);
count += scnprintf(buf + count, PAGE_SIZE - count,
"Power: %s\n", priv->powered_on ? "on" : "off");
count += scnprintf(buf + count, PAGE_SIZE - count,
"Sleep: %s\n", priv->sleeping ? "yes" : "no");
count += scnprintf(buf + count, PAGE_SIZE - count,
"FPS: %d\n", priv->fps);
count += scnprintf(buf + count, PAGE_SIZE - count,
"Brightness: %d\n", priv->brightness);
count += scnprintf(buf + count, PAGE_SIZE - count,
"SPI Speed: %d Hz\n", priv->spi_speed);
count += scnprintf(buf + count, PAGE_SIZE - count,
"BGR Order: %s\n", priv->bgr ? "yes" : "no");
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 强制刷新属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数
*
* 通过sysfs触发强制刷新显示。
*/
static ssize_t refresh_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
mutex_lock(&priv->lock);
if (priv->powered_on) {
st7789_update_full(priv);
dev_dbg(dev, "Manual refresh triggered\n");
}
mutex_unlock(&priv->lock);
return count;
}
/* 设备属性定义 */
static DEVICE_ATTR_RW(rotation);
static DEVICE_ATTR_RW(brightness);
static DEVICE_ATTR_RW(fps);
static DEVICE_ATTR_RW(power);
static DEVICE_ATTR_RO(diagnostic);
static DEVICE_ATTR_WO(refresh);
/**
* @brief 设备属性组
*
* 定义所有sysfs属性文件。
*/
static struct attribute *st7789_attrs[] = {
&dev_attr_rotation.attr,
&dev_attr_brightness.attr,
&dev_attr_fps.attr,
&dev_attr_power.attr,
&dev_attr_diagnostic.attr,
&dev_attr_refresh.attr,
NULL,
};
static const struct attribute_group st7789_attr_group = {
.attrs = st7789_attrs,
};
7.2 调试文件系统接口
#ifdef CONFIG_DEBUG_FS
/**
* @brief 调试信息显示函数
* @param s seq_file结构
* @param data 私有数据
* @return 成功返回0
*
* 在debugfs中显示调试信息。
*/
static int st7789_debug_show(struct seq_file *s, void *data)
{
struct st7789_data *priv = s->private;
mutex_lock(&priv->lock);
seq_printf(s, "ST7789 Debug Information\n");
seq_printf(s, "=======================\n");
seq_printf(s, "Driver Version: %s\n", DRIVER_VERSION);
seq_printf(s, "Display Size: %dx%d\n", priv->width, priv->height);
seq_printf(s, "Rotation: %d\n", priv->rotation);
seq_printf(s, "FPS: %d\n", priv->fps);
seq_printf(s, "Brightness: %d\n", priv->brightness);
seq_printf(s, "Power: %s\n", priv->powered_on ? "ON" : "OFF");
seq_printf(s, "Sleep: %s\n", priv->sleeping ? "YES" : "NO");
seq_printf(s, "SPI Speed: %d Hz\n", priv->spi_speed);
seq_printf(s, "Framebuffer: %zu bytes at %p (DMA: %pad)\n",
priv->fb_size, priv->fb_virt_addr, &priv->fb_dma_addr);
seq_printf(s, "Update Count: %lu\n", priv->update_count);
seq_printf(s, "Dirty Rectangles: %lu\n", priv->dirty_count);
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 打开调试文件
* @param inode inode结构
* @param file 文件结构
* @return 成功返回0
*/
static int st7789_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, st7789_debug_show, inode->i_private);
}
/**
* @brief 寄存器读取函数
* @param s seq_file结构
* @param data 私有数据
* @return 成功返回0
*
* 读取并显示ST7789寄存器值。
*/
static int st7789_registers_show(struct seq_file *s, void *data)
{
struct st7789_data *priv = s->private;
u8 reg;
u8 val[4];
int ret;
mutex_lock(&priv->lock);
if (!priv->powered_on) {
seq_printf(s, "Display is powered off\n");
mutex_unlock(&priv->lock);
return 0;
}
seq_printf(s, "ST7789 Register Dump\n");
seq_printf(s, "===================\n");
/* 读取显示ID */
ret = st7789_read_data(priv, ST7789_RDDID, val, 3);
if (ret == 3) {
seq_printf(s, "RDDID: 0x%02X 0x%02X 0x%02X\n",
val[0], val[1], val[2]);
}
/* 读取显示状态 */
ret = st7789_read_data(priv, ST7789_RDDST, val, 4);
if (ret == 4) {
seq_printf(s, "RDDST: 0x%02X 0x%02X 0x%02X 0x%02X\n",
val[0], val[1], val[2], val[3]);
}
/* 读取显示亮度 */
ret = st7789_read_data(priv, ST7789_RDDISBV, val, 2);
if (ret == 2) {
seq_printf(s, "RDDISBV: 0x%02X 0x%02X\n", val[0], val[1]);
}
/* 读取控制显示 */
ret = st7789_read_data(priv, ST7789_RDCTRLD, val, 2);
if (ret == 2) {
seq_printf(s, "RDCTRLD: 0x%02X 0x%02X\n", val[0], val[1]);
}
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 打开寄存器文件
* @param inode inode结构
* @param file 文件结构
* @return 成功返回0
*/
static int st7789_registers_open(struct inode *inode, struct file *file)
{
return single_open(file, st7789_registers_show, inode->i_private);
}
/**
* @brief 写入命令到调试文件
* @param file 文件结构
* @param buf 用户缓冲区
* @param count 字节数
* @param ppos 文件位置指针
* @return 写入的字节数或错误码
*
* 允许通过debugfs发送命令到ST7789。
*/
static ssize_t st7789_command_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
struct st7789_data *priv = file->private_data;
char cmd_buf[16];
u8 cmd;
u8 data;
int ret;
if (count < 3 || count > sizeof(cmd_buf))
return -EINVAL;
if (copy_from_user(cmd_buf, buf, count))
return -EFAULT;
cmd_buf[count] = '\0';
/* 解析命令和数据 */
ret = sscanf(cmd_buf, "%hhx %hhx", &cmd, &data);
if (ret != 2) {
dev_err(priv->dev, "Invalid command format: %s\n", cmd_buf);
return -EINVAL;
}
mutex_lock(&priv->lock);
if (!priv->powered_on) {
mutex_unlock(&priv->lock);
return -ENODEV;
}
/* 发送命令和数据 */
st7789_write_cmd_optimized(priv, cmd);
st7789_write_data_optimized(priv, data);
dev_info(priv->dev, "Debug command: 0x%02X 0x%02X\n", cmd, data);
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 打开命令文件
* @param inode inode结构
* @param file 文件结构
* @return 成功返回0
*/
static int st7789_command_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
/* 调试文件操作 */
static const struct file_operations st7789_debug_fops = {
.owner = THIS_MODULE,
.open = st7789_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations st7789_registers_fops = {
.owner = THIS_MODULE,
.open = st7789_registers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations st7789_command_fops = {
.owner = THIS_MODULE,
.open = st7789_command_open,
.write = st7789_command_write,
.llseek = noop_llseek,
};
/**
* @brief 创建调试文件系统接口
* @param priv 驱动私有数据
*
* 在debugfs中创建设备特定的调试文件。
*/
static void st7789_create_debugfs(struct st7789_data *priv)
{
struct dentry *dir;
if (!st7789_debugfs_root) {
st7789_debugfs_root = debugfs_create_dir("st7789", NULL);
if (!st7789_debugfs_root) {
dev_warn(priv->dev, "Failed to create debugfs root\n");
return;
}
}
dir = debugfs_create_dir(dev_name(priv->dev), st7789_debugfs_root);
if (!dir) {
dev_warn(priv->dev, "Failed to create device debugfs directory\n");
return;
}
debugfs_create_file("info", 0444, dir, priv, &st7789_debug_fops);
debugfs_create_file("registers", 0444, dir, priv, &st7789_registers_fops);
debugfs_create_file("command", 0200, dir, priv, &st7789_command_fops);
debugfs_create_u32("width", 0444, dir, &priv->width);
debugfs_create_u32("height", 0444, dir, &priv->height);
debugfs_create_u32("rotation", 0644, dir, &priv->rotation);
debugfs_create_u32("fps", 0644, dir, &priv->fps);
debugfs_create_bool("powered", 0444, dir, &priv->powered_on);
debugfs_create_bool("sleeping", 0444, dir, &priv->sleeping);
priv->debug_dir = dir;
dev_info(priv->dev, "Debugfs interface created\n");
}
/**
* @brief 移除调试文件系统接口
* @param priv 驱动私有数据
*
* 清理debugfs中的调试文件。
*/
static void st7789_remove_debugfs(struct st7789_data *priv)
{
if (priv->debug_dir) {
debugfs_remove_recursive(priv->debug_dir);
priv->debug_dir = NULL;
}
}
#else /* CONFIG_DEBUG_FS */
static void st7789_create_debugfs(struct st7789_data *priv) {}
static void st7789_remove_debugfs(struct st7789_data *priv) {}
#endif /* CONFIG_DEBUG_FS */
7.3 模块初始化和退出
/**
* @brief 驱动模块初始化函数
* @return 成功返回0,失败返回错误码
*
* 注册SPI驱动到内核。
*/
static int __init st7789_init(void)
{
int ret;
pr_info("ST7789 SPI TFT display driver initializing\n");
/* 创建调试文件系统根目录 */
#ifdef CONFIG_DEBUG_FS
st7789_debugfs_root = debugfs_create_dir("st7789", NULL);
if (!st7789_debugfs_root)
pr_warn("Failed to create debugfs root directory\n");
#endif
/* 注册SPI驱动 */
ret = spi_register_driver(&st7789_driver);
if (ret < 0) {
pr_err("Failed to register ST7789 driver: %d\n", ret);
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(st7789_debugfs_root);
#endif
return ret;
}
pr_info("ST7789 SPI TFT display driver initialized\n");
return 0;
}
/**
* @brief 驱动模块退出函数
*
* 从内核注销SPI驱动。
*/
static void __exit st7789_exit(void)
{
pr_info("ST7789 SPI TFT display driver exiting\n");
/* 注销SPI驱动 */
spi_unregister_driver(&st7789_driver);
/* 清理调试文件系统 */
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(st7789_debugfs_root);
st7789_debugfs_root = NULL;
#endif
pr_info("ST7789 SPI TFT display driver exited\n");
}
module_init(st7789_init);
module_exit(st7789_exit);
八、内核配置和编译集成
8.1 修改video子系统Kconfig
# drivers/video/fbdev/Kconfig 添加以下内容 +config FB_ST7789 + tristate "ST7789 SPI TFT LCD support" + depends on FB && SPI && OF + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + help + FrameBuffer driver for ST7789 SPI TFT LCD controller. + + This driver supports 320x240 RGB565 displays over SPI interface. + It includes hardware acceleration for basic operations and + supports power management and screen rotation. + + To compile this driver as a module, choose M here: the + module will be called st7789_fb.
8.2 修改video子系统Makefile
# drivers/video/fbdev/Makefile 添加以下内容 +obj-$(CONFIG_FB_ST7789) += st7789/
8.3 完整的内核配置示例
# .config文件中添加以下配置 # ST7789 SPI TFT Display CONFIG_FB=y CONFIG_FB_ST7789=y CONFIG_FB_ST7789_DEBUG=y CONFIG_FB_ST7789_BACKLIGHT=y CONFIG_FB_ST7789_ROTATION=y CONFIG_FB_ST7789_GPIO_RESET=y CONFIG_FB_ST7789_SPI_SPEED=40000000 CONFIG_FB_ST7789_FPS=30 # 依赖的子系统 CONFIG_SPI=y CONFIG_SPI_MASTER=y CONFIG_OF=y CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_REGULATOR=y CONFIG_DEBUG_FS=y
九、Logo支持和头文件定义
9.1 头文件定义 (st7789_fb.h)
/**
* @file st7789_fb.h
* @brief ST7789 SPI TFT FrameBuffer驱动头文件
*
* 包含ST7789驱动所需的寄存器定义、数据结构和函数声明。
*
* @copyright GPL v2
*/
#ifndef _ST7789_FB_H_
#define _ST7789_FB_H_
#include <linux/types.h>
#include <linux/fb.h>
#include <linux/spi/spi.h>
#include <linux/gpio/consumer.h>
#include <linux/backlight.h>
#include <linux/regulator/consumer.h>
#include <linux/pwm.h>
/* 驱动版本信息 */
#define DRIVER_VERSION "1.0.0"
#define DRIVER_AUTHOR "Your Name <your.email@example.com>"
#define DRIVER_DESC "ST7789 SPI TFT FrameBuffer driver"
/* ST7789显示控制器寄存器定义 */
#define ST7789_NOP 0x00 /**< 无操作 */
#define ST7789_SWRESET 0x01 /**< 软件复位 */
#define ST7789_RDDID 0x04 /**< 读取显示ID */
#define ST7789_RDDST 0x09 /**< 读取显示状态 */
#define ST7789_SLPIN 0x10 /**< 进入睡眠模式 */
#define ST7789_SLPOUT 0x11 /**< 退出睡眠模式 */
#define ST7789_PTLON 0x12 /**< 部分显示模式开启 */
#define ST7789_NORON 0x13 /**< 正常显示模式开启 */
#define ST7789_INVOFF 0x20 /**< 显示反转关闭 */
#define ST7789_INVON 0x21 /**< 显示反转开启 */
#define ST7789_DISPOFF 0x28 /**< 显示关闭 */
#define ST7789_DISPON 0x29 /**< 显示开启 */
#define ST7789_CASET 0x2A /**< 列地址设置 */
#define ST7789_RASET 0x2B /**< 行地址设置 */
#define ST7789_RAMWR 0x2C /**< 内存写入 */
#define ST7789_RAMRD 0x2E /**< 内存读取 */
#define ST7789_PTLAR 0x30 /**< 部分区域设置 */
#define ST7789_VSCRDEF 0x33 /**< 垂直滚动定义 */
#define ST7789_TEOFF 0x34 /**< 撕裂效果关闭 */
#define ST7789_TEON 0x35 /**< 撕裂效果开启 */
#define ST7789_MADCTL 0x36 /**< 内存访问控制 */
#define ST7789_VSCSAD 0x37 /**< 垂直滚动起始地址 */
#define ST7789_IDMOFF 0x38 /**< 空闲模式关闭 */
#define ST7789_IDMON 0x39 /**< 空闲模式开启 */
#define ST7789_COLMOD 0x3A /**< 颜色模式设置 */
#define ST7789_RAMWRC 0x3C /**< 内存写入继续 */
#define ST7789_RAMRDC 0x3E /**< 内存读取继续 */
#define ST7789_WRDISBV 0x51 /**< 写入显示亮度 */
#define ST7789_RDDISBV 0x52 /**< 读取显示亮度 */
#define ST7789_WRCTRLD 0x53 /**< 写入控制显示 */
#define ST7789_RDCTRLD 0x54 /**< 读取控制显示 */
/* MADCTL寄存器位定义 */
#define MADCTL_MY 0x80 /**< 行地址顺序 */
#define MADCTL_MX 0x40 /**< 列地址顺序 */
#define MADCTL_MV 0x20 /**< 行/列交换 */
#define MADCTL_ML 0x10 /**< 垂直刷新顺序 */
#define MADCTL_RGB 0x00 /**< RGB顺序 */
#define MADCTL_BGR 0x08 /**< BGR顺序 */
#define MADCTL_MH 0x04 /**< 水平刷新顺序 */
/* 颜色模式定义 */
#define COLMOD_12BIT 0x03 /**< 12位/像素 */
#define COLMOD_16BIT 0x05 /**< 16位/像素 (RGB565) */
#define COLMOD_18BIT 0x06 /**< 18位/像素 */
/* 默认显示参数 */
#define ST7789_DEFAULT_WIDTH 320 /**< 默认宽度 */
#define ST7789_DEFAULT_HEIGHT 240 /**< 默认高度 */
#define ST7789_DEFAULT_BPP 16 /**< 默认每像素位数 */
#define ST7789_DEFAULT_FPS 30 /**< 默认帧率 */
#define ST7789_DEFAULT_SPI_SPEED 40000000 /**< 默认SPI速度 */
/**
* @struct st7789_data
* @brief ST7789驱动私有数据结构
*
* 存储驱动状态、硬件资源引用和显存信息。
*/
struct st7789_data {
/* SPI和平台设备 */
struct spi_device *spi; /**< SPI设备指针 */
struct device *dev; /**< 设备指针 */
struct fb_info *fb_info; /**< FrameBuffer信息 */
/* GPIO引脚 */
struct gpio_desc *reset_gpio; /**< 复位引脚 */
struct gpio_desc *dc_gpio; /**< 数据/命令选择引脚 */
struct gpio_desc *bl_gpio; /**< 背光控制引脚 */
/* 电源管理 */
struct regulator *vdd_reg; /**< VDD稳压器 */
struct regulator *vcc_reg; /**< VCC稳压器 */
/* 背光控制 */
struct backlight_device *bl_dev; /**< 背光设备 */
struct pwm_device *pwm; /**< PWM设备(可选) */
struct pwm_state pwm_state; /**< PWM状态 */
u8 brightness; /**< 当前亮度 (0-255) */
/* 显示参数 */
u32 width; /**< 显示宽度 */
u32 height; /**< 显示高度 */
u8 rotation; /**< 屏幕旋转角度 */
u8 madctl_val; /**< MADCTL寄存器值 */
bool bgr; /**< BGR颜色顺序标志 */
bool swap_xy; /**< 交换XY坐标标志 */
bool invert_x; /**< X轴反转标志 */
bool invert_y; /**< Y轴反转标志 */
u16 offset_x; /**< X轴偏移 */
u16 offset_y; /**< Y轴偏移 */
/* SPI参数 */
u32 spi_speed; /**< SPI时钟频率 */
u32 buswidth; /**< 总线宽度 */
struct st7789_spi_buffer spi_buf; /**< SPI缓冲区 */
/* 显存管理 */
dma_addr_t fb_dma_addr; /**< 显存DMA地址 */
void *fb_virt_addr; /**< 显存虚拟地址 */
size_t fb_size; /**< 显存大小 */
/* 显示状态 */
bool powered_on; /**< 电源状态 */
bool sleeping; /**< 睡眠状态 */
bool addr_window_set; /**< 地址窗口设置标志 */
/* 性能统计 */
u32 fps; /**< 目标帧率 */
unsigned long update_count; /**< 更新计数 */
unsigned long dirty_count; /**< 脏区域计数 */
/* 同步保护 */
struct mutex lock; /**< 驱动锁,保护并发访问 */
struct list_head list; /**< 设备列表节点 */
/* 工作队列 */
struct delayed_work dirty_work; /**< 脏区域处理工作队列 */
struct work_struct update_work; /**< 显存更新工作队列 */
void (*dirty)(struct fb_info *info, struct fb_deferred_io *fbdefio);
/* 调试支持 */
bool debug; /**< 调试模式标志 */
#ifdef CONFIG_DEBUG_FS
struct dentry *debug_dir; /**< 调试目录 */
#endif
/* Logo支持 */
bool logo_loaded; /**< Logo加载标志 */
const u8 *logo_data; /**< Logo数据指针 */
u32 logo_size; /**< Logo数据大小 */
u16 logo_width; /**< Logo宽度 */
u16 logo_height; /**< Logo高度 */
};
/**
* @struct st7789_spi_buffer
* @brief SPI传输缓冲区结构
*
* 预分配的SPI传输缓冲区,用于优化小数据传输。
*/
struct st7789_spi_buffer {
u8 data[256]; /**< 数据缓冲区 */
struct spi_transfer xfer; /**< SPI传输结构 */
struct spi_message msg; /**< SPI消息结构 */
};
/* 函数声明 - SPI通信 */
void st7789_write_cmd_optimized(struct st7789_data *priv, u8 cmd);
void st7789_write_data_optimized(struct st7789_data *priv, u8 data);
int st7789_write_buf_dma(struct st7789_data *priv, const u8 *buf, size_t len);
void st7789_write_data16(struct st7789_data *priv, u16 data);
int st7789_read_data(struct st7789_data *priv, u8 cmd, u8 *buf, size_t len);
/* 函数声明 - 硬件控制 */
void st7789_hardware_reset(struct st7789_data *priv);
int st7789_send_init_sequence(struct st7789_data *priv);
void st7789_set_rotation(struct st7789_data *priv);
void st7789_set_display_offset(struct st7789_data *priv);
void st7789_set_addr_window(struct st7789_data *priv,
u16 x0, u16 y0, u16 x1, u16 y1);
/* 函数声明 - 显示更新 */
void st7789_update_area(struct st7789_data *priv,
u16 x0, u16 y0, u16 x1, u16 y1);
void st7789_update_full(struct st7789_data *priv);
void st7789_update_work_handler(struct work_struct *work);
void st7789_dirty_work_handler(struct work_struct *work);
/* 函数声明 - 显存管理 */
int st7789_alloc_framebuffer(struct st7789_data *priv);
void st7789_free_framebuffer(struct st7789_data *priv);
/* 函数声明 - 电源管理 */
int st7789_power_on(struct st7789_data *priv);
void st7789_power_off(struct st7789_data *priv);
int st7789_suspend(struct device *dev);
int st7789_resume(struct device *dev);
/* 函数声明 - 背光控制 */
int st7789_backlight_update_status(struct backlight_device *bl_dev);
int st7789_backlight_get_brightness(struct backlight_device *bl_dev);
int st7789_init_backlight(struct st7789_data *priv);
/* 函数声明 - FrameBuffer操作 */
int st7789_check_var(struct fb_var_screeninfo *var, struct fb_info *info);
int st7789_set_par(struct fb_info *info);
int st7789_setcolreg(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
int st7789_blank(int blank_mode, struct fb_info *info);
void st7789_fillrect(struct fb_info *info, const struct fb_fillrect *rect);
void st7789_copyarea(struct fb_info *info, const struct fb_copyarea *area);
void st7789_imageblit(struct fb_info *info, const struct fb_image *image);
int st7789_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg);
/* 函数声明 - 设备树支持 */
int st7789_parse_dt(struct device *dev, struct st7789_data *priv);
int st7789_get_gpios(struct device *dev, struct st7789_data *priv);
int st7789_get_regulators(struct device *dev, struct st7789_data *priv);
/* 函数声明 - Logo支持 */
int st7789_load_logo(struct st7789_data *priv);
void st7789_display_logo(struct st7789_data *priv);
void st7789_clear_logo(struct st7789_data *priv);
/* 函数声明 - 调试支持 */
void st7789_create_debugfs(struct st7789_data *priv);
void st7789_remove_debugfs(struct st7789_data *priv);
/* 外部声明 */
extern struct fb_ops st7789_fb_ops;
extern const struct attribute_group st7789_attr_group;
extern struct dentry *st7789_debugfs_root;
#endif /* _ST7789_FB_H_ */
9.2 Logo支持实现 (st7789_logo.c)
/**
* @file st7789_logo.c
* @brief ST7789 Logo显示支持
*
* 提供启动Logo显示功能,支持内置Logo和外部Logo加载。
*
* @copyright GPL v2
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/ctype.h>
#include <linux/vmalloc.h>
#include "st7789_fb.h"
/* 默认Logo数据 - 32x32的Linux企鹅Logo(RGB565格式) */
static const u16 st7789_default_logo[] = {
/* 这里应该放置实际的Logo数据 */
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
/* ... 更多Logo数据 ... */
};
/* Logo文件路径 */
#define LOGO_FILE_PATH "/etc/logo.rgb565"
#define LOGO_MAX_SIZE (320 * 240 * 2) /* 最大320x240 RGB565 */
/**
* @brief 从文件加载Logo图像
* @param priv 驱动私有数据
* @param filename Logo文件路径
* @return 成功返回0,失败返回错误码
*
* 从文件系统加载Logo图像文件,支持RGB565原始格式。
*/
static int st7789_load_logo_from_file(struct st7789_data *priv,
const char *filename)
{
struct file *fp = NULL;
loff_t pos = 0;
ssize_t ret;
u16 *logo_data = NULL;
u32 file_size;
dev_info(priv->dev, "Loading logo from file: %s\n", filename);
/* 打开Logo文件 */
fp = filp_open(filename, O_RDONLY, 0);
if (IS_ERR(fp)) {
dev_warn(priv->dev, "Failed to open logo file: %ld\n", PTR_ERR(fp));
return PTR_ERR(fp);
}
/* 获取文件大小 */
file_size = i_size_read(file_inode(fp));
if (file_size == 0 || file_size > LOGO_MAX_SIZE) {
dev_err(priv->dev, "Invalid logo file size: %u\n", file_size);
ret = -EINVAL;
goto close_file;
}
/* 分配Logo内存 */
logo_data = vmalloc(file_size);
if (!logo_data) {
dev_err(priv->dev, "Failed to allocate memory for logo\n");
ret = -ENOMEM;
goto close_file;
}
/* 读取Logo数据 */
ret = kernel_read(fp, logo_data, file_size, &pos);
if (ret != file_size) {
dev_err(priv->dev, "Failed to read logo data: %ld\n", ret);
ret = -EIO;
goto free_logo;
}
/* 计算Logo尺寸(假设为正方形) */
priv->logo_width = (u16)sqrt(file_size / 2);
priv->logo_height = priv->logo_width;
/* 验证尺寸 */
if (priv->logo_width * priv->logo_height * 2 != file_size) {
dev_warn(priv->dev, "Logo size may not be square: %ux%u\n",
priv->logo_width, priv->logo_height);
}
/* 保存Logo数据 */
priv->logo_data = (u8 *)logo_data;
priv->logo_size = file_size;
priv->logo_loaded = true;
dev_info(priv->dev, "Logo loaded: %dx%d, %u bytes\n",
priv->logo_width, priv->logo_height, priv->logo_size);
filp_close(fp, NULL);
return 0;
free_logo:
vfree(logo_data);
close_file:
filp_close(fp, NULL);
return ret;
}
/**
* @brief 使用默认Logo
* @param priv 驱动私有数据
* @return 成功返回0
*
* 当没有外部Logo文件时,使用内置的默认Logo。
*/
static int st7789_load_default_logo(struct st7789_data *priv)
{
dev_info(priv->dev, "Using default built-in logo\n");
/* 使用默认Logo数据 */
priv->logo_data = (u8 *)st7789_default_logo;
priv->logo_size = sizeof(st7789_default_logo);
priv->logo_width = 32;
priv->logo_height = 32;
priv->logo_loaded = true;
return 0;
}
/**
* @brief 加载Logo图像
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 尝试从文件系统加载Logo,如果失败则使用默认Logo。
*/
int st7789_load_logo(struct st7789_data *priv)
{
int ret;
/* 首先尝试从文件加载 */
ret = st7789_load_logo_from_file(priv, LOGO_FILE_PATH);
if (ret == 0) {
return 0;
}
/* 文件加载失败,使用默认Logo */
dev_warn(priv->dev, "Failed to load logo from file, using default\n");
return st7789_load_default_logo(priv);
}
/**
* @brief 在屏幕上居中显示Logo
* @param priv 驱动私有数据
*
* 将Logo图像居中显示在屏幕上。
*/
void st7789_display_logo(struct st7789_data *priv)
{
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 *logo = (u16 *)priv->logo_data;
u16 x, y;
u16 start_x, start_y;
if (!priv->logo_loaded || !priv->logo_data) {
dev_warn(priv->dev, "No logo loaded to display\n");
return;
}
/* 计算居中位置 */
start_x = (priv->width - priv->logo_width) / 2;
start_y = (priv->height - priv->logo_height) / 2;
/* 边界检查 */
if (start_x >= priv->width || start_y >= priv->height) {
dev_err(priv->dev, "Logo position out of bounds\n");
return;
}
dev_info(priv->dev, "Displaying logo at (%u, %u)\n", start_x, start_y);
mutex_lock(&priv->lock);
/* 将Logo复制到显存 */
for (y = 0; y < priv->logo_height; y++) {
u16 fb_y = start_y + y;
if (fb_y >= priv->height)
break;
for (x = 0; x < priv->logo_width; x++) {
u16 fb_x = start_x + x;
if (fb_x >= priv->width)
break;
fb[fb_y * priv->width + fb_x] = logo[y * priv->logo_width + x];
}
}
/* 更新显示区域 */
st7789_update_area(priv, start_x, start_y,
start_x + priv->logo_width - 1,
start_y + priv->logo_height - 1);
mutex_unlock(&priv->lock);
dev_info(priv->dev, "Logo displayed successfully\n");
}
/**
* @brief 清除Logo显示区域
* @param priv 驱动私有数据
*
* 清除Logo占用的显示区域,恢复为背景色。
*/
void st7789_clear_logo(struct st7789_data *priv)
{
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 start_x, start_y;
u16 x, y;
if (!priv->logo_loaded) {
return;
}
/* 计算Logo位置 */
start_x = (priv->width - priv->logo_width) / 2;
start_y = (priv->height - priv->logo_height) / 2;
mutex_lock(&priv->lock);
/* 清除Logo区域(填充黑色) */
for (y = 0; y < priv->logo_height; y++) {
u16 fb_y = start_y + y;
if (fb_y >= priv->height)
break;
for (x = 0; x < priv->logo_width; x++) {
u16 fb_x = start_x + x;
if (fb_x >= priv->width)
break;
fb[fb_y * priv->width + fb_x] = 0x0000; /* 黑色 */
}
}
/* 更新显示区域 */
st7789_update_area(priv, start_x, start_y,
start_x + priv->logo_width - 1,
start_y + priv->logo_height - 1);
mutex_unlock(&priv->lock);
dev_dbg(priv->dev, "Logo cleared\n");
}
/**
* @brief Logo属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取Logo状态信息。
*/
static ssize_t logo_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
ssize_t count = 0;
mutex_lock(&priv->lock);
if (priv->logo_loaded) {
count += scnprintf(buf + count, PAGE_SIZE - count,
"Logo: loaded\n");
count += scnprintf(buf + count, PAGE_SIZE - count,
"Size: %dx%d\n", priv->logo_width, priv->logo_height);
count += scnprintf(buf + count, PAGE_SIZE - count,
"Data size: %u bytes\n", priv->logo_size);
} else {
count += scnprintf(buf + count, PAGE_SIZE - count,
"Logo: not loaded\n");
}
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief Logo控制属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs控制Logo显示。
*/
static ssize_t logo_control_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
if (sysfs_streq(buf, "show")) {
if (priv->logo_loaded) {
st7789_display_logo(priv);
dev_info(dev, "Logo displayed\n");
} else {
dev_warn(dev, "No logo loaded\n");
}
} else if (sysfs_streq(buf, "hide")) {
st7789_clear_logo(priv);
dev_info(dev, "Logo hidden\n");
} else if (sysfs_streq(buf, "reload")) {
/* 清除现有Logo */
if (priv->logo_loaded) {
if (priv->logo_data != (u8 *)st7789_default_logo) {
vfree((void *)priv->logo_data);
}
priv->logo_loaded = false;
}
/* 重新加载Logo */
st7789_load_logo(priv);
dev_info(dev, "Logo reloaded\n");
} else {
dev_err(dev, "Invalid command: %s\n", buf);
return -EINVAL;
}
return count;
}
/* Logo属性定义 */
static DEVICE_ATTR(logo_status, 0444, logo_status_show, NULL);
static DEVICE_ATTR(logo_control, 0200, NULL, logo_control_store);
/**
* @brief 添加Logo属性到sysfs
* @param priv 驱动私有数据
*
* 在sysfs中创建Logo相关的属性文件。
*/
void st7789_add_logo_attributes(struct st7789_data *priv)
{
struct device *dev = priv->dev;
int ret;
ret = device_create_file(dev, &dev_attr_logo_status);
if (ret)
dev_warn(dev, "Failed to create logo_status attribute: %d\n", ret);
ret = device_create_file(dev, &dev_attr_logo_control);
if (ret)
dev_warn(dev, "Failed to create logo_control attribute: %d\n", ret);
}
/**
* @brief 移除Logo属性从sysfs
* @param priv 驱动私有数据
*
* 从sysfs中移除Logo相关的属性文件。
*/
void st7789_remove_logo_attributes(struct st7789_data *priv)
{
struct device *dev = priv->dev;
device_remove_file(dev, &dev_attr_logo_status);
device_remove_file(dev, &dev_attr_logo_control);
}
/**
* @brief 初始化Logo支持
* @param priv 驱动私有数据
* @return 成功返回0
*
* 在驱动探测时初始化Logo支持。
*/
int st7789_init_logo(struct st7789_data *priv)
{
int ret;
/* 加载Logo */
ret = st7789_load_logo(priv);
if (ret) {
dev_warn(priv->dev, "Failed to load logo: %d\n", ret);
return ret;
}
/* 添加sysfs属性 */
st7789_add_logo_attributes(priv);
/* 显示Logo */
st7789_display_logo(priv);
return 0;
}
/**
* @brief 清理Logo资源
* @param priv 驱动私有数据
*
* 在驱动移除时清理Logo相关资源。
*/
void st7789_cleanup_logo(struct st7789_data *priv)
{
/* 移除sysfs属性 */
st7789_remove_logo_attributes(priv);
/* 清理Logo数据 */
if (priv->logo_loaded && priv->logo_data) {
if (priv->logo_data != (u8 *)st7789_default_logo) {
vfree((void *)priv->logo_data);
}
priv->logo_data = NULL;
priv->logo_loaded = false;
}
}
9.3 驱动模块参数
/**
* @file st7789_params.c
* @brief ST7789驱动模块参数
*
* 定义驱动模块参数,允许在加载模块时配置。
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include "st7789_fb.h"
/* 模块参数:显示宽度 */
static unsigned int width = ST7789_DEFAULT_WIDTH;
module_param(width, uint, 0644);
MODULE_PARM_DESC(width, "Display width in pixels (default: 320)");
/* 模块参数:显示高度 */
static unsigned int height = ST7789_DEFAULT_HEIGHT;
module_param(height, uint, 0644);
MODULE_PARM_DESC(height, "Display height in pixels (default: 240)");
/* 模块参数:旋转角度 */
static unsigned int rotation = 0;
module_param(rotation, uint, 0644);
MODULE_PARM_DESC(rotation, "Display rotation (0, 90, 180, 270)");
/* 模块参数:SPI速度 */
static unsigned int spi_speed = ST7789_DEFAULT_SPI_SPEED;
module_param(spi_speed, uint, 0644);
MODULE_PARM_DESC(spi_speed, "SPI clock speed in Hz (default: 40000000)");
/* 模块参数:帧率 */
static unsigned int fps = ST7789_DEFAULT_FPS;
module_param(fps, uint, 0644);
MODULE_PARM_DESC(fps, "Target frame rate (1-60, default: 30)");
/* 模块参数:BGR顺序 */
static bool bgr = false;
module_param(bgr, bool, 0644);
MODULE_PARM_DESC(bgr, "Enable BGR color order (default: RGB)");
/* 模块参数:调试模式 */
static bool debug = false;
module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, "Enable debug output (default: false)");
/* 模块参数:背光亮度 */
static unsigned int brightness = 255;
module_param(brightness, uint, 0644);
MODULE_PARM_DESC(brightness, "Backlight brightness (0-255, default: 255)");
/* 模块参数:强制复位 */
static bool force_reset = false;
module_param(force_reset, bool, 0644);
MODULE_PARM_DESC(force_reset, "Force hardware reset on probe (default: false)");
/**
* @brief 应用模块参数到驱动实例
* @param priv 驱动私有数据
*
* 将模块参数应用到新创建的驱动实例。
*/
void st7789_apply_module_params(struct st7789_data *priv)
{
/* 应用显示尺寸参数 */
if (width >= 240 && width <= 480)
priv->width = width;
if (height >= 240 && height <= 320)
priv->height = height;
/* 应用旋转参数 */
if (rotation <= 270 && (rotation % 90) == 0)
priv->rotation = rotation;
/* 应用SPI速度参数 */
if (spi_speed >= 1000000 && spi_speed <= 80000000)
priv->spi_speed = spi_speed;
/* 应用帧率参数 */
if (fps >= 1 && fps <= 60)
priv->fps = fps;
/* 应用BGR顺序参数 */
priv->bgr = bgr;
/* 应用调试模式参数 */
priv->debug = debug;
/* 应用背光亮度参数 */
if (brightness <= 255)
priv->brightness = brightness;
/* 应用强制复位参数 */
if (force_reset) {
dev_info(priv->dev, "Force reset enabled\n");
/* 在探测时会执行强制复位 */
}
}
9.4 st7789_fb.c 完整
/**
* @file st7789_main.c
* @brief ST7789 SPI TFT FrameBuffer驱动主实现
*
* 该驱动为ST7789 SPI TFT显示器提供FrameBuffer接口。
* 支持320x240 RGB565模式,包含硬件加速和电源管理功能。
*
* @author Your Name
* @date 2024
* @copyright GPL v2
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/list.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/completion.h>
#include "st7789_fb.h"
/* 模块信息 */
MODULE_AUTHOR("Your Name <your.email@example.com>");
MODULE_DESCRIPTION("ST7789 SPI TFT FrameBuffer驱动");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0.0");
/* 默认显示参数 */
#define ST7789_DEFAULT_WIDTH 320
#define ST7789_DEFAULT_HEIGHT 240
#define ST7789_DEFAULT_BPP 16
#define ST7789_DEFAULT_FPS 30
/* 全局驱动状态 */
static struct dentry *st7789_debugfs_root;
static LIST_HEAD(st7789_devices);
static DEFINE_MUTEX(st7789_list_lock);
/* SPI传输缓冲区(命令/数据) */
#define SPI_BUF_SIZE 256
struct st7789_spi_buffer {
u8 data[SPI_BUF_SIZE]; /* 数据缓冲区 */
struct spi_transfer xfer; /* SPI传输结构 */
struct spi_message msg; /* SPI消息结构 */
};
/**
* @struct st7789_display_mode
* @brief ST7789显示模式配置
*
* 包含不同显示模式的时序参数。
*/
struct st7789_display_mode {
u16 width; /* 显示宽度 */
u16 height; /* 显示高度 */
u16 hsync_start; /* 水平同步开始 */
u16 hsync_end; /* 水平同步结束 */
u16 htotal; /* 水平总周期 */
u16 vsync_start; /* 垂直同步开始 */
u16 vsync_end; /* 垂直同步结束 */
u16 vtotal; /* 垂直总周期 */
u32 clock; /* 像素时钟(kHz) */
u32 refresh; /* 刷新率(Hz) */
u8 bpp; /* 每像素位数 */
u32 flags; /* 标志位 */
};
/* 支持的显示模式 */
static const struct st7789_display_mode st7789_modes[] = {
{
.width = 320,
.height = 240,
.hsync_start = 320 + 10,
.hsync_end = 320 + 10 + 10,
.htotal = 320 + 10 + 10 + 20,
.vsync_start = 240 + 10,
.vsync_end = 240 + 10 + 10,
.vtotal = 240 + 10 + 10 + 10,
.clock = 6000, /* 6 MHz */
.refresh = 60,
.bpp = 16,
.flags = 0,
},
};
/**
* @brief 向ST7789写入命令(优化版本)
* @param priv 驱动私有数据
* @param cmd 命令字节
*
* 使用预分配的SPI消息进行优化的命令写入。
*/
static void st7789_write_cmd_optimized(struct st7789_data *priv, u8 cmd)
{
int ret;
struct st7789_spi_buffer *buf = &priv->spi_buf;
/* 设置DC引脚为命令模式 */
gpiod_set_value(priv->dc_gpio, 0);
/* 准备SPI传输 */
buf->data[0] = cmd;
buf->xfer.tx_buf = buf->data;
buf->xfer.len = 1;
buf->xfer.speed_hz = priv->spi_speed;
buf->xfer.bits_per_word = 8;
spi_message_init(&buf->msg);
spi_message_add_tail(&buf->xfer, &buf->msg);
/* 发送命令 */
ret = spi_sync(priv->spi, &buf->msg);
if (ret < 0)
dev_err_ratelimited(priv->dev, "命令0x%02x发送失败: %d\n", cmd, ret);
}
/**
* @brief 向ST7789写入数据(优化版本)
* @param priv 驱动私有数据
* @param data 数据字节
*
* 使用预分配的SPI消息进行优化的数据写入。
*/
static void st7789_write_data_optimized(struct st7789_data *priv, u8 data)
{
int ret;
struct st7789_spi_buffer *buf = &priv->spi_buf;
/* 设置DC引脚为数据模式 */
gpiod_set_value(priv->dc_gpio, 1);
/* 准备SPI传输 */
buf->data[0] = data;
buf->xfer.tx_buf = buf->data;
buf->xfer.len = 1;
buf->xfer.speed_hz = priv->spi_speed;
spi_message_init(&buf->msg);
spi_message_add_tail(&buf->xfer, &buf->msg);
/* 发送数据 */
ret = spi_sync(priv->spi, &buf->msg);
if (ret < 0)
dev_err_ratelimited(priv->dev, "数据0x%02x发送失败: %d\n", data, ret);
}
/**
* @brief 向ST7789写入缓冲区(支持DMA)
* @param priv 驱动私有数据
* @param buf 数据缓冲区
* @param len 缓冲区长度
* @return 成功返回写入的字节数,失败返回错误码
*
* 如果配置了DMA,对大传输使用DMA。
*/
static int st7789_write_buf_dma(struct st7789_data *priv,
const u8 *buf, size_t len)
{
int ret;
struct spi_transfer xfer = {
.tx_buf = buf,
.len = len,
.speed_hz = priv->spi_speed,
};
struct spi_message msg;
if (!buf || len == 0)
return 0;
/* 设置DC引脚为数据模式 */
gpiod_set_value(priv->dc_gpio, 1);
/* 准备SPI消息 */
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
#ifdef ST7789_USE_DMA
/* 如果缓冲区足够大且支持DMA,则启用DMA */
if (len > 64 && priv->spi->master->dma_tx) {
xfer.tx_dma = dma_map_single(priv->dev, (void *)buf, len,
DMA_TO_DEVICE);
if (dma_mapping_error(priv->dev, xfer.tx_dma)) {
dev_warn(priv->dev, "DMA映射失败,回退到PIO模式\n");
} else {
xfer.tx_dma = xfer.tx_dma;
}
}
#endif
/* 发送数据 */
ret = spi_sync(priv->spi, &msg);
#ifdef ST7789_USE_DMA
/* 如果使用了DMA,取消映射 */
if (xfer.tx_dma)
dma_unmap_single(priv->dev, xfer.tx_dma, len, DMA_TO_DEVICE);
#endif
if (ret < 0) {
dev_err_ratelimited(priv->dev, "缓冲区写入失败: %d\n", ret);
return ret;
}
return len;
}
/**
* @brief 向ST7789写入16位数据
* @param priv 驱动私有数据
* @param data 16位数据
*
* 优化的16位数据写入。
*/
static void st7789_write_data16(struct st7789_data *priv, u16 data)
{
u8 buf[2];
buf[0] = (data >> 8) & 0xFF; /* 高字节 */
buf[1] = data & 0xFF; /* 低字节 */
st7789_write_buf_dma(priv, buf, 2);
}
/**
* @brief 从ST7789读取数据
* @param priv 驱动私有数据
* @param cmd 读取命令
* @param buf 存储读取数据的缓冲区
* @param len 要读取的字节数
* @return 成功返回读取的字节数,失败返回错误码
*
* 注意:ST7789读取能力有限,主要用于诊断。
*/
static int st7789_read_data(struct st7789_data *priv, u8 cmd,
u8 *buf, size_t len)
{
int ret;
struct spi_transfer xfer[2] = {};
struct spi_message msg;
if (!buf || len == 0)
return 0;
/* 命令阶段 */
xfer[0].tx_buf = &cmd;
xfer[0].len = 1;
xfer[0].speed_hz = priv->spi_speed;
/* 数据阶段 */
xfer[1].rx_buf = buf;
xfer[1].len = len;
xfer[1].speed_hz = priv->spi_speed;
/* 设置DC引脚:先命令模式,后数据模式 */
gpiod_set_value(priv->dc_gpio, 0);
spi_message_init(&msg);
spi_message_add_tail(&xfer[0], &msg);
gpiod_set_value(priv->dc_gpio, 1);
spi_message_add_tail(&xfer[1], &msg);
ret = spi_sync(priv->spi, &msg);
if (ret < 0) {
dev_err(priv->dev, "读取命令0x%02x失败: %d\n", cmd, ret);
return ret;
}
return len;
}
/**
* @brief 设置显示地址窗口
* @param priv 驱动私有数据
* @param x0 起始X坐标
* @param y0 起始Y坐标
* @param x1 结束X坐标
* @param y1 结束Y坐标
*
* 为后续的RAM写操作设置活动显示区域。
*/
static void st7789_set_addr_window(struct st7789_data *priv,
u16 x0, u16 y0, u16 x1, u16 y1)
{
/* 根据旋转调整坐标 */
#ifdef ST7789_ROTATION_SUPPORT
switch (priv->rotation) {
case 90:
case 270:
/* 纵向模式下交换坐标 */
if (priv->swap_xy) {
u16 tmp;
tmp = x0; x0 = y0; y0 = tmp;
tmp = x1; x1 = y1; y1 = tmp;
}
break;
}
#endif
/* 设置列地址 */
st7789_write_cmd_optimized(priv, ST7789_CASET);
st7789_write_data16(priv, x0 + priv->offset_x);
st7789_write_data16(priv, x1 + priv->offset_x);
/* 设置行地址 */
st7789_write_cmd_optimized(priv, ST7789_RASET);
st7789_write_data16(priv, y0 + priv->offset_y);
st7789_write_data16(priv, y1 + priv->offset_y);
/* 准备内存写入 */
st7789_write_cmd_optimized(priv, ST7789_RAMWR);
priv->addr_window_set = true;
}
/**
* @brief 更新部分显示区域
* @param priv 驱动私有数据
* @param x0 起始X坐标
* @param y0 起始Y坐标
* @param x1 结束X坐标
* @param y1 结束Y坐标
*
* 针对部分屏幕区域的优化更新。
*/
static void st7789_update_area(struct st7789_data *priv,
u16 x0, u16 y0, u16 x1, u16 y1)
{
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 *line_buffer;
int x, y;
int width, height;
int line_len;
/* 计算区域尺寸 */
width = x1 - x0 + 1;
height = y1 - y0 + 1;
if (width <= 0 || height <= 0)
return;
/* 分配行缓冲区 */
line_len = width * 2; /* RGB565:每像素2字节 */
line_buffer = kmalloc(line_len, GFP_KERNEL);
if (!line_buffer) {
dev_err(priv->dev, "分配行缓冲区失败\n");
return;
}
/* 为该区域设置地址窗口 */
st7789_set_addr_window(priv, x0, y0, x1, y1);
/* 传输区域数据 */
for (y = y0; y <= y1; y++) {
u16 *line_start = fb + y * priv->width + x0;
/* 将行转换为字节流 */
for (x = 0; x < width; x++) {
u16 pixel = line_start[x];
line_buffer[x * 2] = (pixel >> 8) & 0xFF; /* 高字节 */
line_buffer[x * 2 + 1] = pixel & 0xFF; /* 低字节 */
}
/* 发送行数据 */
st7789_write_buf_dma(priv, (u8 *)line_buffer, line_len);
}
kfree(line_buffer);
}
/**
* @brief 全屏更新
* @param priv 驱动私有数据
*
* 从framebuffer更新整个显示。
*/
static void st7789_update_full(struct st7789_data *priv)
{
st7789_update_area(priv, 0, 0, priv->width - 1, priv->height - 1);
}
/**
* @brief 延迟IO更新回调
* @param info FrameBuffer信息结构
* @param pagelist 脏页列表
*
* 当页面变脏时由延迟IO子系统调用。
*/
static void st7789_fb_deferred_io(struct fb_info *info,
struct list_head *pagelist)
{
struct st7789_data *priv = info->par;
struct page *page;
unsigned int y1 = 0, y2 = 0;
int count = 0;
/* 计算脏区域 */
list_for_each_entry(page, pagelist, lru) {
unsigned int y = page->index << (PAGE_SHIFT - info->fix.line_shift);
if (count == 0) {
y1 = y;
y2 = y;
} else {
if (y < y1)
y1 = y;
if (y > y2)
y2 = y;
}
count++;
}
if (count > 0) {
/* 更新脏区域 */
y2 = min_t(unsigned int, y2 + (PAGE_SIZE >> info->fix.line_shift) - 1,
info->var.yres - 1);
st7789_update_area(priv, 0, y1, info->var.xres - 1, y2);
}
}
/**
* @brief ST7789的延迟IO结构
*/
static struct fb_deferred_io st7789_defio = {
.delay = HZ / ST7789_DEFAULT_FPS, /* 基于FPS的更新延迟 */
.deferred_io = st7789_fb_deferred_io,
};
/**
* @brief 初始化ST7789的延迟IO
* @param info FrameBuffer信息结构
*
* 为高效的屏幕更新设置延迟IO。
*/
static void st7789_init_defio(struct fb_info *info)
{
struct st7789_data *priv = info->par;
/* 配置延迟IO */
info->fbdefio = &st7789_defio;
fb_deferred_io_init(info);
/* 设置脏函数 */
priv->dirty = fb_deferred_io_mmap_damage;
dev_info(priv->dev, "延迟IO已初始化(延迟: %d ms)\n",
jiffies_to_msecs(st7789_defio.delay));
}
/**
* @brief 清理ST7789的延迟IO
* @param info FrameBuffer信息结构
*/
static void st7789_cleanup_defio(struct fb_info *info)
{
fb_deferred_io_cleanup(info);
}
/**
* @brief 硬件复位ST7789
* @param priv 驱动私有数据
*
* 通过复位引脚执行硬件复位序列,遵循ST7789的复位时序要求。
*/
static void st7789_hardware_reset(struct st7789_data *priv)
{
if (!priv->reset_gpio) {
dev_warn(priv->dev, "未指定复位GPIO,使用软件复位\n");
st7789_write_cmd_optimized(priv, ST7789_SWRESET);
msleep(150);
return;
}
dev_dbg(priv->dev, "执行硬件复位\n");
/* 复位序列 */
gpiod_set_value(priv->reset_gpio, 0);
msleep(10);
gpiod_set_value(priv->reset_gpio, 1);
msleep(120); /* ST7789需要至少120ms启动时间 */
/* 等待复位完成 */
usleep_range(5000, 10000);
dev_dbg(priv->dev, "硬件复位完成\n");
}
/**
* @brief 发送初始化命令序列
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 发送ST7789显示控制器的完整初始化命令序列。
*/
static int st7789_send_init_sequence(struct st7789_data *priv)
{
dev_info(priv->dev, "发送ST7789初始化序列\n");
/* 1. 退出睡眠模式 */
st7789_write_cmd_optimized(priv, ST7789_SLPOUT);
msleep(120);
/* 2. 设置接口像素格式为16位RGB565 */
st7789_write_cmd_optimized(priv, ST7789_COLMOD);
st7789_write_data_optimized(priv, 0x55); /* RGB565 */
/* 3. 配置内存访问控制 */
st7789_write_cmd_optimized(priv, ST7789_MADCTL);
st7789_write_data_optimized(priv, priv->madctl_val);
/* 4. 配置帧率控制(PORCH设置) */
st7789_write_cmd_optimized(priv, 0xB2);
st7789_write_data_optimized(priv, 0x0C);
st7789_write_data_optimized(priv, 0x0C);
st7789_write_data_optimized(priv, 0x00);
st7789_write_data_optimized(priv, 0x33);
st7789_write_data_optimized(priv, 0x33);
/* 5. 配置门控控制 */
st7789_write_cmd_optimized(priv, 0xB7);
st7789_write_data_optimized(priv, 0x35); /* VGH=13.26V, VGL=-10.43V */
/* 6. 配置VCOM电压 */
st7789_write_cmd_optimized(priv, 0xBB);
st7789_write_data_optimized(priv, 0x19); /* VCOM=0.725V */
/* 7. 配置LCM控制 */
st7789_write_cmd_optimized(priv, 0xC0);
st7789_write_data_optimized(priv, 0x2C); /* LCM=标准模式 */
/* 8. 配置VDV和VRH使能 */
st7789_write_cmd_optimized(priv, 0xC2);
st7789_write_data_optimized(priv, 0x01);
/* 9. 配置VRH电压 */
st7789_write_cmd_optimized(priv, 0xC3);
st7789_write_data_optimized(priv, 0x12); /* VRH=4.6V */
/* 10. 配置VDV电压 */
st7789_write_cmd_optimized(priv, 0xC4);
st7789_write_data_optimized(priv, 0x20); /* VDV=0V */
/* 11. 配置VCOM偏移 */
st7789_write_cmd_optimized(priv, 0xC6);
st7789_write_data_optimized(priv, 0x0F); /* VCOM偏移=0V */
/* 12. 配置电源控制 */
st7789_write_cmd_optimized(priv, 0xD0);
st7789_write_data_optimized(priv, 0xA4);
st7789_write_data_optimized(priv, 0xA1);
/* 13. 正极伽马校正 */
st7789_write_cmd_optimized(priv, 0xE0);
st7789_write_data_optimized(priv, 0xD0);
st7789_write_data_optimized(priv, 0x04);
st7789_write_data_optimized(priv, 0x0D);
st7789_write_data_optimized(priv, 0x11);
st7789_write_data_optimized(priv, 0x13);
st7789_write_data_optimized(priv, 0x2B);
st7789_write_data_optimized(priv, 0x3F);
st7789_write_data_optimized(priv, 0x54);
st7789_write_data_optimized(priv, 0x4C);
st7789_write_data_optimized(priv, 0x18);
st7789_write_data_optimized(priv, 0x0D);
st7789_write_data_optimized(priv, 0x0B);
st7789_write_data_optimized(priv, 0x1F);
st7789_write_data_optimized(priv, 0x23);
/* 14. 负极伽马校正 */
st7789_write_cmd_optimized(priv, 0xE1);
st7789_write_data_optimized(priv, 0xD0);
st7789_write_data_optimized(priv, 0x04);
st7789_write_data_optimized(priv, 0x0C);
st7789_write_data_optimized(priv, 0x11);
st7789_write_data_optimized(priv, 0x13);
st7789_write_data_optimized(priv, 0x2C);
st7789_write_data_optimized(priv, 0x3F);
st7789_write_data_optimized(priv, 0x44);
st7789_write_data_optimized(priv, 0x51);
st7789_write_data_optimized(priv, 0x2F);
st7789_write_data_optimized(priv, 0x1F);
st7789_write_data_optimized(priv, 0x1F);
st7789_write_data_optimized(priv, 0x20);
st7789_write_data_optimized(priv, 0x23);
/* 15. 退出空闲模式 */
st7789_write_cmd_optimized(priv, ST7789_IDMOFF);
/* 16. 打开正常显示模式 */
st7789_write_cmd_optimized(priv, ST7789_NORON);
msleep(10);
/* 17. 清除显示内存(填充黑色) */
st7789_set_addr_window(priv, 0, 0, priv->width - 1, priv->height - 1);
for (int i = 0; i < priv->width * priv->height; i++) {
st7789_write_data16(priv, 0x0000);
}
/* 18. 打开显示 */
st7789_write_cmd_optimized(priv, ST7789_DISPON);
msleep(100);
dev_info(priv->dev, "ST7789初始化序列完成\n");
return 0;
}
/**
* @brief 配置屏幕旋转参数
* @param priv 驱动私有数据
*
* 根据旋转角度配置MADCTL寄存器和显示偏移。
*/
static void st7789_set_rotation(struct st7789_data *priv)
{
u8 madctl = MADCTL_BGR; /* 默认BGR顺序 */
switch (priv->rotation) {
case 90:
madctl |= MADCTL_MV | MADCTL_MX;
priv->swap_xy = true;
priv->invert_x = false;
priv->invert_y = false;
priv->offset_x = 0;
priv->offset_y = 0;
break;
case 180:
madctl |= MADCTL_MY | MADCTL_MX;
priv->swap_xy = false;
priv->invert_x = true;
priv->invert_y = true;
priv->offset_x = 0;
priv->offset_y = 80; /* 对于320x240屏幕,Y偏移80像素 */
break;
case 270:
madctl |= MADCTL_MV | MADCTL_MY;
priv->swap_xy = true;
priv->invert_x = true;
priv->invert_y = false;
priv->offset_x = 80; /* 对于240x320屏幕,X偏移80像素 */
priv->offset_y = 0;
break;
default: /* 0度 */
madctl |= MADCTL_RGB; /* RGB顺序 */
priv->swap_xy = false;
priv->invert_x = false;
priv->invert_y = false;
priv->offset_x = 0;
priv->offset_y = 80; /* 对于320x240屏幕,Y偏移80像素 */
break;
}
priv->madctl_val = madctl;
dev_info(priv->dev, "旋转设置为%d度 (MADCTL=0x%02x)\n",
priv->rotation, madctl);
}
/**
* @brief 配置显示偏移
* @param priv 驱动私有数据
*
* 设置ST7789的显示偏移寄存器。
*/
static void st7789_set_display_offset(struct st7789_data *priv)
{
if (priv->offset_x > 0 || priv->offset_y > 0) {
st7789_write_cmd_optimized(priv, 0x37); /* VSCSAD - 垂直滚动开始地址 */
st7789_write_data16(priv, priv->offset_y);
dev_dbg(priv->dev, "显示偏移设置为 X=%d, Y=%d\n",
priv->offset_x, priv->offset_y);
}
}
/**
* @brief 打开显示电源
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 执行完整的电源开启序列。
*/
static int st7789_power_on(struct st7789_data *priv)
{
int ret = 0;
if (priv->powered_on)
return 0;
dev_info(priv->dev, "打开显示电源\n");
mutex_lock(&priv->lock);
/* 1. 使能电源稳压器(如果存在) */
if (priv->vdd_reg) {
ret = regulator_enable(priv->vdd_reg);
if (ret) {
dev_err(priv->dev, "使能VDD稳压器失败: %d\n", ret);
goto unlock;
}
msleep(10);
}
if (priv->vcc_reg) {
ret = regulator_enable(priv->vcc_reg);
if (ret) {
dev_err(priv->dev, "使能VCC稳压器失败: %d\n", ret);
goto disable_vdd;
}
msleep(10);
}
/* 2. 硬件复位 */
st7789_hardware_reset(priv);
/* 3. 配置旋转参数 */
st7789_set_rotation(priv);
/* 4. 发送初始化序列 */
ret = st7789_send_init_sequence(priv);
if (ret)
goto disable_regulators;
/* 5. 配置显示偏移 */
st7789_set_display_offset(priv);
/* 6. 使能背光 */
if (priv->bl_dev) {
ret = backlight_update_status(priv->bl_dev);
if (ret)
dev_warn(priv->dev, "更新背光状态失败: %d\n", ret);
} else if (priv->bl_gpio) {
gpiod_set_value(priv->bl_gpio, 1);
}
/* 7. 更新显示状态 */
priv->powered_on = true;
priv->sleeping = false;
/* 8. 强制刷新显示 */
st7789_update_full(priv);
mutex_unlock(&priv->lock);
dev_info(priv->dev, "显示电源已成功打开\n");
return 0;
disable_regulators:
if (priv->vcc_reg)
regulator_disable(priv->vcc_reg);
disable_vdd:
if (priv->vdd_reg)
regulator_disable(priv->vdd_reg);
unlock:
mutex_unlock(&priv->lock);
return ret;
}
/**
* @brief 关闭显示电源
* @param priv 驱动私有数据
*
* 执行完整的电源关闭序列,进入最低功耗状态。
*/
static void st7789_power_off(struct st7789_data *priv)
{
if (!priv->powered_on)
return;
dev_info(priv->dev, "关闭显示电源\n");
mutex_lock(&priv->lock);
/* 1. 关闭背光 */
if (priv->bl_gpio)
gpiod_set_value(priv->bl_gpio, 0);
/* 2. 进入睡眠模式 */
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
/* 3. 禁用电源稳压器 */
if (priv->vcc_reg)
regulator_disable(priv->vcc_reg);
if (priv->vdd_reg)
regulator_disable(priv->vdd_reg);
/* 4. 更新显示状态 */
priv->powered_on = false;
priv->sleeping = true;
mutex_unlock(&priv->lock);
dev_info(priv->dev, "显示电源已关闭\n");
}
/**
* @brief 系统挂起回调
* @param dev 设备指针
* @return 成功返回0
*
* 当系统进入挂起状态时调用。
*/
static int st7789_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_info(dev, "挂起显示\n");
/* 取消所有待处理的工作 */
cancel_delayed_work_sync(&priv->dirty_work);
cancel_work_sync(&priv->update_work);
/* 关闭显示电源 */
st7789_power_off(priv);
return 0;
}
/**
* @brief 系统恢复回调
* @param dev 设备指针
* @return 成功返回0
*
* 当系统从挂起状态恢复时调用。
*/
static int st7789_resume(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_info(dev, "恢复显示\n");
/* 重新打开显示电源 */
st7789_power_on(priv);
return 0;
}
/**
* @brief 运行时挂起回调
* @param dev 设备指针
* @return 成功返回0
*
* 当设备进入运行时挂起状态时调用。
*/
static int st7789_runtime_suspend(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_dbg(dev, "运行时挂起显示\n");
if (priv->powered_on && !priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
priv->sleeping = true;
}
return 0;
}
/**
* @brief 运行时恢复回调
* @param dev 设备指针
* @return 成功返回0
*
* 当设备从运行时挂起状态恢复时调用。
*/
static int st7789_runtime_resume(struct device *dev)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
dev_dbg(dev, "运行时恢复显示\n");
if (priv->powered_on && priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_SLPOUT);
msleep(120);
st7789_write_cmd_optimized(priv, ST7789_DISPON);
priv->sleeping = false;
}
return 0;
}
/**
* @brief 电源管理操作集
*/
static const struct dev_pm_ops st7789_pm_ops = {
.suspend = st7789_suspend,
.resume = st7789_resume,
.poweroff = st7789_suspend,
.restore = st7789_resume,
.runtime_suspend = st7789_runtime_suspend,
.runtime_resume = st7789_runtime_resume,
};
/**
* @brief 更新背光状态
* @param bl_dev 背光设备
* @return 成功返回0
*
* 背光子系统的回调函数,当亮度改变时调用。
*/
static int st7789_backlight_update_status(struct backlight_device *bl_dev)
{
struct st7789_data *priv = bl_get_data(bl_dev);
int brightness = bl_dev->props.brightness;
/* 检查显示状态 */
if (bl_dev->props.power != FB_BLANK_UNBLANK ||
bl_dev->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
mutex_lock(&priv->lock);
/* 更新背光亮度 */
if (priv->bl_gpio) {
/* GPIO背光控制 */
gpiod_set_value(priv->bl_gpio, brightness > 0 ? 1 : 0);
} else {
/* PWM背光控制(如果支持) */
dev_dbg(priv->dev, "背光亮度: %d\n", brightness);
}
priv->brightness = brightness;
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 获取背光亮度
* @param bl_dev 背光设备
* @return 当前亮度值
*
* 背光子系统的回调函数,返回当前亮度。
*/
static int st7789_backlight_get_brightness(struct backlight_device *bl_dev)
{
struct st7789_data *priv = bl_get_data(bl_dev);
return priv->brightness;
}
/**
* @brief 背光操作集
*/
static const struct backlight_ops st7789_backlight_ops = {
.update_status = st7789_backlight_update_status,
.get_brightness = st7789_backlight_get_brightness,
};
/**
* @brief 初始化背光控制
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 根据设备树配置初始化背光控制。
*/
static int st7789_init_backlight(struct st7789_data *priv)
{
struct backlight_properties props = {
.type = BACKLIGHT_RAW,
.max_brightness = 255,
.brightness = 255,
.power = FB_BLANK_UNBLANK,
};
struct device *dev = priv->dev;
struct backlight_device *bl_dev;
int ret;
/* 尝试获取GPIO背光控制 */
priv->bl_gpio = devm_gpiod_get_optional(dev, "backlight",
GPIOD_OUT_LOW);
if (IS_ERR(priv->bl_gpio)) {
ret = PTR_ERR(priv->bl_gpio);
dev_warn(dev, "获取背光GPIO失败: %d\n", ret);
priv->bl_gpio = NULL;
}
if (priv->bl_gpio) {
/* GPIO背光控制可用 */
gpiod_set_value(priv->bl_gpio, 1);
dev_info(dev, "使用GPIO背光控制\n");
return 0;
}
/* 尝试获取PWM背光控制 */
priv->pwm = devm_pwm_get_optional(dev, NULL);
if (IS_ERR(priv->pwm)) {
ret = PTR_ERR(priv->pwm);
dev_warn(dev, "获取PWM背光失败: %d\n", ret);
priv->pwm = NULL;
}
if (priv->pwm) {
/* PWM背光控制可用 */
ret = pwm_apply_state(priv->pwm, &priv->pwm_state);
if (ret) {
dev_warn(dev, "应用PWM状态失败: %d\n", ret);
priv->pwm = NULL;
} else {
dev_info(dev, "使用PWM背光控制\n");
return 0;
}
}
/* 如果没有GPIO或PWM背光,注册背光子系统设备 */
bl_dev = devm_backlight_device_register(dev, "st7789-bl",
dev, priv,
&st7789_backlight_ops,
&props);
if (IS_ERR(bl_dev)) {
ret = PTR_ERR(bl_dev);
dev_warn(dev, "注册背光设备失败: %d\n", ret);
return ret;
}
priv->bl_dev = bl_dev;
backlight_update_status(bl_dev);
dev_info(dev, "使用背光子系\n");
return 0;
}
/**
* @brief 检查显示变量参数
* @param var 变量参数结构
* @param info FrameBuffer信息
* @return 成功返回0,失败返回错误码
*
* 验证应用程序请求的显示参数是否受硬件支持。
*/
static int st7789_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "check_var: xres=%u, yres=%u, bpp=%u\n",
var->xres, var->yres, var->bits_per_pixel);
/* 检查分辨率 */
if (var->xres > priv->width || var->yres > priv->height) {
dev_err(priv->dev, "不支持的分辨率: %ux%u (最大: %ux%u)\n",
var->xres, var->yres, priv->width, priv->height);
return -EINVAL;
}
/* 检查颜色深度 */
if (var->bits_per_pixel != 16 && var->bits_per_pixel != 32) {
dev_err(priv->dev, "不支持的bpp: %u (必须为16或32)\n",
var->bits_per_pixel);
return -EINVAL;
}
/* 设置支持的参数 */
var->xres = var->xres_virtual = priv->width;
var->yres = var->yres_virtual = priv->height;
/* RGB565格式配置(16bpp) */
if (var->bits_per_pixel == 16) {
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;
} else { /* RGB888格式配置(32bpp) */
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;
}
/* 显示时序参数 */
var->pixclock = 10000000; /* 10MHz像素时钟 */
var->left_margin = 10;
var->right_margin = 10;
var->upper_margin = 10;
var->lower_margin = 10;
var->hsync_len = 10;
var->vsync_len = 10;
var->sync = 0;
var->vmode = FB_VMODE_NONINTERLACED;
var->rotate = priv->rotation;
return 0;
}
/**
* @brief 设置显示参数
* @param info FrameBuffer信息
* @return 成功返回0,失败返回错误码
*
* 应用新的显示参数,可能需要重新配置硬件。
*/
static int st7789_set_par(struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "set_par被调用\n");
mutex_lock(&priv->lock);
/* 重新配置硬件 */
if (priv->powered_on) {
st7789_set_rotation(priv);
st7789_write_cmd_optimized(priv, ST7789_MADCTL);
st7789_write_data_optimized(priv, priv->madctl_val);
st7789_set_display_offset(priv);
}
/* 更新FrameBuffer参数 */
info->fix.line_length = info->var.xres * (info->var.bits_per_pixel / 8);
info->fix.visual = (info->var.bits_per_pixel == 16) ?
FB_VISUAL_TRUECOLOR : FB_VISUAL_TRUECOLOR;
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 设置颜色寄存器
* @param regno 寄存器编号
* @param red 红色分量
* @param green 绿色分量
* @param blue 蓝色分量
* @param transp 透明度分量
* @param info FrameBuffer信息
* @return 成功返回0
*
* 支持伪调色板模式的颜色寄存器设置。
*/
static int st7789_setcolreg(unsigned regno, unsigned red,
unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct st7789_data *priv = info->par;
/* ST7789使用硬件调色板,但支持伪调色板 */
if (regno >= 16)
return -EINVAL;
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
u32 *pal = info->pseudo_palette;
u32 val;
/* 将24/32位RGB转换为RGB565或RGB888 */
red >>= 8;
green >>= 8;
blue >>= 8;
if (info->var.bits_per_pixel == 16) {
val = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3);
} else {
val = (red << 16) | (green << 8) | blue;
if (info->var.transp.length > 0)
val |= (transp << 24);
}
pal[regno] = val;
dev_dbg(priv->dev, "setcolreg[%d] = 0x%08x\n", regno, val);
}
return 0;
}
/**
* @brief 空白显示控制
* @param blank_mode 空白模式
* @param info FrameBuffer信息
* @return 成功返回0
*
* 控制显示器的电源状态,支持多种省电模式。
*/
static int st7789_blank(int blank_mode, struct fb_info *info)
{
struct st7789_data *priv = info->par;
dev_dbg(priv->dev, "blank: 模式=%d\n", blank_mode);
mutex_lock(&priv->lock);
switch (blank_mode) {
case FB_BLANK_UNBLANK: /* 正常显示 */
if (priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_SLPOUT);
msleep(120);
priv->sleeping = false;
}
st7789_write_cmd_optimized(priv, ST7789_DISPON);
if (priv->bl_dev)
backlight_update_status(priv->bl_dev);
break;
case FB_BLANK_NORMAL: /* 正常空白(背光关) */
if (priv->bl_dev) {
struct backlight_device *bl = priv->bl_dev;
bl->props.brightness = 0;
backlight_update_status(bl);
}
break;
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN: /* 完全关闭 */
if (priv->bl_dev) {
struct backlight_device *bl = priv->bl_dev;
bl->props.brightness = 0;
backlight_update_status(bl);
}
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
priv->sleeping = true;
break;
default:
mutex_unlock(&priv->lock);
return -EINVAL;
}
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 填充矩形区域
* @param info FrameBuffer信息
* @param rect 矩形区域信息
*
* 硬件加速的矩形填充操作。
*/
static void st7789_fillrect(struct fb_info *info,
const struct fb_fillrect *rect)
{
struct st7789_data *priv = info->par;
u32 color = rect->color;
int x = rect->dx;
int y = rect->dy;
int width = rect->width;
int height = rect->height;
/* 边界检查 */
if (x >= priv->width || y >= priv->height)
return;
if (x + width > priv->width)
width = priv->width - x;
if (y + height > priv->height)
height = priv->height - y;
mutex_lock(&priv->lock);
/* 从伪调色板获取颜色值 */
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
if (color < 16)
color = info->pseudo_palette[color];
}
/* 填充矩形到显存 */
if (info->var.bits_per_pixel == 16) {
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 color16 = (u16)color;
for (int j = 0; j < height; j++) {
u16 *line = fb + (y + j) * priv->width + x;
for (int i = 0; i < width; i++) {
line[i] = color16;
}
}
} else {
u32 *fb = (u32 *)priv->fb_virt_addr;
for (int j = 0; j < height; j++) {
u32 *line = fb + (y + j) * priv->width + x;
for (int i = 0; i < width; i++) {
line[i] = color;
}
}
}
/* 标记脏区域 */
if (info->fbdefio) {
struct fb_deferred_io *fbdefio = info->fbdefio;
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
mutex_unlock(&priv->lock);
}
/**
* @brief 复制显示区域
* @param info FrameBuffer信息
* @param area 区域信息
*
* 硬件加速的区域复制操作。
*/
static void st7789_copyarea(struct fb_info *info,
const struct fb_copyarea *area)
{
struct st7789_data *priv = info->par;
int dx = area->dx;
int dy = area->dy;
int sx = area->sx;
int sy = area->sy;
int width = area->width;
int height = area->height;
/* 边界检查 */
if (dx >= priv->width || dy >= priv->height ||
sx >= priv->width || sy >= priv->height)
return;
if (dx + width > priv->width)
width = priv->width - dx;
if (dy + height > priv->height)
height = priv->height - dy;
if (sx + width > priv->width)
width = priv->width - sx;
if (sy + height > priv->height)
height = priv->height - sy;
mutex_lock(&priv->lock);
/* 执行内存复制 */
if (info->var.bits_per_pixel == 16) {
u16 *fb = (u16 *)priv->fb_virt_addr;
for (int j = 0; j < height; j++) {
u16 *src = fb + (sy + j) * priv->width + sx;
u16 *dst = fb + (dy + j) * priv->width + dx;
memmove(dst, src, width * sizeof(u16));
}
} else {
u32 *fb = (u32 *)priv->fb_virt_addr;
for (int j = 0; j < height; j++) {
u32 *src = fb + (sy + j) * priv->width + sx;
u32 *dst = fb + (dy + j) * priv->width + dx;
memmove(dst, src, width * sizeof(u32));
}
}
/* 标记脏区域 */
if (info->fbdefio) {
struct fb_deferred_io *fbdefio = info->fbdefio;
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
mutex_unlock(&priv->lock);
}
/**
* @brief 图像位块传输
* @param info FrameBuffer信息
* @param image 图像信息
*
* 将单色位图图像绘制到FrameBuffer。
*/
static void st7789_imageblit(struct fb_info *info,
const struct fb_image *image)
{
struct st7789_data *priv = info->par;
const u8 *src = image->data;
u32 fgcolor = image->fg_color;
u32 bgcolor = image->bg_color;
int x = image->dx;
int y = image->dy;
int width = image->width;
int height = image->height;
int bpp = image->depth;
/* 边界检查 */
if (x >= priv->width || y >= priv->height)
return;
if (x + width > priv->width)
width = priv->width - x;
if (y + height > priv->height)
height = priv->height - y;
mutex_lock(&priv->lock);
/* 从伪调色板获取颜色值 */
if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
if (fgcolor < 16)
fgcolor = info->pseudo_palette[fgcolor];
if (bgcolor < 16)
bgcolor = info->pseudo_palette[bgcolor];
}
/* 绘制图像 */
if (info->var.bits_per_pixel == 16) {
u16 *fb = (u16 *)priv->fb_virt_addr;
u16 fg16 = (u16)fgcolor;
u16 bg16 = (u16)bgcolor;
if (bpp == 1) {
/* 1bpp位图 */
for (int j = 0; j < height; j++) {
u16 *line = fb + (y + j) * priv->width + x;
const u8 *src_line = src + j * ((width + 7) / 8);
for (int i = 0; i < width; i++) {
int byte_idx = i / 8;
int bit_idx = 7 - (i % 8);
u8 byte = src_line[byte_idx];
line[i] = (byte & (1 << bit_idx)) ? fg16 : bg16;
}
}
}
}
/* 标记脏区域 */
if (info->fbdefio) {
struct fb_deferred_io *fbdefio = info->fbdefio;
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
}
mutex_unlock(&priv->lock);
}
/**
* @brief FrameBuffer操作集定义
*/
static struct fb_ops st7789_fb_ops = {
.owner = THIS_MODULE,
.fb_check_var = st7789_check_var,
.fb_set_par = st7789_set_par,
.fb_setcolreg = st7789_setcolreg,
.fb_blank = st7789_blank,
.fb_fillrect = st7789_fillrect,
.fb_copyarea = st7789_copyarea,
.fb_imageblit = st7789_imageblit,
.fb_mmap = NULL, /* SPI设备通常不支持mmap */
.fb_ioctl = st7789_ioctl,
.fb_pan_display = NULL,
};
/**
* @brief 从设备树解析显示参数
* @param dev 设备指针
* @param priv 驱动私有数据
* @return 成功返回0,失败返回错误码
*
* 解析设备树节点中的显示配置参数。
*/
static int st7789_parse_dt(struct device *dev, struct st7789_data *priv)
{
struct device_node *np = dev->of_node;
int ret;
if (!np) {
dev_err(dev, "未找到设备树节点\n");
return -EINVAL;
}
/* 解析显示尺寸 */
ret = of_property_read_u32(np, "width", &priv->width);
if (ret) {
dev_warn(dev, "未指定宽度,使用默认值 %d\n", ST7789_DEFAULT_WIDTH);
priv->width = ST7789_DEFAULT_WIDTH;
}
ret = of_property_read_u32(np, "height", &priv->height);
if (ret) {
dev_warn(dev, "未指定高度,使用默认值 %d\n", ST7789_DEFAULT_HEIGHT);
priv->height = ST7789_DEFAULT_HEIGHT;
}
/* 解析旋转角度 */
ret = of_property_read_u32(np, "rotate", &priv->rotation);
if (ret) {
priv->rotation = 0; /* 默认0度 */
} else if (priv->rotation > 270) {
dev_warn(dev, "无效的旋转角度 %d,使用0\n", priv->rotation);
priv->rotation = 0;
}
/* 解析BGR顺序 */
if (of_property_read_bool(np, "bgr")) {
priv->bgr = true;
} else {
priv->bgr = false;
}
/* 解析帧率 */
ret = of_property_read_u32(np, "fps", &priv->fps);
if (ret) {
priv->fps = ST7789_DEFAULT_FPS;
} else if (priv->fps < 1 || priv->fps > 60) {
dev_warn(dev, "无效的帧率 %d,使用默认值 %d\n",
priv->fps, ST7789_DEFAULT_FPS);
priv->fps = ST7789_DEFAULT_FPS;
}
/* 解析调试标志 */
if (of_property_read_bool(np, "debug")) {
priv->debug = true;
} else {
priv->debug = false;
}
/* 解析总线宽度 */
ret = of_property_read_u32(np, "buswidth", &priv->buswidth);
if (ret) {
priv->buswidth = 8; /* 默认8位 */
}
if (priv->buswidth != 8) {
dev_err(dev, "仅支持8位总线宽度\n");
return -EINVAL;
}
/* 解析SPI最大频率 */
ret = of_property_read_u32(np, "spi-max-frequency", &priv->spi_speed);
if (ret) {
dev_warn(dev, "未指定spi-max-frequency,使用 %d Hz\n",
ST7789_SPI_SPEED);
priv->spi_speed = ST7789_SPI_SPEED;
}
dev_info(dev, "设备树解析: %dx%d, 旋转=%d, 帧率=%d, BGR=%s\n",
priv->width, priv->height, priv->rotation, priv->fps,
priv->bgr ? "是" : "否");
return 0;
}
/**
* @brief 获取GPIO资源
* @param dev 设备指针
* @param priv 驱动私有数据
* @return 成功返回0
*
* 从设备树获取GPIO引脚配置。
*/
static int st7789_get_gpios(struct device *dev, struct st7789_data *priv)
{
struct device_node *np = dev->of_node;
/* 获取复位GPIO */
priv->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(priv->reset_gpio)) {
int ret = PTR_ERR(priv->reset_gpio);
dev_warn(dev, "获取复位GPIO失败: %d\n", ret);
priv->reset_gpio = NULL;
} else if (priv->reset_gpio) {
dev_info(dev, "使用GPIO进行复位控制\n");
}
/* 获取数据/命令选择GPIO */
priv->dc_gpio = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
if (IS_ERR(priv->dc_gpio)) {
int ret = PTR_ERR(priv->dc_gpio);
dev_err(dev, "获取DC GPIO失败: %d\n", ret);
return ret;
}
dev_info(dev, "DC GPIO获取成功\n");
/* 获取背光GPIO(可选) */
priv->bl_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(priv->bl_gpio)) {
int ret = PTR_ERR(priv->bl_gpio);
dev_warn(dev, "获取背光GPIO失败: %d\n", ret);
priv->bl_gpio = NULL;
} else if (priv->bl_gpio) {
dev_info(dev, "使用GPIO进行背光控制\n");
}
return 0;
}
/**
* @brief 获取电源稳压器
* @param dev 设备指针
* @param priv 驱动私有数据
* @return 成功返回0
*
* 从设备树获取电源稳压器配置。
*/
static int st7789_get_regulators(struct device *dev, struct st7789_data *priv)
{
int ret;
/* 获取VDD稳压器(逻辑电压,通常3.3V) */
priv->vdd_reg = devm_regulator_get_optional(dev, "vdd");
if (IS_ERR(priv->vdd_reg)) {
ret = PTR_ERR(priv->vdd_reg);
if (ret != -ENODEV) {
dev_warn(dev, "获取VDD稳压器失败: %d\n", ret);
}
priv->vdd_reg = NULL;
} else {
ret = regulator_enable(priv->vdd_reg);
if (ret) {
dev_warn(dev, "使能VDD稳压器失败: %d\n", ret);
priv->vdd_reg = NULL;
} else {
dev_info(dev, "VDD稳压器已使能\n");
}
}
/* 获取VCC稳压器(模拟电压,通常5V-18V) */
priv->vcc_reg = devm_regulator_get_optional(dev, "vcc");
if (IS_ERR(priv->vcc_reg)) {
ret = PTR_ERR(priv->vcc_reg);
if (ret != -ENODEV) {
dev_warn(dev, "获取VCC稳压器失败: %d\n", ret);
}
priv->vcc_reg = NULL;
} else {
ret = regulator_enable(priv->vcc_reg);
if (ret) {
dev_warn(dev, "使能VCC稳压器失败: %d\n", ret);
priv->vcc_reg = NULL;
} else {
dev_info(dev, "VCC稳压器已使能\n");
}
}
/* 如果没有指定稳压器,可能需要延时以确保电源稳定 */
if (!priv->vdd_reg && !priv->vcc_reg) {
dev_info(dev, "未指定稳压器,假设电源始终开启\n");
msleep(100); /* 等待电源稳定 */
}
return 0;
}
/**
* @brief SPI设备探测函数
* @param spi SPI设备
* @return 成功返回0,失败返回错误码
*
* 当SPI设备匹配时调用,执行驱动的完整初始化。
*/
static int st7789_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
struct st7789_data *priv;
struct fb_info *info;
int ret;
dev_info(dev, "ST7789 SPI TFT显示驱动正在探测\n");
/* 1. 分配私有数据结构 */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(dev, "分配私有数据失败\n");
return -ENOMEM;
}
/* 2. 初始化私有数据结构 */
priv->spi = spi;
priv->dev = dev;
priv->fps = ST7789_DEFAULT_FPS;
mutex_init(&priv->lock);
INIT_DELAYED_WORK(&priv->dirty_work, st7789_dirty_work_handler);
INIT_WORK(&priv->update_work, st7789_update_work_handler);
/* 3. 设置SPI参数 */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi->max_speed_hz = ST7789_SPI_SPEED;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(dev, "设置SPI失败: %d\n", ret);
return ret;
}
/* 4. 解析设备树参数 */
if (dev->of_node) {
ret = st7789_parse_dt(dev, priv);
if (ret)
return ret;
} else {
/* 非设备树平台使用默认值 */
priv->width = ST7789_DEFAULT_WIDTH;
priv->height = ST7789_DEFAULT_HEIGHT;
priv->rotation = 0;
priv->spi_speed = ST7789_SPI_SPEED;
priv->fps = ST7789_DEFAULT_FPS;
}
/* 5. 获取GPIO资源 */
ret = st7789_get_gpios(dev, priv);
if (ret)
return ret;
/* 6. 获取电源稳压器 */
ret = st7789_get_regulators(dev, priv);
if (ret)
return ret;
/* 7. 初始化背光控制 */
ret = st7789_init_backlight(priv);
if (ret && ret != -ENODEV) {
dev_warn(dev, "背光初始化失败: %d\n", ret);
/* 继续执行,背光不是必需的 */
}
/* 8. 分配FrameBuffer信息结构 */
info = framebuffer_alloc(sizeof(*priv), dev);
if (!info) {
dev_err(dev, "分配framebuffer信息失败\n");
ret = -ENOMEM;
goto err_free_regulators;
}
info->par = priv;
info->fbops = &st7789_fb_ops;
info->flags = FBINFO_FLAG_DEFAULT |
FBINFO_HWACCEL_FILLRECT |
FBINFO_HWACCEL_COPYAREA |
FBINFO_HWACCEL_IMAGEBLIT;
priv->fb_info = info;
/* 9. 设置FrameBuffer固定参数 */
strlcpy(info->fix.id, "ST7789", sizeof(info->fix.id));
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.type_aux = 0;
info->fix.visual = FB_VISUAL_TRUECOLOR;
info->fix.xpanstep = 0;
info->fix.ypanstep = 0;
info->fix.ywrapstep = 0;
info->fix.accel = FB_ACCEL_NONE;
info->fix.smem_len = priv->width * priv->height * 2; /* RGB565 */
/* 10. 设置FrameBuffer可变参数 */
info->var.xres = priv->width;
info->var.yres = priv->height;
info->var.xres_virtual = priv->width;
info->var.yres_virtual = priv->height;
info->var.bits_per_pixel = 16; /* RGB565 */
info->var.grayscale = 0;
info->var.nonstd = 0;
info->var.activate = FB_ACTIVATE_NOW;
info->var.height = -1;
info->var.width = -1;
info->var.rotate = priv->rotation;
/* RGB565格式 */
info->var.red.offset = 11;
info->var.red.length = 5;
info->var.green.offset = 5;
info->var.green.length = 6;
info->var.blue.offset = 0;
info->var.blue.length = 5;
info->var.transp.offset = 0;
info->var.transp.length = 0;
/* 11. 分配显存 */
ret = st7789_alloc_framebuffer(priv);
if (ret) {
dev_err(dev, "分配framebuffer内存失败\n");
goto err_free_fb_info;
}
info->fix.smem_start = priv->fb_dma_addr;
info->screen_base = priv->fb_virt_addr;
info->screen_size = priv->fb_size;
info->fix.line_length = priv->width * 2;
/* 12. 分配伪调色板 */
info->pseudo_palette = devm_kzalloc(dev, sizeof(u32) * 16, GFP_KERNEL);
if (!info->pseudo_palette) {
ret = -ENOMEM;
goto err_free_fb_mem;
}
/* 13. 配置旋转参数 */
st7789_set_rotation(priv);
/* 14. 初始化显示硬件 */
ret = st7789_power_on(priv);
if (ret) {
dev_err(dev, "打开显示电源失败\n");
goto err_free_palette;
}
/* 15. 注册FrameBuffer设备 */
ret = register_framebuffer(info);
if (ret < 0) {
dev_err(dev, "注册framebuffer失败: %d\n", ret);
goto err_power_off;
}
/* 16. 初始化deferred IO */
st7789_init_defio(info);
/* 17. 设置SPI设备私有数据 */
spi_set_drvdata(spi, priv);
/* 18. 添加到全局设备列表 */
mutex_lock(&st7789_list_lock);
list_add(&priv->list, &st7789_devices);
mutex_unlock(&st7789_list_lock);
/* 19. 创建设备属性文件 */
ret = sysfs_create_group(&dev->kobj, &st7789_attr_group);
if (ret) {
dev_warn(dev, "创建设备属性失败: %d\n", ret);
}
/* 20. 创建调试接口 */
if (priv->debug) {
st7789_create_debugfs(priv);
}
/* 21. 显示启动信息 */
dev_info(dev, "ST7789显示初始化完成: %dx%d, %d bpp, 旋转=%d\n",
priv->width, priv->height, info->var.bits_per_pixel,
priv->rotation);
dev_info(dev, "Framebuffer注册为 /dev/fb%d\n", info->node);
return 0;
err_power_off:
st7789_power_off(priv);
err_free_palette:
devm_kfree(dev, info->pseudo_palette);
err_free_fb_mem:
st7789_free_framebuffer(priv);
err_free_fb_info:
framebuffer_release(info);
err_free_regulators:
/* 稳压器由devm管理,会自动清理 */
return ret;
}
/**
* @brief SPI设备移除函数
* @param spi SPI设备
* @return 成功返回0
*
* 当设备被移除或驱动卸载时调用,执行清理操作。
*/
static int st7789_remove(struct spi_device *spi)
{
struct st7789_data *priv = spi_get_drvdata(spi);
struct fb_info *info = priv->fb_info;
dev_info(&spi->dev, "移除ST7789显示驱动\n");
/* 1. 从全局设备列表移除 */
mutex_lock(&st7789_list_lock);
list_del(&priv->list);
mutex_unlock(&st7789_list_lock);
/* 2. 移除调试接口 */
if (priv->debug) {
st7789_remove_debugfs(priv);
}
/* 3. 移除设备属性文件 */
sysfs_remove_group(&spi->dev.kobj, &st7789_attr_group);
/* 4. 清理deferred IO */
if (info->fbdefio) {
st7789_cleanup_defio(info);
}
/* 5. 取消所有待处理的工作 */
cancel_delayed_work_sync(&priv->dirty_work);
cancel_work_sync(&priv->update_work);
/* 6. 注销FrameBuffer设备 */
unregister_framebuffer(info);
/* 7. 关闭显示电源 */
st7789_power_off(priv);
/* 8. 释放FrameBuffer信息结构 */
framebuffer_release(info);
/* 9. 释放显存 */
st7789_free_framebuffer(priv);
/* 10. 销毁互斥锁 */
mutex_destroy(&priv->lock);
dev_info(&spi->dev, "ST7789显示驱动已移除\n");
return 0;
}
/**
* @brief 设备树兼容性匹配表
*
* 定义与驱动兼容的设备树节点。
*/
static const struct of_device_id st7789_of_match[] = {
{ .compatible = "sitronix,st7789v", .data = NULL },
{ .compatible = "sitronix,st7789", .data = NULL },
{ .compatible = "sitronix,st7789v2", .data = NULL },
{ .compatible = "sitronix,st7789v3", .data = NULL },
{ .compatible = "fbtft,st7789v", .data = NULL },
{ .compatible = "waveshare,st7789v", .data = NULL },
{ .compatible = "adafruit,st7789", .data = NULL },
{},
};
MODULE_DEVICE_TABLE(of, st7789_of_match);
/**
* @brief SPI设备ID表
*
* 定义与驱动兼容的SPI设备ID。
*/
static const struct spi_device_id st7789_id[] = {
{ "st7789v", 0 },
{ "st7789", 0 },
{ "st7789v2", 0 },
{ "st7789v3", 0 },
{ "sitronix,st7789v", 0 },
{ "fbtft-st7789v", 0 },
{},
};
MODULE_DEVICE_TABLE(spi, st7789_id);
/**
* @brief SPI驱动结构
*
* 定义ST7789 SPI驱动。
*/
static struct spi_driver st7789_driver = {
.driver = {
.name = "st7789",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(st7789_of_match),
.pm = &st7789_pm_ops,
},
.id_table = st7789_id,
.probe = st7789_probe,
.remove = st7789_remove,
.shutdown = st7789_remove, /* 关机时也执行移除操作 */
};
/**
* @brief 显示旋转属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前显示旋转角度。
*/
static ssize_t rotation_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
return scnprintf(buf, PAGE_SIZE, "%d\n", priv->rotation);
}
/**
* @brief 显示旋转属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs设置显示旋转角度。
*/
static ssize_t rotation_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
unsigned int rotation;
int ret;
ret = kstrtouint(buf, 0, &rotation);
if (ret)
return ret;
if (rotation > 270) {
dev_err(dev, "无效的旋转值: %u\n", rotation);
return -EINVAL;
}
mutex_lock(&priv->lock);
if (priv->rotation != rotation) {
priv->rotation = rotation;
st7789_set_rotation(priv);
/* 更新FrameBuffer变量 */
if (priv->fb_info) {
priv->fb_info->var.rotate = rotation;
}
/* 重新配置显示 */
if (priv->powered_on) {
st7789_write_cmd_optimized(priv, ST7789_MADCTL);
st7789_write_data_optimized(priv, priv->madctl_val);
st7789_set_display_offset(priv);
st7789_update_full(priv);
}
dev_info(dev, "旋转角度已更改为 %d 度\n", rotation);
}
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 背光亮度属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前背光亮度。
*/
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
return scnprintf(buf, PAGE_SIZE, "%d\n", priv->brightness);
}
/**
* @brief 背光亮度属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs设置背光亮度。
*/
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
unsigned int brightness;
int ret;
ret = kstrtouint(buf, 0, &brightness);
if (ret)
return ret;
if (brightness > 255) {
dev_err(dev, "无效的亮度值: %u\n", brightness);
return -EINVAL;
}
mutex_lock(&priv->lock);
priv->brightness = brightness;
/* 更新背光状态 */
if (priv->bl_dev) {
priv->bl_dev->props.brightness = brightness;
backlight_update_status(priv->bl_dev);
} else if (priv->bl_gpio) {
gpiod_set_value(priv->bl_gpio, brightness > 0 ? 1 : 0);
}
mutex_unlock(&priv->lock);
dev_dbg(dev, "背光亮度已更改为 %d\n", brightness);
return count;
}
/**
* @brief 帧率属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前帧率。
*/
static ssize_t fps_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
return scnprintf(buf, PAGE_SIZE, "%d\n", priv->fps);
}
/**
* @brief 帧率属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs设置帧率。
*/
static ssize_t fps_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
unsigned int fps;
int ret;
ret = kstrtouint(buf, 0, &fps);
if (ret)
return ret;
if (fps < 1 || fps > 60) {
dev_err(dev, "无效的帧率值: %u (必须在1-60之间)\n", fps);
return -EINVAL;
}
mutex_lock(&priv->lock);
if (priv->fps != fps) {
priv->fps = fps;
/* 更新deferred IO延迟 */
if (priv->fb_info && priv->fb_info->fbdefio) {
priv->fb_info->fbdefio->delay = HZ / fps;
}
dev_info(dev, "帧率已更改为 %d\n", fps);
}
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 电源状态属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取当前电源状态。
*/
static ssize_t power_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
const char *status;
if (priv->powered_on) {
status = priv->sleeping ? "standby" : "on";
} else {
status = "off";
}
return scnprintf(buf, PAGE_SIZE, "%s\n", status);
}
/**
* @brief 电源状态属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数或错误码
*
* 通过sysfs控制电源状态。
*/
static ssize_t power_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
int ret;
if (sysfs_streq(buf, "on")) {
ret = st7789_power_on(priv);
if (ret)
return ret;
} else if (sysfs_streq(buf, "off")) {
st7789_power_off(priv);
} else if (sysfs_streq(buf, "standby")) {
mutex_lock(&priv->lock);
if (priv->powered_on && !priv->sleeping) {
st7789_write_cmd_optimized(priv, ST7789_DISPOFF);
st7789_write_cmd_optimized(priv, ST7789_SLPIN);
msleep(5);
priv->sleeping = true;
}
mutex_unlock(&priv->lock);
} else {
dev_err(dev, "无效的电源状态: %s\n", buf);
return -EINVAL;
}
return count;
}
/**
* @brief 诊断信息属性读取函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输出缓冲区
* @return 写入的字节数
*
* 通过sysfs读取诊断信息。
*/
static ssize_t diagnostic_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
ssize_t count = 0;
mutex_lock(&priv->lock);
count += scnprintf(buf + count, PAGE_SIZE - count,
"ST7789 诊断信息\n");
count += scnprintf(buf + count, PAGE_SIZE - count,
"============================\n");
count += scnprintf(buf + count, PAGE_SIZE - count,
"分辨率: %dx%d\n", priv->width, priv->height);
count += scnprintf(buf + count, PAGE_SIZE - count,
"旋转: %d 度\n", priv->rotation);
count += scnprintf(buf + count, PAGE_SIZE - count,
"帧缓冲: %zu 字节\n", priv->fb_size);
count += scnprintf(buf + count, PAGE_SIZE - count,
"电源: %s\n", priv->powered_on ? "开启" : "关闭");
count += scnprintf(buf + count, PAGE_SIZE - count,
"睡眠: %s\n", priv->sleeping ? "是" : "否");
count += scnprintf(buf + count, PAGE_SIZE - count,
"帧率: %d\n", priv->fps);
count += scnprintf(buf + count, PAGE_SIZE - count,
"亮度: %d\n", priv->brightness);
count += scnprintf(buf + count, PAGE_SIZE - count,
"SPI速度: %d Hz\n", priv->spi_speed);
count += scnprintf(buf + count, PAGE_SIZE - count,
"BGR顺序: %s\n", priv->bgr ? "是" : "否");
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 强制刷新属性写入函数
* @param dev 设备指针
* @param attr 设备属性
* @param buf 输入缓冲区
* @param count 输入字节数
* @return 处理的字节数
*
* 通过sysfs触发强制刷新显示。
*/
static ssize_t refresh_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct spi_device *spi = to_spi_device(dev);
struct st7789_data *priv = spi_get_drvdata(spi);
mutex_lock(&priv->lock);
if (priv->powered_on) {
st7789_update_full(priv);
dev_dbg(dev, "手动刷新已触发\n");
}
mutex_unlock(&priv->lock);
return count;
}
/* 设备属性定义 */
static DEVICE_ATTR_RW(rotation);
static DEVICE_ATTR_RW(brightness);
static DEVICE_ATTR_RW(fps);
static DEVICE_ATTR_RW(power);
static DEVICE_ATTR_RO(diagnostic);
static DEVICE_ATTR_WO(refresh);
/**
* @brief 设备属性组
*
* 定义所有sysfs属性文件。
*/
static struct attribute *st7789_attrs[] = {
&dev_attr_rotation.attr,
&dev_attr_brightness.attr,
&dev_attr_fps.attr,
&dev_attr_power.attr,
&dev_attr_diagnostic.attr,
&dev_attr_refresh.attr,
NULL,
};
static const struct attribute_group st7789_attr_group = {
.attrs = st7789_attrs,
};
#ifdef CONFIG_DEBUG_FS
/**
* @brief 调试信息显示函数
* @param s seq_file结构
* @param data 私有数据
* @return 成功返回0
*
* 在debugfs中显示调试信息。
*/
static int st7789_debug_show(struct seq_file *s, void *data)
{
struct st7789_data *priv = s->private;
mutex_lock(&priv->lock);
seq_printf(s, "ST7789 调试信息\n");
seq_printf(s, "=======================\n");
seq_printf(s, "驱动版本: %s\n", DRIVER_VERSION);
seq_printf(s, "显示尺寸: %dx%d\n", priv->width, priv->height);
seq_printf(s, "旋转: %d\n", priv->rotation);
seq_printf(s, "帧率: %d\n", priv->fps);
seq_printf(s, "亮度: %d\n", priv->brightness);
seq_printf(s, "电源: %s\n", priv->powered_on ? "开启" : "关闭");
seq_printf(s, "睡眠: %s\n", priv->sleeping ? "是" : "否");
seq_printf(s, "SPI速度: %d Hz\n", priv->spi_speed);
seq_printf(s, "帧缓冲: %zu 字节 at %p (DMA: %pad)\n",
priv->fb_size, priv->fb_virt_addr, &priv->fb_dma_addr);
seq_printf(s, "更新计数: %lu\n", priv->update_count);
seq_printf(s, "脏区域计数: %lu\n", priv->dirty_count);
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 打开调试文件
* @param inode inode结构
* @param file 文件结构
* @return 成功返回0
*/
static int st7789_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, st7789_debug_show, inode->i_private);
}
/**
* @brief 寄存器读取函数
* @param s seq_file结构
* @param data 私有数据
* @return 成功返回0
*
* 读取并显示ST7789寄存器值。
*/
static int st7789_registers_show(struct seq_file *s, void *data)
{
struct st7789_data *priv = s->private;
u8 reg;
u8 val[4];
int ret;
mutex_lock(&priv->lock);
if (!priv->powered_on) {
seq_printf(s, "显示已关闭\n");
mutex_unlock(&priv->lock);
return 0;
}
seq_printf(s, "ST7789 寄存器转储\n");
seq_printf(s, "===================\n");
/* 读取显示ID */
ret = st7789_read_data(priv, ST7789_RDDID, val, 3);
if (ret == 3) {
seq_printf(s, "RDDID: 0x%02X 0x%02X 0x%02X\n",
val[0], val[1], val[2]);
}
/* 读取显示状态 */
ret = st7789_read_data(priv, ST7789_RDDST, val, 4);
if (ret == 4) {
seq_printf(s, "RDDST: 0x%02X 0x%02X 0x%02X 0x%02X\n",
val[0], val[1], val[2], val[3]);
}
/* 读取显示亮度 */
ret = st7789_read_data(priv, ST7789_RDDISBV, val, 2);
if (ret == 2) {
seq_printf(s, "RDDISBV: 0x%02X 0x%02X\n", val[0], val[1]);
}
/* 读取控制显示 */
ret = st7789_read_data(priv, ST7789_RDCTRLD, val, 2);
if (ret == 2) {
seq_printf(s, "RDCTRLD: 0x%02X 0x%02X\n", val[0], val[1]);
}
mutex_unlock(&priv->lock);
return 0;
}
/**
* @brief 打开寄存器文件
* @param inode inode结构
* @param file 文件结构
* @return 成功返回0
*/
static int st7789_registers_open(struct inode *inode, struct file *file)
{
return single_open(file, st7789_registers_show, inode->i_private);
}
/**
* @brief 写入命令到调试文件
* @param file 文件结构
* @param buf 用户缓冲区
* @param count 字节数
* @param ppos 文件位置指针
* @return 写入的字节数或错误码
*
* 允许通过debugfs发送命令到ST7789。
*/
static ssize_t st7789_command_write(struct file *file,
const char __user *buf,
size_t count, loff_t *ppos)
{
struct st7789_data *priv = file->private_data;
char cmd_buf[16];
u8 cmd;
u8 data;
int ret;
if (count < 3 || count > sizeof(cmd_buf))
return -EINVAL;
if (copy_from_user(cmd_buf, buf, count))
return -EFAULT;
cmd_buf[count] = '\0';
/* 解析命令和数据 */
ret = sscanf(cmd_buf, "%hhx %hhx", &cmd, &data);
if (ret != 2) {
dev_err(priv->dev, "无效的命令格式: %s\n", cmd_buf);
return -EINVAL;
}
mutex_lock(&priv->lock);
if (!priv->powered_on) {
mutex_unlock(&priv->lock);
return -ENODEV;
}
/* 发送命令和数据 */
st7789_write_cmd_optimized(priv, cmd);
st7789_write_data_optimized(priv, data);
dev_info(priv->dev, "调试命令: 0x%02X 0x%02X\n", cmd, data);
mutex_unlock(&priv->lock);
return count;
}
/**
* @brief 打开命令文件
* @param inode inode结构
* @param file 文件结构
* @return 成功返回0
*/
static int st7789_command_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
/* 调试文件操作 */
static const struct file_operations st7789_debug_fops = {
.owner = THIS_MODULE,
.open = st7789_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations st7789_registers_fops = {
.owner = THIS_MODULE,
.open = st7789_registers_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations st7789_command_fops = {
.owner = THIS_MODULE,
.open = st7789_command_open,
.write = st7789_command_write,
.llseek = noop_llseek,
};
/**
* @brief 创建调试文件系统接口
* @param priv 驱动私有数据
*
* 在debugfs中创建设备特定的调试文件。
*/
static void st7789_create_debugfs(struct st7789_data *priv)
{
struct dentry *dir;
if (!st7789_debugfs_root) {
st7789_debugfs_root = debugfs_create_dir("st7789", NULL);
if (!st7789_debugfs_root) {
dev_warn(priv->dev, "创建debugfs根目录失败\n");
return;
}
}
dir = debugfs_create_dir(dev_name(priv->dev), st7789_debugfs_root);
if (!dir) {
dev_warn(priv->dev, "创建设备debugfs目录失败\n");
return;
}
debugfs_create_file("info", 0444, dir, priv, &st7789_debug_fops);
debugfs_create_file("registers", 0444, dir, priv, &st7789_registers_fops);
debugfs_create_file("command", 0200, dir, priv, &st7789_command_fops);
debugfs_create_u32("width", 0444, dir, &priv->width);
debugfs_create_u32("height", 0444, dir, &priv->height);
debugfs_create_u32("rotation", 0644, dir, &priv->rotation);
debugfs_create_u32("fps", 0644, dir, &priv->fps);
debugfs_create_bool("powered", 0444, dir, &priv->powered_on);
debugfs_create_bool("sleeping", 0444, dir, &priv->sleeping);
priv->debug_dir = dir;
dev_info(priv->dev, "Debugfs接口已创建\n");
}
/**
* @brief 移除调试文件系统接口
* @param priv 驱动私有数据
*
* 清理debugfs中的调试文件。
*/
static void st7789_remove_debugfs(struct st7789_data *priv)
{
if (priv->debug_dir) {
debugfs_remove_recursive(priv->debug_dir);
priv->debug_dir = NULL;
}
}
#else /* CONFIG_DEBUG_FS */
static void st7789_create_debugfs(struct st7789_data *priv) {}
static void st7789_remove_debugfs(struct st7789_data *priv) {}
#endif /* CONFIG_DEBUG_FS */
/**
* @brief 驱动模块初始化函数
* @return 成功返回0,失败返回错误码
*
* 注册SPI驱动到内核。
*/
static int __init st7789_init(void)
{
int ret;
pr_info("ST7789 SPI TFT显示驱动正在初始化\n");
/* 创建调试文件系统根目录 */
#ifdef CONFIG_DEBUG_FS
st7789_debugfs_root = debugfs_create_dir("st7789", NULL);
if (!st7789_debugfs_root)
pr_warn("创建debugfs根目录失败\n");
#endif
/* 注册SPI驱动 */
ret = spi_register_driver(&st7789_driver);
if (ret < 0) {
pr_err("注册ST7789驱动失败: %d\n", ret);
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(st7789_debugfs_root);
#endif
return ret;
}
pr_info("ST7789 SPI TFT显示驱动初始化完成\n");
return 0;
}
/**
* @brief 驱动模块退出函数
*
* 从内核注销SPI驱动。
*/
static void __exit st7789_exit(void)
{
pr_info("ST7789 SPI TFT显示驱动正在退出\n");
/* 注销SPI驱动 */
spi_unregister_driver(&st7789_driver);
/* 清理调试文件系统 */
#ifdef CONFIG_DEBUG_FS
debugfs_remove_recursive(st7789_debugfs_root);
st7789_debugfs_root = NULL;
#endif
pr_info("ST7789 SPI TFT显示驱动已退出\n");
}
module_init(st7789_init);
module_exit(st7789_exit);
十、构建和集成说明
10.1 内核构建配置
# drivers/video/fbdev/st7789/Makefile 完整版本 # ST7789 SPI TFT FrameBuffer driver # # 主目标文件 obj-$(CONFIG_FB_ST7789) += st7789_fb.o # 分解目标文件 st7789_fb-objs := \ st7789_main.o \ st7789_logo.o \ st7789_params.o # 调试支持 ifeq ($(CONFIG_FB_ST7789_DEBUG),y) EXTRA_CFLAGS += -DDEBUG -DST7789_DEBUG endif # DMA支持 ifeq ($(CONFIG_FB_ST7789_DMA),y) EXTRA_CFLAGS += -DST7789_USE_DMA endif # 背光控制支持 ifeq ($(CONFIG_FB_ST7789_BACKLIGHT),y) EXTRA_CFLAGS += -DST7789_BACKLIGHT endif # Logo支持 ifeq ($(CONFIG_FB_ST7789_LOGO),y) EXTRA_CFLAGS += -DST7789_LOGO endif # 旋转支持 ifeq ($(CONFIG_FB_ST7789_ROTATION),y) EXTRA_CFLAGS += -DST7789_ROTATION endif # GPIO复位支持 ifeq ($(CONFIG_FB_ST7789_GPIO_RESET),y) EXTRA_CFLAGS += -DST7789_GPIO_RESET endif # 包含配置的SPI速度 ifdef CONFIG_FB_ST7789_SPI_SPEED EXTRA_CFLAGS += -DST7789_SPI_SPEED=$(CONFIG_FB_ST7789_SPI_SPEED) else EXTRA_CFLAGS += -DST7789_SPI_SPEED=40000000 endif # 包含配置的帧率 ifdef CONFIG_FB_ST7789_FPS EXTRA_CFLAGS += -DST7789_FPS=$(CONFIG_FB_ST7789_FPS) else EXTRA_CFLAGS += -DST7789_FPS=30 endif # 通用编译标志 EXTRA_CFLAGS += -Wall -Werror -Wextra EXTRA_CFLAGS += -I$(src)/../ # 禁用特定警告 EXTRA_CFLAGS += -Wno-unused-parameter EXTRA_CFLAGS += -Wno-missing-field-initializers
10.2 设备树配置示例
// st7789-example.dts - ST7789设备树配置示例
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&spi0>;
__overlay__ {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
st7789v: display@0 {
compatible = "sitronix,st7789v";
reg = <0>;
spi-max-frequency = <40000000>;
/* 显示尺寸 */
width = <320>;
height = <240>;
/* GPIO引脚 */
reset-gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
dc-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
led-gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;
/* 电源管理 */
vdd-supply = <&display_vdd>;
vcc-supply = <&display_vcc>;
/* 显示设置 */
rotate = <90>;
bgr;
fps = <30>;
debug;
/* 背光设置 */
brightness-levels = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
96 97 98 99 100 101 102 103 104 105 106 107 108
109 110 111 112 113 114 115 116 117 118 119 120
121 122 123 124 125 126 127 128 129 130 131 132
133 134 135 136 137 138 139 140 141 142 143 144
145 146 147 148 149 150 151 152 153 154 155 156
157 158 159 160 161 162 163 164 165 166 167 168
169 170 171 172 173 174 175 176 177 178 179 180
181 182 183 184 185 186 187 188 189 190 191 192
193 194 195 196 197 198 199 200 201 202 203 204
205 206 207 208 209 210 211 212 213 214 215 216
217 218 219 220 221 222 223 224 225 226 227 228
229 230 231 232 233 234 235 236 237 238 239 240
241 242 243 244 245 246 247 248 249 250 251 252
253 254 255>;
default-brightness-level = <255>;
};
};
};
fragment@1 {
target = <&gpio>;
__overlay__ {
display_pins: display_pins {
brcm,pins = <17 25 18>;
brcm,function = <1 1 1>; /* 输出 */
brcm,pull = <0 0 0>; /* 不上拉/下拉 */
};
};
};
fragment@2 {
target-path = "/";
__overlay__ {
display_vdd: regulator-vdd {
compatible = "regulator-fixed";
regulator-name = "display-vdd";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-always-on;
};
display_vcc: regulator-vcc {
compatible = "regulator-fixed";
regulator-name = "display-vcc";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
regulator-always-on;
};
};
};
};
使用指令调试:
# 1. 查看设备信息 cat /sys/kernel/debug/st7789/spi0.0-st7789/info # 2. 读取寄存器状态 cat /sys/kernel/debug/st7789/spi0.0-st7789/registers # 3. 发送命令(16进制) echo "36 00" > /sys/kernel/debug/st7789/spi0.0-st7789/command # 设置MADCTL为0x00 # 4. 修改参数 echo 90 > /sys/kernel/debug/st7789/spi0.0-st7789/rotation echo 30 > /sys/kernel/debug/st7789/spi0.0-st7789/fps # 5. 查看状态 cat /sys/kernel/debug/st7789/spi0.0-st7789/powered_on cat /sys/kernel/debug/st7789/spi0.0-st7789/update_count
这是驱动程序的第四部分,涵盖了Logo支持、头文件定义、模块参数和构建配置。整个ST7789 FrameBuffer驱动现在已经完成,包括:
-
完整的FrameBuffer接口:支持标准的Linux FrameBuffer API
-
硬件加速:优化了矩形填充、区域复制和图像绘制
-
电源管理:支持休眠、唤醒和运行时电源管理
-
设备树支持:完整的设备树绑定和解析
-
调试接口:sysfs属性和debugfs调试支持
-
Logo显示:启动Logo和自定义Logo支持
-
旋转支持:支持0°、90°、180°、270°旋转
-
背光控制:支持GPIO、PWM和背光子系统的背光控制
-
模块参数:支持模块加载时配置参数
驱动已经可以集成到Linux内核中,为ST7789 SPI TFT显示器提供完整的FrameBuffer支持。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)