Linux DMA(二)

2. 主驱动程序 (spi_oled_dma.c)

/*
 * spi_oled_dma.c - SPI OLED DMA教学驱动
 *
 * 这个驱动的主要目的是通过一个实际的SPI OLED显示驱动,
 * 来学习和理解Linux内核中DMA的各种概念和用法。
 *
 * 支持的DMA模式:
 *   1. Coherent DMA  - 一致性DMA,最简单
 *   2. Streaming DMA - 流式DMA,最灵活
 *   3. Scatter-Gather - 分散/聚集DMA,最高效
 *   4. PIO          - 无DMA对比基准
 *
 * 作者: 教学示例
 * 许可: GPL v2
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/scatterlist.h>
#include <linux/workqueue.h>
#include <linux/of.h>
#include <linux/of_device.h>

#include "spi_oled_dma.h"

/* 模块参数 - 选择DMA模式 */
static int dma_mode = DMA_MODE_COHERENT;
module_param(dma_mode, int, 0644);
MODULE_PARM_DESC(dma_mode, "DMA mode: 0=coherent, 1=streaming, 2=scatter-gather, 3=PIO");

/* 刷新率(ms) */
static int refresh_interval = 33;  /* ~30fps */
module_param(refresh_interval, int, 0644);
MODULE_PARM_DESC(refresh_interval, "Display refresh interval in ms (default: 33)");

/* ============================================================
 * 第1部分: SPI基础通信函数
 * ============================================================
 * 
 * SSD1306有两种数据类型:
 *   - 命令 (DC=0): 控制显示器行为
 *   - 数据 (DC=1): 显示内容(像素数据)
 *
 * DC引脚用于区分命令和数据
 */

/**
 * oled_write_cmd - 向OLED发送单个命令
 * @oled: OLED设备结构
 * @cmd: 命令字节
 *
 * DC引脚拉低表示发送的是命令
 * 这里使用普通SPI传输(非DMA),因为命令通常很短
 */
int oled_write_cmd(struct spi_oled_device *oled, u8 cmd)
{
    int ret;

    /* DC = 0 表示命令模式 */
    gpiod_set_value_cansleep(oled->dc_gpio, 0);

    /* 
     * spi_write() 是最简单的SPI发送函数
     * 内部可能使用PIO或DMA,取决于SPI控制器驱动的实现
     * 对于单字节传输,通常使用PIO
     */
    ret = spi_write(oled->spi, &cmd, 1);
    if (ret)
        dev_err(oled->dev, "Failed to write command 0x%02x: %d\n", cmd, ret);

    return ret;
}

/**
 * oled_write_cmd_seq - 发送命令序列
 * @oled: OLED设备结构
 * @cmds: 命令数组
 * @len: 命令数量
 */
int oled_write_cmd_seq(struct spi_oled_device *oled, const u8 *cmds, size_t len)
{
    int ret;

    gpiod_set_value_cansleep(oled->dc_gpio, 0);
    ret = spi_write(oled->spi, cmds, len);
    if (ret)
        dev_err(oled->dev, "Failed to write command sequence: %d\n", ret);

    return ret;
}

/**
 * oled_write_data_pio - 使用PIO方式发送显示数据(无DMA对比)
 * @oled: OLED设备结构
 * @data: 数据缓冲区
 * @len: 数据长度
 *
 * 📚 PIO (Programmed I/O) 说明:
 *   CPU直接参与每个字节的传输:
 *   for each byte:
 *     CPU读取内存 -> CPU写入SPI数据寄存器 -> 等待传输完成
 *   
 *   问题:
 *   - CPU全程占用,无法执行其他任务
 *   - 对于大量数据传输效率低下
 *   - 每次传输都有CPU中断/轮询开销
 */
static int oled_write_data_pio(struct spi_oled_device *oled, const u8 *data, size_t len)
{
    int ret;
    ktime_t start, end;

    start = ktime_get();

    /* DC = 1 表示数据模式 */
    gpiod_set_value_cansleep(oled->dc_gpio, 1);

    /* 
     * spi_write 内部流程:
     * 1. 构建 spi_message 和 spi_transfer
     * 2. 调用 spi_sync() 同步发送
     * 3. SPI控制器驱动决定使用PIO还是DMA
     *    - 对于小数据量,通常使用PIO
     *    - spi_transfer没有设置tx_dma,控制器需要自己映射
     */
    ret = spi_write(oled->spi, data, len);

    end = ktime_get();

    /* 更新统计 */
    spin_lock(&oled->stats_lock);
    oled->stats.pio_transfers++;
    oled->stats.last_transfer_start = start;
    oled->stats.last_transfer_end = end;
    spin_unlock(&oled->stats_lock);

    if (ret)
        dev_err(oled->dev, "PIO write failed: %d\n", ret);

    return ret;
}

/* ============================================================
 * 第2部分: DMA初始化和清理
 * ============================================================
 *
 * 📚 DMA初始化流程:
 *   1. 设置DMA掩码 - 告诉内核设备能访问多大的地址空间
 *   2. 分配DMA缓冲区 - 根据需要选择一致性或流式
 *   3. 获取DMA通道(可选)- 如果需要直接控制DMA引擎
 */

/**
 * oled_dma_init_coherent - 初始化一致性DMA缓冲区
 *
 * 📚 一致性DMA (Coherent DMA) 详解:
 *
 * 分配函数: dma_alloc_coherent(dev, size, &dma_handle, gfp)
 *   - dev: 设备结构(用于确定DMA地址空间)
 *   - size: 缓冲区大小
 *   - dma_handle: 输出参数,返回DMA总线地址
 *   - gfp: 内存分配标志
 *
 * 返回值: CPU可用的虚拟地址
 *
 * 特点:
 *   ┌─────────┐     ┌──────────────┐     ┌──────────┐
 *   │  CPU    │────>│  一致性内存   │<────│ DMA控制器│
 *   │(虚拟地址)│     │(非缓存/写透) │     │(DMA地址) │
 *   └─────────┘     └──────────────┘     └──────────┘
 *
 *   - 内存通常映射为non-cacheable或write-through
 *   - 硬件保证CPU写入立即对设备可见
 *   - 设备写入立即对CPU可见
 *   - 无需手动同步操作
 *   - 适合生命周期长、频繁访问的缓冲区
 *
 * 缺点:
 *   - 因为non-cacheable,CPU访问速度较慢
 *   - 分配的内存是物理连续的,大块分配可能失败
 */
static int oled_dma_init_coherent(struct spi_oled_device *oled)
{
    struct device *dev = oled->dev;

    dev_info(dev, "📚 初始化一致性DMA缓冲区\n");

    /*
     * 📚 第一步: 设置DMA掩码
     *
     * DMA掩码告诉内核该设备能够寻址的内存范围。
     * 例如:
     *   32位掩码 (0xFFFFFFFF) = 设备可以访问4GB以内的地址
     *   24位掩码 (0x00FFFFFF) = 设备只能访问16MB以内的地址(ISA设备)
     *
     * 内核根据这个掩码来分配DMA缓冲区,确保地址在设备可达范围内。
     *
     * dma_set_mask_and_coherent() 同时设置:
     *   - streaming DMA mask (dma_mask)
     *   - coherent DMA mask (coherent_dma_mask)
     */
    if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        dev_err(dev, "Failed to set DMA mask\n");
        return -EIO;
    }
    dev_info(dev, "  DMA掩码设置为32位 (可访问4GB地址空间)\n");

    /*
     * 📚 第二步: 分配一致性DMA缓冲区
     *
     * dma_alloc_coherent() 做了什么:
     *   1. 分配物理连续的内存页
     *   2. 将内存映射为non-cacheable(在大多数架构上)
     *   3. 如果存在IOMMU,设置IOMMU映射
     *   4. 返回CPU虚拟地址和DMA总线地址
     *
     * 注意: 返回的两个地址指向同一块物理内存,但"视角"不同
     *   - 虚拟地址: CPU通过MMU翻译后访问
     *   - DMA地址: DMA控制器通过总线/IOMMU访问
     */
    oled->coherent_buf.size = OLED_BUF_SIZE;
    oled->coherent_buf.vaddr = dma_alloc_coherent(
        dev,
        oled->coherent_buf.size,
        &oled->coherent_buf.dma_addr,   /* 输出: DMA地址 */
        GFP_KERNEL                       /* 可以睡眠的分配 */
    );

    if (!oled->coherent_buf.vaddr) {
        dev_err(dev, "Failed to allocate coherent DMA buffer\n");
        return -ENOMEM;
    }
    oled->coherent_buf.is_coherent = true;

    dev_info(dev, "  一致性DMA缓冲区已分配:\n");
    dev_info(dev, "    CPU虚拟地址: %px\n", oled->coherent_buf.vaddr);
    dev_info(dev, "    DMA总线地址: %pad\n", &oled->coherent_buf.dma_addr);
    dev_info(dev, "    大小: %zu bytes\n", oled->coherent_buf.size);

    /* 清零缓冲区 */
    memset(oled->coherent_buf.vaddr, 0, oled->coherent_buf.size);

    return 0;
}

/**
 * oled_dma_init_streaming - 初始化流式DMA
 *
 * 📚 流式DMA (Streaming DMA) 详解:
 *
 * 与一致性DMA不同,流式DMA使用普通内存(可能被CPU缓存)。
 * 需要在CPU和设备之间传递"所有权"。
 *
 * 工作流程(CPU到设备方向):
 *   1. CPU写入数据到缓冲区(数据可能在CPU Cache中)
 *   2. dma_map_single() - 映射缓冲区,刷新Cache到内存
 *   3. 启动DMA传输
 *   4. 等待DMA完成
 *   5. dma_unmap_single() - 取消映射
 *
 * 所有权模型:
 *   ┌─────┐  map()   ┌───────┐  完成后  ┌─────┐
 *   │ CPU │ -------> │ 设备  │ -------> │ CPU │
 *   │拥有  │  (同步)   │ 拥有  │ (unmap)  │拥有  │
 *   └─────┘          └───────┘          └─────┘
 *
 * DMA方向:
 *   DMA_TO_DEVICE     - CPU -> 设备 (如发送数据到OLED)
 *   DMA_FROM_DEVICE   - 设备 -> CPU (如接收传感器数据)
 *   DMA_BIDIRECTIONAL - 双向 (性能最差,因为要双向同步)
 */
static int oled_dma_init_streaming(struct spi_oled_device *oled)
{
    struct device *dev = oled->dev;

    dev_info(dev, "📚 初始化流式DMA缓冲区\n");

    /* 设置DMA掩码 */
    if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        dev_err(dev, "Failed to set DMA mask\n");
        return -EIO;
    }

    /*
     * 📚 流式DMA使用普通kmalloc分配内存
     *
     * 注意: 不是任何内存都可以做DMA!
     * 要求:
     *   1. 物理连续(kmalloc满足,vmalloc不满足)
     *   2. 不在栈上(栈可能不是DMA-safe的)
     *   3. 对齐到cache line(避免cache line共享问题)
     *
     * kmalloc + GFP_DMA: 分配在DMA区域(低地址)
     * kmalloc + GFP_KERNEL: 分配在普通区域(通常也OK,因为DMA掩码是32位)
     *
     * 使用 devm_kmalloc 可以自动在设备移除时释放
     */
    oled->streaming_buf.size = OLED_BUF_SIZE;
    oled->streaming_buf.vaddr = devm_kmalloc(
        dev,
        oled->streaming_buf.size,
        GFP_KERNEL | __GFP_DMA   /* 确保在DMA可达区域 */
    );

    if (!oled->streaming_buf.vaddr) {
        dev_err(dev, "Failed to allocate streaming DMA buffer\n");
        return -ENOMEM;
    }
    oled->streaming_buf.is_coherent = false;
    oled->streaming_buf.dma_addr = 0;  /* 尚未映射 */

    dev_info(dev, "  流式DMA缓冲区已分配:\n");
    dev_info(dev, "    CPU虚拟地址: %px\n", oled->streaming_buf.vaddr);
    dev_info(dev, "    大小: %zu bytes\n", oled->streaming_buf.size);
    dev_info(dev, "    注意: DMA地址将在每次传输时动态映射\n");

    memset(oled->streaming_buf.vaddr, 0, oled->streaming_buf.size);

    return 0;
}

/**
 * oled_dma_init_sg - 初始化Scatter-Gather DMA
 *
 * 📚 Scatter-Gather DMA 详解:
 *
 * 问题场景: 数据分布在多个不连续的内存块中
 *   例如: framebuffer在虚拟地址上连续,但物理页可能不连续
 *   或者: 需要将多个小缓冲区合并在一次DMA操作中传输
 *
 * 传统DMA: 只能传输一块连续的内存
 *   ┌──────────────────┐
 *   │   连续内存块      │ ──DMA──> 设备
 *   └──────────────────┘
 *
 * Scatter-Gather DMA: 可以同时传输多个不连续的内存块
 *   ┌──────┐
 *   │ 块1  │──┐
 *   └──────┘  │
 *   ┌──────┐  ├──DMA──> 设备 (硬件自动按顺序传输)
 *   │ 块2  │──┤
 *   └──────┘  │
 *   ┌──────┐  │
 *   │ 块3  │──┘
 *   └──────┘
 *
 * scatterlist结构: 描述一个内存块
 *   - page:     内存页
 *   - offset:   页内偏移
 *   - length:   数据长度
 *   - dma_addr: DMA映射后的总线地址
 *
 * sg_table: 管理一组scatterlist
 *   - sgl:   scatterlist数组的首地址
 *   - nents: 原始条目数
 *   - orig_nents: 初始分配的条目数
 *
 * 优点:
 *   1. 减少内存碎片要求(不需要大块连续物理内存)
 *   2. 可以一次传输提交多个缓冲区
 *   3. DMA控制器硬件支持链式传输,无需CPU介入
 *
 * 在OLED场景中的应用:
 *   将1024字节的显示数据分成8个128字节的页(page)
 *   每个页对应OLED的一行(8像素高)
 *   可以实现局部刷新(只更新变化的页)
 */
static int oled_dma_init_sg(struct spi_oled_device *oled)
{
    struct device *dev = oled->dev;
    struct scatterlist *sg;
    int i, ret;

    dev_info(dev, "📚 初始化Scatter-Gather DMA\n");

    /* 设置DMA掩码 */
    if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) {
        dev_err(dev, "Failed to set DMA mask\n");
        return -EIO;
    }

    /*
     * 📚 我们将显示数据分成OLED_PAGES(8)个块
     * 每个块对应OLED的一页 = 128字节
     * 这样可以实现按页刷新
     */
    oled->sg_nents = OLED_PAGES;

    /*
     * 📚 sg_alloc_table() - 分配scatter-gather表
     *
     * 内部分配一个scatterlist数组
     * 如果条目数超过SG_MAX_SINGLE_ALLOC(通常128),
     * 会分配链式的scatterlist
     */
    ret = sg_alloc_table(&oled->sg_table, oled->sg_nents, GFP_KERNEL);
    if (ret) {
        dev_err(dev, "Failed to allocate SG table: %d\n", ret);
        return ret;
    }

    /*
     * 📚 为每个SG条目分配独立的内存块
     *
     * 故意分开分配,模拟"不连续内存"的场景
     * 实际中这些内存块在物理上可能不连续
     */
    i = 0;
    for_each_sg(oled->sg_table.sgl, sg, oled->sg_nents, i) {
        void *buf;
        size_t chunk_size = OLED_WIDTH; /* 每页128字节 */

        /*
         * 用kmalloc分配每个小块
         * 这些块在物理上很可能不连续
         */
        buf = kmalloc(chunk_size, GFP_KERNEL | __GFP_DMA);
        if (!buf) {
            dev_err(dev, "Failed to allocate SG buffer %d\n", i);
            goto err_free_bufs;
        }

        memset(buf, 0, chunk_size);

        /*
         * 📚 sg_set_buf() - 设置scatterlist条目
         *
         * 将一个虚拟地址缓冲区关联到scatterlist条目
         * 内部调用 virt_to_page() 获取页结构
         * 并计算页内偏移
         *
         * sg_set_buf(sg, buf, len) 等价于:
         *   sg_set_page(sg, virt_to_page(buf), len, offset_in_page(buf));
         */
        sg_set_buf(sg, buf, chunk_size);

        dev_info(dev, "  SG[%d]: vaddr=%px, size=%zu\n", i, buf, chunk_size);
    }

    dev_info(dev, "  Scatter-Gather表已创建: %d个条目\n", oled->sg_nents);
    dev_info(dev, "  总大小: %d bytes (每块 %d bytes)\n",
             OLED_BUF_SIZE, OLED_WIDTH);

    return 0;

err_free_bufs:
    /* 释放已分配的缓冲区 */
    {
        struct scatterlist *sg_iter;
        int j;

        for_each_sg(oled->sg_table.sgl, sg_iter, i, j) {
            kfree(sg_virt(sg_iter));
        }
        sg_free_table(&oled->sg_table);
    }
    return -ENOMEM;
}

/**
 * oled_dma_init - DMA总初始化函数
 *
 * 根据选择的模式初始化对应的DMA资源
 */
int oled_dma_init(struct spi_oled_device *oled)
{
    int ret;

    dev_info(oled->dev, "====== DMA初始化开始 ======\n");
    dev_info(oled->dev, "选择的DMA模式: %d\n", oled->dma_mode);

    /* 初始化完成量,用于等待DMA传输完成 */
    init_completion(&oled->dma_completion);

    switch (oled->dma_mode) {
    case DMA_MODE_COHERENT:
        dev_info(oled->dev, "模式: 一致性DMA (Coherent)\n");
        ret = oled_dma_init_coherent(oled);
        break;

    case DMA_MODE_STREAMING:
        dev_info(oled->dev, "模式: 流式DMA (Streaming)\n");
        ret = oled_dma_init_streaming(oled);
        break;

    case DMA_MODE_SG:
        dev_info(oled->dev, "模式: Scatter-Gather DMA\n");
        ret = oled_dma_init_sg(oled);
        break;

    case DMA_MODE_PIO:
        dev_info(oled->dev, "模式: PIO (无DMA,CPU直接传输)\n");
        /* PIO不需要DMA初始化,只需要普通缓冲区 */
        oled->streaming_buf.size = OLED_BUF_SIZE;
        oled->streaming_buf.vaddr = devm_kmalloc(oled->dev,
                                                  OLED_BUF_SIZE,
                                                  GFP_KERNEL);
        if (!oled->streaming_buf.vaddr)
            return -ENOMEM;
        ret = 0;
        break;

    default:
        dev_err(oled->dev, "Unknown DMA mode: %d\n", oled->dma_mode);
        return -EINVAL;
    }

    if (ret == 0) {
        dev_info(oled->dev, "====== DMA初始化完成 ======\n");
    }

    return ret;
}

/**
 * oled_dma_cleanup - 清理DMA资源
 *
 * 📚 DMA资源释放的重要性:
 *   - 必须在设备移除或驱动卸载时释放所有DMA资源
 *   - 释放顺序通常与分配顺序相反
 *   - 未释放的DMA映射会导致内存泄漏和IOMMU资源泄漏
 *   - 内核的DMA debug功能(CONFIG_DMA_API_DEBUG)可以检测泄漏
 */
void oled_dma_cleanup(struct spi_oled_device *oled)
{
    struct device *dev = oled->dev;

    dev_info(dev, "清理DMA资源...\n");

    switch (oled->dma_mode) {
    case DMA_MODE_COHERENT:
        /*
         * 📚 dma_free_coherent() - 释放一致性DMA缓冲区
         *
         * 必须传入与分配时相同的:
         *   - 设备指针
         *   - 大小
         *   - 虚拟地址
         *   - DMA地址
         *
         * 内部操作:
         *   1. 解除IOMMU映射(如果有)
         *   2. 释放物理页
         *   3. 解除虚拟地址映射
         */
        if (oled->coherent_buf.vaddr) {
            dma_free_coherent(dev,
                             oled->coherent_buf.size,
                             oled->coherent_buf.vaddr,
                             oled->coherent_buf.dma_addr);
            oled->coherent_buf.vaddr = NULL;
            dev_info(dev, "  一致性DMA缓冲区已释放\n");
        }
        break;

    case DMA_MODE_STREAMING:
        /*
         * 📚 流式DMA缓冲区清理
         *
         * 如果当前有未完成的DMA映射,需要先unmap
         * 这里使用devm_kmalloc分配的内存会自动释放
         * 但DMA映射必须手动取消
         */
        if (oled->streaming_buf.dma_addr) {
            dma_unmap_single(dev,
                           oled->streaming_buf.dma_addr,
                           oled->streaming_buf.size,
                           DMA_TO_DEVICE);
            oled->streaming_buf.dma_addr = 0;
            dev_info(dev, "  流式DMA映射已取消\n");
        }
        /* devm_kmalloc的内存会自动释放 */
        break;

    case DMA_MODE_SG:
        /*
         * 📚 Scatter-Gather DMA清理
         *
         * 需要:
         *   1. 取消SG表的DMA映射 (dma_unmap_sg)
         *   2. 释放每个SG条目对应的缓冲区
         *   3. 释放SG表本身
         */
        if (oled->sg_table.sgl) {
            struct scatterlist *sg;
            int i;

            /* 如果SG表已被映射,先取消映射 */
            /* dma_unmap_sg() 在需要时调用 */

            /* 释放每个块的内存 */
            for_each_sg(oled->sg_table.sgl, sg, oled->sg_nents, i) {
                void *buf = sg_virt(sg);
                if (buf)
                    kfree(buf);
            }

            /* 释放SG表 */
            sg_free_table(&oled->sg_table);
            dev_info(dev, "  SG表和缓冲区已释放\n");
        }
        break;

    case DMA_MODE_PIO:
        /* devm_kmalloc的内存会自动释放 */
        dev_info(dev, "  PIO模式无DMA资源需要释放\n");
        break;
    }

    /* 释放DMA通道 */
    if (oled->dma_chan_tx) {
        dma_release_channel(oled->dma_chan_tx);
        oled->dma_chan_tx = NULL;
        dev_info(dev, "  DMA通道已释放\n");
    }
}

/* ============================================================
 * 第3部分: DMA数据传输
 * ============================================================
 *
 * 这是驱动最核心的部分
 * 演示了三种DMA传输方式的具体实现
 */

/**
 * oled_dma_transfer_coherent - 使用一致性DMA传输数据
 *
 * 📚 一致性DMA传输流程:
 *
 *   ┌─────────────────────────────────────────────────────┐
 *   │ 1. CPU将数据写入一致性缓冲区 (memcpy)              │
 *   │    - 因为是一致性映射,写入立即对DMA控制器可见       │
 *   │    - 不需要任何同步操作!                           │
 *   ├─────────────────────────────────────────────────────┤
 *   │ 2. 配置SPI传输,指定DMA地址                         │
 *   │    - spi_transfer.tx_buf = 虚拟地址                 │
 *   │    - spi_transfer.tx_dma = DMA地址                  │
 *   │    - spi_transfer.is_dma_mapped = 1 (告诉SPI框架    │
 *   │      缓冲区已经映射好了,不要再映射)                │
 *   ├─────────────────────────────────────────────────────┤
 *   │ 3. 提交SPI传输                                      │
 *   │    - SPI控制器驱动直接使用我们提供的DMA地址          │
 *   │    - DMA控制器从缓冲区读取数据,发送到SPI            │
 *   │    - CPU在此期间可以做其他事情                       │
 *   ├─────────────────────────────────────────────────────┤
 *   │ 4. 等待传输完成                                      │
 *   │    - DMA控制器传输完成后产生中断                     │
 *   │    - SPI驱动收到中断后通知我们                       │
 *   └─────────────────────────────────────────────────────┘
 *
 * is_dma_mapped 标志的作用:
 *   当设为1时,告诉SPI控制器驱动:
 *   "我已经做好了DMA映射,直接用tx_dma地址就行"
 *   
 *   如果设为0(默认),SPI控制器驱动会:
 *   自己调用dma_map_single()来映射tx_buf
 */
int oled_dma_transfer_coherent(struct spi_oled_device *oled)
{
    struct spi_transfer xfer = {0};
    struct spi_message msg;
    ktime_t start, end;
    s64 elapsed_ns;
    int ret;

    dev_dbg(oled->dev, "📚 一致性DMA传输开始\n");

    mutex_lock(&oled->buf_lock);
    start = ktime_get();

    /*
     * 📚 步骤1: 数据已经在一致性缓冲区中
     * oled_convert_fb_to_oled() 已经将数据写入 coherent_buf.vaddr
     * 因为是一致性映射,不需要任何sync操作
     */

    /* DC = 1 表示数据模式 */
    gpiod_set_value_cansleep(oled->dc_gpio, 1);

    /*
     * 📚 步骤2: 配置SPI传输描述符
     */
    spi_message_init(&msg);

    xfer.tx_buf = oled->coherent_buf.vaddr;     /* CPU使用的虚拟地址 */
    xfer.tx_dma = oled->coherent_buf.dma_addr;  /* DMA控制器使用的总线地址 */
    xfer.len = oled->coherent_buf.size;
    xfer.bits_per_word = 8;
    xfer.speed_hz = oled->spi->max_speed_hz;

    /*
     * 📚 关键标志: is_dma_mapped
     *
     * = true: 我们自己管理DMA映射
     *   SPI控制器驱动直接使用tx_dma地址,不做额外映射
     *   适用于一致性DMA缓冲区(已经有DMA地址了)
     *
     * = false: SPI控制器驱动负责DMA映射
     *   驱动会调用dma_map_single(tx_buf)来获取DMA地址
     *   适用于普通内存缓冲区
     */
    xfer.is_dma_mapped = true;  /* 告诉SPI: 我已经映射好了! */

    spi_message_add_tail(&xfer, &msg);

    /*
     * 📚 步骤3: 同步提交SPI传输
     *
     * spi_sync() 会阻塞直到传输完成
     * 内部流程:
     *   1. 将message加入SPI控制器的队列
     *   2. SPI控制器驱动配置DMA控制器
     *   3. DMA控制器从coherent_buf.dma_addr读取数据
     *   4. DMA控制器将数据写入SPI数据寄存器
     *   5. SPI控制器将数据通过MOSI线发送
     *   6. DMA完成 -> 中断 -> 唤醒等待的线程
     *
     * 也可以使用 spi_async() 异步传输,不阻塞
     */
    ret = spi_sync(oled->spi, &msg);

    end = ktime_get();
    elapsed_ns = ktime_to_ns(ktime_sub(end, start));

    /* 更新统计 */
    spin_lock(&oled->stats_lock);
    if (ret == 0) {
        oled->stats.dma_transfers++;
        oled->stats.dma_bytes += oled->coherent_buf.size;
        oled->stats.avg_transfer_time_ns =
            (oled->stats.avg_transfer_time_ns * (oled->stats.dma_transfers - 1)
             + elapsed_ns) / oled->stats.dma_transfers;
    } else {
        oled->stats.dma_errors++;
    }
    oled->stats.last_transfer_start = start;
    oled->stats.last_transfer_end = end;
    spin_unlock(&oled->stats_lock);

    mutex_unlock(&oled->buf_lock);

    if (ret)
        dev_err(oled->dev, "Coherent DMA transfer failed: %d\n", ret);
    else
        dev_dbg(oled->dev, "  传输完成, 耗时: %lld ns\n", elapsed_ns);

    return ret;
}

/**
 * oled_dma_transfer_streaming - 使用流式DMA传输数据
 *
 * 📚 流式DMA传输流程 (这是最重要的DMA概念之一):
 *
 *   ┌──────────────────────────────────────────────────────────────┐
 *   │ 阶段A: CPU拥有缓冲区                                        │
 *   │                                                              │
 *   │   CPU可以读写缓冲区                                          │
 *   │   此时设备不能访问缓冲区                                     │
 *   │                                                              │
 *   │   [CPU Cache]     [内存]                                     │
 *   │   ┌─────────┐    ┌─────────┐                                │
 *   │   │新数据   │    │旧数据    │  <-- Cache和内存可能不一致!    │
 *   │   └─────────┘    └─────────┘                                │
 *   ├──────────────────────────────────────────────────────────────┤
 *   │ 阶段B: dma_map_single() - 将所有权交给设备                   │
 *   │                                                              │
 *   │   对于DMA_TO_DEVICE方向:                                     │
 *   │   - 将CPU Cache中的脏数据刷新(flush/writeback)到内存         │
 *   │   - 确保设备能看到最新的数据                                 │
 *   │                                                              │
 *   │   [CPU Cache]     [内存]                                     │
 *   │   ┌─────────┐    ┌─────────┐                                │
 *   │   │新数据   │───>│新数据    │  <-- Cache flush到内存        │
 *   │   └─────────┘    └─────────┘                                │
 *   │                       ↓                                      │
 *   │                  [DMA控制器可以安全读取]                      │
 *   │                                                              │
 *   │   ⚠️ 此时CPU不能再访问这块内存!                             │
 *   ├──────────────────────────────────────────────────────────────┤
 *   │ 阶段C: DMA传输进行中                                        │
 *   │                                                              │
 *   │   DMA控制器从内存读取数据,发送到SPI控制器                   │
 *   │   CPU不参与,可以执行其他任务                                │
 *   │                                                              │
 *   │   [内存] ──DMA──> [SPI TX FIFO] ──MOSI──> [OLED]           │
 *   ├──────────────────────────────────────────────────────────────┤
 *   │ 阶段D: dma_unmap_single() - 将所有权还给CPU                  │
 *   │                                                              │
 *   │   对于DMA_TO_DEVICE方向:                                     │
 *   │   - 无效化(invalidate) Cache中对应的行                      │
 *   │   - 确保CPU下次读取时从内存获取最新数据                      │
 *   │                                                              │
 *   │   现在CPU可以再次安全访问这块内存                             │
 *   └──────────────────────────────────────────────────────────────┘
 *
 * Cache一致性问题详解:
 *
 *   为什么需要sync?
 *   
 *   现代CPU使用Cache来加速内存访问。CPU写数据时,
 *   数据可能只写到Cache中(write-back策略),而不是立即写到内存。
 *   
 *   DMA控制器直接访问物理内存,看不到CPU Cache中的数据。
 *   如果不做sync,DMA可能读到旧数据!
 *
 *   ┌───────┐     ┌───────────┐     ┌─────────┐
 *   │  CPU  │────>│ L1/L2 Cache│    │  内存    │
 *   │       │     │ (新数据)   │    │ (旧数据) │
 *   └───────┘     └───────────┘    └─────────┘
 *                                       ↑
 *                                  DMA读取的是这里!
 *                                  = 旧数据 = BUG!
 *
 *   解决: dma_map_single() 中的cache flush
 *
 *   ┌───────┐     ┌───────────┐     ┌─────────┐
 *   │  CPU  │     │ L1/L2 Cache│───>│  内存    │
 *   │       │     │ (新数据)   │flush│ (新数据) │ ← DMA现在能读到正确数据
 *   └───────┘     └───────────┘    └─────────┘
 */
int oled_dma_transfer_streaming(struct spi_oled_device *oled)
{
    struct spi_transfer xfer = {0};
    struct spi_message msg;
    struct device *dev = oled->dev;
    dma_addr_t dma_addr;
    ktime_t start, end;
    s64 elapsed_ns;
    int ret;

    dev_dbg(dev, "📚 流式DMA传输开始\n");

    mutex_lock(&oled->buf_lock);
    start = ktime_get();

    /*
     * 📚 步骤1: 数据已经由CPU写入streaming_buf.vaddr
     * 此时数据可能还在CPU Cache中,尚未写入物理内存
     */

    /*
     * 📚 步骤2: dma_map_single() - DMA映射
     *
     * 函数签名: dma_map_single(dev, vaddr, size, direction)
     *
     * 参数说明:
     *   dev       - 执行DMA的设备
     *   vaddr     - CPU虚拟地址(要映射的缓冲区)
     *   size      - 映射大小
     *   direction - DMA方向:
     *               DMA_TO_DEVICE     = CPU -> 设备 (TX, 我们这里用这个)
     *               DMA_FROM_DEVICE   = 设备 -> CPU (RX)
     *               DMA_BIDIRECTIONAL = 双向
     *
     * 返回值: DMA总线地址 (设备使用的地址)
     *
     * 内部操作 (以DMA_TO_DEVICE为例):
     *   1. 计算虚拟地址对应的物理地址
     *   2. 如果有IOMMU: 在IOMMU中建立映射
     *   3. Cache操作: clean (write-back) 对应的cache行
     *      确保Cache中的脏数据写入内存
     *   4. 返回DMA总线地址
     *
     * 映射后的规则 (DMA_TO_DEVICE):
     *   - CPU不能再写入缓冲区 (写入的数据可能不可见)
     *   - CPU不应该读取缓冲区 (可能读到缓存的旧数据)
     *   - 只有DMA控制器可以访问
     *
     * 错误检查: 必须用dma_mapping_error()检查返回值!
     */
    dma_addr = dma_map_single(
        dev,                            /* 设备 */
        oled->streaming_buf.vaddr,      /* CPU虚拟地址 */
        oled->streaming_buf.size,       /* 大小 */
        DMA_TO_DEVICE                   /* 方向: CPU -> 设备 */
    );

    /*
     * 📚 检查映射错误
     *
     * DMA映射可能失败的原因:
     *   1. IOMMU地址空间不足
     *   2. 内存区域不可映射
     *   3. bounce buffer分配失败
     *
     * 注意: 不要用 if (dma_addr == 0) 来检查!
     * 0是一个合法的DMA地址,必须用dma_mapping_error()
     */
    if (dma_mapping_error(dev, dma_addr)) {
        dev_err(dev, "DMA mapping failed!\n");
        mutex_unlock(&oled->buf_lock);
        return -EIO;
    }

    dev_dbg(dev, "  DMA映射成功:\n");
    dev_dbg(dev, "    虚拟地址: %px -> DMA地址: %pad\n",
            oled->streaming_buf.vaddr, &dma_addr);

    /* 保存DMA地址,用于后续unmap */
    oled->streaming_buf.dma_addr = dma_addr;

    /*
     * 📚 (可选) 如果在map之后、DMA启动之前还需要修改数据:
     *
     * dma_sync_single_for_cpu(dev, dma_addr, size, direction);
     * // ... 修改数据 ...
     * dma_sync_single_for_device(dev, dma_addr, size, direction);
     *
     * sync_for_cpu: 使CPU能看到设备写入的数据 (invalidate cache)
     * sync_for_device: 使设备能看到CPU写入的数据 (flush cache)
     *
     * 这对于需要多次重复使用同一映射的场景很有用:
     *
     *   dma_map_single()           // 初始映射
     *   for each transfer:
     *     sync_for_cpu()           // CPU获取所有权
     *     // CPU修改缓冲区
     *     sync_for_device()        // 设备获取所有权
     *     // 启动DMA传输
     *     // 等待完成
     *   dma_unmap_single()         // 最终取消映射
     *
     * 好处: 避免反复map/unmap的开销
     * 但必须严格遵守所有权协议!
     */

    /* DC = 1 表示数据模式 */
    gpiod_set_value_cansleep(oled->dc_gpio, 1);

    /*
     * 📚 步骤3: 配置SPI传输
     */
    spi_message_init(&msg);

    xfer.tx_buf = oled->streaming_buf.vaddr;
    xfer.tx_dma = dma_addr;         /* 使用我们映射的DMA地址 */
    xfer.len = oled->streaming_buf.size;
    xfer.bits_per_word = 8;
    xfer.speed_hz = oled->spi->max_speed_hz;
    xfer.is_dma_mapped = true;      /* 我们自己管理DMA映射 */

    spi_message_add_tail(&xfer, &msg);

    /*
     * 📚 步骤4: 执行传输
     *
     * 此时DMA控制器将:
     *   从 dma_addr 读取数据 -> 写入SPI TX FIFO -> 串行发送到OLED
     */
    ret = spi_sync(oled->spi, &msg);

    /*
     * 📚 步骤5: dma_unmap_single() - 取消DMA映射
     *
     * 传输完成后,必须取消映射!
     *
     * 函数签名: dma_unmap_single(dev, dma_addr, size, direction)
     *
     * 内部操作 (DMA_TO_DEVICE方向):
     *   1. invalidate 对应的CPU Cache行
     *      (虽然对于TO_DEVICE方向不是严格必须,但实现通常会做)
     *   2. 如果有IOMMU: 解除IOMMU映射
     *   3. 如果使用了bounce buffer: 释放bounce buffer
     *
     * 取消映射后:
     *   - CPU重新获得缓冲区的所有权
     *   - DMA地址不再有效,不能再使用
     *   - CPU可以安全地读写缓冲区
     *
     * ⚠️ 常见错误:
     *   - 忘记unmap (内存/IOMMU泄漏)
     *   - 传输完成前unmap (数据损坏)
     *   - unmap后继续使用DMA地址 (未定义行为)
     */
    dma_unmap_single(dev,
                     dma_addr,
                     oled->streaming_buf.size,
                     DMA_TO_DEVICE);
    oled->streaming_buf.dma_addr = 0;  /* 标记为未映射 */

    dev_dbg(dev, "  DMA映射已取消\n");

    end = ktime_get();
    elapsed_ns = ktime_to_ns(ktime_sub(end, start));

    /* 更新统计 */
    spin_lock(&oled->stats_lock);
    if (ret == 0) {
        oled->stats.dma_transfers++;
        oled->stats.dma_bytes += oled->streaming_buf.size;
        oled->stats.avg_transfer_time_ns =
            (oled->stats.avg_transfer_time_ns * (oled->stats.dma_transfers - 1)
             + elapsed_ns) / oled->stats.dma_transfers;
    } else {
        oled->stats.dma_errors++;
    }
    oled->stats.last_transfer_start = start;
    oled->stats.last_transfer_end = end;
    spin_unlock(&oled->stats_lock);

    mutex_unlock(&oled->buf_lock);

    if (ret)
        dev_err(dev, "Streaming DMA transfer failed: %d\n", ret);
    else
        dev_dbg(dev, "  传输完成, 耗时: %lld ns\n", elapsed_ns);

    return ret;
}

/**
 * oled_dma_transfer_sg - 使用Scatter-Gather DMA传输数据
 *
 * 📚 Scatter-Gather DMA传输流程:
 *
 *   ┌─────────────────────────────────────────────────────────┐
 *   │                                                         │
 *   │   内存中不连续的缓冲区:                                  │
 *   │                                                         │
 *   │   地址100: ┌──────┐                                     │
 *   │           │ 页0  │ 128 bytes (OLED第0页)               │
 *   │           └──────┘                                     │
 *   │   地址500: ┌──────┐                                     │
 *   │           │ 页1  │ 128 bytes (OLED第1页)               │
 *   │           └──────┘                                     │
 *   │   地址900: ┌──────┐                                     │
 *   │           │ 页2  │ 128 bytes (OLED第2页)               │
 *   │           └──────┘                                     │
 *   │   ...                                                   │
 *   │                                                         │
 *   │   SG列表: [100,128] -> [500,128] -> [900,128] -> ...   │
 *   │                                                         │
 *   │   dma_map_sg()后:                                       │
 *   │   每个SG条目得到一个DMA地址                              │
 *   │   DMA控制器按链表顺序自动传输所有块                      │
 *   │                                                         │
 *   │   好处: 不需要大块连续物理内存!                          │
 *   │         DMA控制器硬件处理跳转,无需CPU参与               │
 *   └─────────────────────────────────────────────────────────┘
 *
 * IOMMU与SG的关系:
 *
 *   没有IOMMU时:
 *     dma_map_sg() 返回的条目数 == 输入条目数
 *     每个物理不连续的块对应一个DMA传输
 *     DMA控制器需要支持链式传输
 *
 *   有IOMMU时:
 *     IOMMU可以将物理不连续的页映射到DMA地址连续的区域
 *     dma_map_sg() 可能合并多个条目!
 *     返回的条目数 <= 输入条目数
 *     
 *     物理内存:      IOMMU映射:        设备看到:
 *     ┌──────┐       ┌─────────┐       ┌──────────┐
 *     │ 0x100│───┐   │         │   ┌──>│ DMA 0x00 │
 *     └──────┘   └──>│ IOMMU   │───┘   │ 连续!    │
 *     ┌──────┐   ┌──>│ 页表    │───┐   │          │
 *     │ 0x500│───┘   │         │   └──>│          │
 *     └──────┘       └─────────┘       └──────────┘
 */
int oled_dma_transfer_sg(struct spi_oled_device *oled)
{
    struct device *dev = oled->dev;
    struct spi_transfer *xfers;
    struct spi_message msg;
    struct scatterlist *sg;
    int i, mapped_nents;
    ktime_t start, end;
    s64 elapsed_ns;
    int ret;

    dev_dbg(dev, "📚 Scatter-Gather DMA传输开始\n");

    mutex_lock(&oled->buf_lock);
    start = ktime_get();

    /*
     * 📚 步骤1: dma_map_sg() - 映射整个SG表
     *
     * 函数签名: dma_map_sg(dev, sglist, nents, direction)
     *
     * 参数:
     *   dev      - 设备
     *   sglist   - scatterlist数组首地址
     *   nents    - SG条目数量
     *   direction - DMA方向
     *
     * 返回值: 映射后的条目数量
     *   - 可能小于nents (IOMMU合并了条目)
     *   - 返回0表示失败
     *
     * 内部操作:
     *   对每个SG条目:
     *   1. 获取其物理地址
     *   2. 做Cache操作 (flush/invalidate)
     *   3. 设置DMA地址 (直接映射或通过IOMMU)
     *   4. 尝试合并相邻的条目 (如果IOMMU允许)
     *
     * 映射后:
     *   使用 for_each_sg() 遍历,用 sg_dma_address() 和
     *   sg_dma_len() 获取DMA地址和长度
     *   (而不是 sg_phys() 和 sg->length)
     */
    mapped_nents = dma_map_sg(dev,
                              oled->sg_table.sgl,
                              oled->sg_nents,
                              DMA_TO_DEVICE);

    if (mapped_nents == 0) {
        dev_err(dev, "SG DMA mapping failed!\n");
        mutex_unlock(&oled->buf_lock);
        return -EIO;
    }

    dev_dbg(dev, "  SG映射: %d个输入条目 -> %d个DMA条目%s\n",
            oled->sg_nents, mapped_nents,
            (mapped_nents < oled->sg_nents) ? " (IOMMU合并了部分条目)" : "");

    /* DC = 1 表示数据模式 */
    gpiod_set_value_cansleep(oled->dc_gpio, 1);

    /*
     * 📚 步骤2: 为每个SG条目创建SPI传输
     *
     * SPI框架不直接支持SG列表传输
     * 我们需要将每个SG条目转换为一个spi_transfer
     * 然后将所有transfer添加到一个spi_message中
     *
     * spi_message支持链式transfer:
     *   message: [xfer0] -> [xfer1] -> [xfer2] -> ...
     *   SPI控制器会依次执行所有transfer
     *   如果支持DMA链式传输,可以一次设置所有DMA描述符
     */
    xfers = kcalloc(mapped_nents, sizeof(struct spi_transfer), GFP_KERNEL);
    if (!xfers) {
        ret = -ENOMEM;
        goto err_unmap;
    }

    spi_message_init(&msg);

    i = 0;
    for_each_sg(oled->sg_table.sgl, sg, mapped_nents, i) {
        /*
         * 📚 sg_dma_address() vs sg_phys()
         *
         * sg_dma_address(sg) - 返回DMA映射后的总线地址
         *   这是DMA控制器应该使用的地址
         *   如果有IOMMU,这是IOMMU虚拟地址
         *
         * sg_phys(sg) - 返回物理地址
         *   不应该用于DMA传输!
         *   除非确定没有IOMMU且DMA地址 == 物理地址
         *
         * sg_dma_len(sg) - 返回DMA映射后的长度
         *   可能与sg->length不同(IOMMU合并后)
         *
         * sg_virt(sg) - 返回虚拟地址
         *   用于CPU访问
         */
        xfers[i].tx_buf = sg_virt(sg);
        xfers[i].tx_dma = sg_dma_address(sg);
        xfers[i].len = sg_dma_len(sg);
        xfers[i].bits_per_word = 8;
        xfers[i].speed_hz = oled->spi->max_speed_hz;
        xfers[i].is_dma_mapped = true;

        /*
         * 📚 cs_change标志:
         * 默认情况下,SPI在message的最后一个transfer后取消CS
         * cs_change = 1 在非最后transfer中: 传输后短暂拉高CS再拉低
         * cs_change = 0 (默认): transfer之间保持CS低电平
         * 
         * 对OLED: 我们希望CS在整个message期间保持低
         */
        xfers[i].cs_change = 0;

        spi_message_add_tail(&xfers[i], &msg);

        dev_dbg(dev, "  SG[%d]: DMA地址=%pad, 长度=%u\n",
                i, &xfers[i].tx_dma, xfers[i].len);
    }

    /*
     * 📚 步骤3: 提交SPI消息
     *
     * 所有transfer会被链式执行
     * SPI控制器驱动可能:
     *   a) 用一个大的DMA链表一次性传输所有transfer
     *   b) 依次为每个transfer设置DMA
     *   取决于SPI控制器驱动的实现
     */
    ret = spi_sync(oled->spi, &msg);

    /* 释放transfer数组 */
    kfree(xfers);

err_unmap:
    /*
     * 📚 步骤4: dma_unmap_sg() - 取消SG映射
     *
     * 函数签名: dma_unmap_sg(dev, sglist, nents, direction)
     *
     * 注意: nents参数必须是原始的条目数(不是mapped_nents)!
     *       dma_unmap_sg需要知道原始映射了多少条目
     *
     * 内部操作:
     *   对每个条目执行cache操作并解除DMA/IOMMU映射
     */
    dma_unmap_sg(dev,
                 oled->sg_table.sgl,
                 oled->sg_nents,        /* 注意: 用原始数量,不是mapped_nents */
                 DMA_TO_DEVICE);

    end = ktime_get();
    elapsed_ns = ktime_to_ns(ktime_sub(end, start));

    /* 更新统计 */
    spin_lock(&oled->stats_lock);
    if (ret == 0) {
        oled->stats.dma_transfers++;
        oled->stats.dma_bytes += OLED_BUF_SIZE;
        oled->stats.avg_transfer_time_ns =
            (oled->stats.avg_transfer_time_ns * (oled->stats.dma_transfers - 1)
             + elapsed_ns) / oled->stats.dma_transfers;
    } else {
        oled->stats.dma_errors++;
    }
    oled->stats.last_transfer_start = start;
    oled->stats.last_transfer_end = end;
    spin_unlock(&oled->stats_lock);

    mutex_unlock(&oled->buf_lock);

    if (ret)
        dev_err(dev, "SG DMA transfer failed: %d\n", ret);
    else
        dev_dbg(dev, "  传输完成, 耗时: %lld ns\n", elapsed_ns);

    return ret;
}

/**
 * oled_pio_transfer - PIO传输(无DMA对比)
 *
 * 📚 PIO vs DMA 对比:
 *
 *   PIO (Programmed I/O):
 *     CPU循环:
 *       读取内存[i] -> 写入SPI_DR寄存器 -> 等待TXE标志 -> i++
 *     
 *     优点: 简单,无需配置DMA
 *     缺点: CPU全程占用,不能做其他事情
 *     适用: 小量数据(几个字节)
 *
 *   DMA:
 *     CPU配置DMA -> DMA自动传输 -> CPU做其他事 -> DMA完成中断
 *     
 *     优点: CPU空闲,可以处理其他任务
 *     缺点: 配置开销较大
 *     适用: 大量数据(数百字节以上)
 *
 *   何时选择DMA:
 *     通常SPI控制器驱动有一个阈值:
 *     数据量 < 阈值: 使用PIO (配置DMA的开销 > 传输时间)
 *     数据量 >= 阈值: 使用DMA (CPU节省的时间 > DMA配置开销)
 *     阈值通常为 64-256 字节
 */
int oled_pio_transfer(struct spi_oled_device *oled)
{
    ktime_t start, end;
    s64 elapsed_ns;
    int ret;

    dev_dbg(oled->dev, "📚 PIO传输开始 (无DMA)\n");

    mutex_lock(&oled->buf_lock);
    start = ktime_get();

    ret = oled_write_data_pio(oled, oled->streaming_buf.vaddr,
                              oled->streaming_buf.size);

    end = ktime_get();
    elapsed_ns = ktime_to_ns(ktime_sub(end, start));

    spin_lock(&oled->stats_lock);
    oled->stats.pio_transfers++;
    oled->stats.last_transfer_start = start;
    oled->stats.last_transfer_end = end;
    spin_unlock(&oled->stats_lock);

    mutex_unlock(&oled->buf_lock);

    dev_dbg(oled->dev, "  PIO传输完成, 耗时: %lld ns\n", elapsed_ns);

    return ret;
}

/* ============================================================
 * 第4部分: Framebuffer格式转换与显示刷新
 * ============================================================
 */

/**
 * oled_convert_fb_to_oled - 将framebuffer格式转换为OLED格式
 *
 * Linux framebuffer: 逐行扫描,每像素1bit
 *   byte0: [px7 px6 px5 px4 px3 px2 px1 px0] <- 第0行的8个像素
 *
 * SSD1306 OLED: 按页(page)组织,每页8行,逐列扫描
 *   byte0: [row7 row6 row5 row4 row3 row2 row1 row0] <- 第0列,第0页的8行像素
 *
 * 需要做位重排
 */
void oled_convert_fb_to_oled(struct spi_oled_device *oled, u8 *dst)
{
    u8 *src = oled->fb_vmem;
    int x, page;
    int src_line_len = OLED_WIDTH / 8;  /* 每行字节数 = 16 */

    for (page = 0; page < OLED_PAGES; page++) {
        for (x = 0; x < OLED_WIDTH; x++) {
            u8 byte = 0;
            int bit;

            for (bit = 0; bit < 8; bit++) {
                int y = page * 8 + bit;
                int src_byte_idx = y * src_line_len + (x / 8);
                int src_bit_idx = 7 - (x % 8);

                if (src[src_byte_idx] & (1 << src_bit_idx))
                    byte |= (1 << bit);
            }

            dst[page * OLED_WIDTH + x] = byte;
        }
    }
}

/**
 * oled_refresh_display - 刷新OLED显示内容
 *
 * 核心刷新函数: 转换格式 + DMA传输
 */
int oled_refresh_display(struct spi_oled_device *oled)
{
    u8 *dst_buf;
    int ret;

    /* 设置OLED写入区域为全屏 */
    u8 cmds[] = {
        SSD1306_SET_COL_ADDR, 0, OLED_WIDTH - 1,
        SSD1306_SET_PAGE_ADDR, 0, OLED_PAGES - 1,
    };

    ret = oled_write_cmd_seq(oled, cmds, sizeof(cmds));
    if (ret)
        return ret;

    /* 根据DMA模式选择目标缓冲区并传输 */
    switch (oled->dma_mode) {
    case DMA_MODE_COHERENT:
        /*
         * 📚 一致性DMA: 直接写入一致性缓冲区
         * CPU写入后,DMA控制器立即可见,无需sync
         */
        dst_buf = oled->coherent_buf.vaddr;
        oled_convert_fb_to_oled(oled, dst_buf);
        ret = oled_dma_transfer_coherent(oled);
        break;

    case DMA_MODE_STREAMING:
        /*
         * 📚 流式DMA: 写入普通缓冲区
         * 在dma_map_single()时会自动flush cache
         */
        dst_buf = oled->streaming_buf.vaddr;
        oled_convert_fb_to_oled(oled, dst_buf);
        ret = oled_dma_transfer_streaming(oled);
        break;

    case DMA_MODE_SG:
        /*
         * 📚 SG DMA: 将数据分散写入各个SG缓冲区
         * 每个SG条目对应OLED的一页
         */
        {
            u8 tmp_buf[OLED_BUF_SIZE];
            struct scatterlist *sg;
            int i, offset = 0;

            /* 先转换到临时缓冲区 */
            oled_convert_fb_to_oled(oled, tmp_buf);

            /* 然后分散复制到各SG缓冲区 */
            for_each_sg(oled->sg_table.sgl, sg, oled->sg_nents, i) {
                void *sg_buf = sg_virt(sg);
                size_t chunk = sg->length;

                memcpy(sg_buf, tmp_buf + offset, chunk);
                offset += chunk;
            }

            ret = oled_dma_transfer_sg(oled);
        }
        break;

    case DMA_MODE_PIO:
        /*
         * 📚 PIO: 直接CPU传输,作为对比基准
         */
        dst_buf = oled->streaming_buf.vaddr;
        oled_convert_fb_to_oled(oled, dst_buf);
        ret = oled_pio_transfer(oled);
        break;

    default:
        ret = -EINVAL;
    }

    return ret;
}

/**
 * oled_refresh_work_func - 周期性刷新工作函数
 */
static void oled_refresh_work_func(struct work_struct *work)
{
    struct spi_oled_device *oled =
        container_of(work, struct spi_oled_device, refresh_work.work);

    if (!oled->powered_on)
        return;

    oled_refresh_display(oled);

    /* 重新调度下一次刷新 */
    if (oled->refresh_pending) {
        schedule_delayed_work(&oled->refresh_work,
                             msecs_to_jiffies(refresh_interval));
    }
}

/* ============================================================
 * 第5部分: SSD1306 OLED硬件初始化
 * ============================================================
 */

/**
 * oled_hw_reset - 硬件复位OLED
 */
void oled_hw_reset(struct spi_oled_device *oled)
{
    if (!oled->reset_gpio)
        return;

    gpiod_set_value_cansleep(oled->reset_gpio, 1);  /* 拉高复位 */
    msleep(10);
    gpiod_set_value_cansleep(oled->reset_gpio, 0);  /* 释放复位 */
    msleep(10);
    gpiod_set_value_cansleep(oled->reset_gpio, 1);  /* 正常工作 */
    msleep(10);
}

/**
 * oled_hw_init - 初始化SSD1306 OLED
 */
int oled_hw_init(struct spi_oled_device *oled)
{
    /* SSD1306 初始化序列 */
    static const u8 init_cmds[] = {
        SSD1306_DISPLAY_OFF,
        SSD1306_SET_DISPLAY_CLOCK_DIV, 0x80,
        SSD1306_SET_MUX_RATIO, 0x3F,           /* 64行 */
        SSD1306_SET_DISPLAY_OFFSET, 0x00,
        SSD1306_SET_START_LINE | 0x00,
        SSD1306_CHARGE_PUMP, 0x14,              /* 使能内部charge pump */
        SSD1306_SET_MEMORY_MODE, 0x00,          /* 水平寻址模式 */
        SSD1306_SET_SEG_REMAP,                  /* 段重映射 */
        SSD1306_SET_COM_SCAN_DEC,               /* COM扫描方向 */
        SSD1306_SET_COM_PINS, 0x12,
        SSD1306_SET_CONTRAST, 0xCF,
        SSD1306_SET_PRECHARGE, 0xF1,
        SSD1306_SET_VCOM_DETECT, 0x40,
        SSD1306_DISPLAY_ALL_ON_RESUME,
        SSD1306_SET_NORMAL_DISPLAY,
        SSD1306_DISPLAY_ON,
    };

    int i, ret;

    dev_info(oled->dev, "初始化SSD1306 OLED...\n");

    oled_hw_reset(oled);

    /* 逐个发送初始化命令 */
    for (i = 0; i < sizeof(init_cmds); i++) {
        ret = oled_write_cmd(oled, init_cmds[i]);
        if (ret)
            return ret;
    }

    oled->powered_on = true;
    oled->contrast = 0xCF;

    dev_info(oled->dev, "SSD1306 OLED初始化完成\n");

    return 0;
}

/* ============================================================
 * 第6部分: Sysfs接口 - 用于调试和学习
 * ============================================================
 *
 * 通过sysfs可以:
 *   - 查看DMA统计信息
 *   - 动态切换DMA模式
 *   - 对比不同模式的性能
 */

/**
 * 显示DMA统计信息
 * cat /sys/devices/.../dma_stats
 */
static ssize_t dma_stats_show(struct device *dev,
                               struct device_attribute *attr, char *buf)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);
    struct oled_dma_stats stats;
    s64 last_elapsed;

    spin_lock(&oled->stats_lock);
    stats = oled->stats;
    spin_unlock(&oled->stats_lock);

    last_elapsed = ktime_to_ns(ktime_sub(stats.last_transfer_end,
                                          stats.last_transfer_start));

    return sysfs_emit(buf,
        "=== DMA统计信息 ===\n"
        "当前DMA模式: %d (%s)\n"
        "DMA传输次数: %llu\n"
        "DMA传输字节: %llu\n"
        "DMA错误次数: %llu\n"
        "PIO传输次数: %llu\n"
        "平均传输时间: %llu ns\n"
        "上次传输时间: %lld ns\n"
        "\n"
        "=== DMA模式说明 ===\n"
        "0: Coherent  - 一致性DMA (无需同步)\n"
        "1: Streaming - 流式DMA (需要map/unmap)\n"
        "2: SG        - 分散聚集DMA\n"
        "3: PIO       - CPU直接传输(无DMA)\n",
        oled->dma_mode,
        (oled->dma_mode == 0) ? "Coherent" :
        (oled->dma_mode == 1) ? "Streaming" :
        (oled->dma_mode == 2) ? "Scatter-Gather" : "PIO",
        stats.dma_transfers,
        stats.dma_bytes,
        stats.dma_errors,
        stats.pio_transfers,
        stats.avg_transfer_time_ns,
        last_elapsed
    );
}
static DEVICE_ATTR_RO(dma_stats);

/**
 * 显示和设置DMA模式
 * echo 1 > /sys/devices/.../dma_mode   # 切换到Streaming模式
 * cat /sys/devices/.../dma_mode         # 查看当前模式
 */
static ssize_t dma_mode_show(struct device *dev,
                              struct device_attribute *attr, char *buf)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);

    return sysfs_emit(buf, "%d\n", oled->dma_mode);
}

static ssize_t dma_mode_store(struct device *dev,
                               struct device_attribute *attr,
                               const char *buf, size_t count)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);
    int new_mode;
    int ret;

    ret = kstrtoint(buf, 10, &new_mode);
    if (ret)
        return ret;

    if (new_mode < 0 || new_mode > DMA_MODE_PIO) {
        dev_err(dev, "Invalid DMA mode: %d (valid: 0-3)\n", new_mode);
        return -EINVAL;
    }

    if (new_mode == oled->dma_mode)
        return count;

    dev_info(dev, "📚 切换DMA模式: %d -> %d\n", oled->dma_mode, new_mode);

    /*
     * 📚 切换DMA模式流程:
     *   1. 暂停刷新工作
     *   2. 清理旧模式的DMA资源
     *   3. 切换模式
     *   4. 初始化新模式的DMA资源
     *   5. 恢复刷新工作
     *
     * 这展示了DMA资源管理的生命周期
     */
    oled->refresh_pending = false;
    cancel_delayed_work_sync(&oled->refresh_work);

    /* 清理旧DMA资源 */
    oled_dma_cleanup(oled);

    /* 切换模式并初始化新资源 */
    oled->dma_mode = new_mode;
    ret = oled_dma_init(oled);
    if (ret) {
        dev_err(dev, "Failed to init new DMA mode: %d\n", ret);
        /* 回退到PIO模式 */
        oled->dma_mode = DMA_MODE_PIO;
        oled_dma_init(oled);
    }

    /* 重置统计 */
    spin_lock(&oled->stats_lock);
    memset(&oled->stats, 0, sizeof(oled->stats));
    spin_unlock(&oled->stats_lock);

    /* 恢复刷新 */
    oled->refresh_pending = true;
    schedule_delayed_work(&oled->refresh_work,
                         msecs_to_jiffies(refresh_interval));

    return count;
}
static DEVICE_ATTR_RW(dma_mode);

/**
 * 显示DMA地址信息 - 帮助理解地址映射
 * cat /sys/devices/.../dma_addresses
 */
static ssize_t dma_addresses_show(struct device *dev,
                                   struct device_attribute *attr, char *buf)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);
    int len = 0;

    len += sysfs_emit_at(buf, len,
        "=== 📚 DMA地址映射信息 ===\n\n"
        "地址类型说明:\n"
        "  虚拟地址(vaddr): CPU通过MMU翻译后使用的地址\n"
        "  DMA地址(dma_addr): DMA控制器使用的总线地址\n"
        "  物理地址: 实际内存芯片上的地址\n\n"
    );

    switch (oled->dma_mode) {
    case DMA_MODE_COHERENT:
        len += sysfs_emit_at(buf, len,
            "模式: 一致性DMA (Coherent)\n"
            "  虚拟地址:  %px\n"
            "  DMA地址:   %pad\n"
            "  大小:      %zu bytes\n"
            "  特点:      两个地址指向同一物理内存\n"
            "             内存被映射为non-cacheable\n"
            "             CPU写入后设备立即可见\n",
            oled->coherent_buf.vaddr,
            &oled->coherent_buf.dma_addr,
            oled->coherent_buf.size
        );
        break;

    case DMA_MODE_STREAMING:
        len += sysfs_emit_at(buf, len,
            "模式: 流式DMA (Streaming)\n"
            "  虚拟地址:  %px\n"
            "  DMA地址:   %s\n"
            "  大小:      %zu bytes\n"
            "  特点:      DMA地址在每次传输时动态映射\n"
            "             需要map/unmap操作\n"
            "             内存是cacheable的(性能更好)\n",
            oled->streaming_buf.vaddr,
            oled->streaming_buf.dma_addr ? "已映射" : "未映射(传输时才映射)",
            oled->streaming_buf.size
        );
        break;

    case DMA_MODE_SG:
        {
            struct scatterlist *sg;
            int i;

            len += sysfs_emit_at(buf, len,
                "模式: Scatter-Gather DMA\n"
                "  SG条目数: %d\n\n",
                oled->sg_nents
            );

            for_each_sg(oled->sg_table.sgl, sg, oled->sg_nents, i) {
                len += sysfs_emit_at(buf, len,
                    "  SG[%d]: vaddr=%px, length=%u\n",
                    i, sg_virt(sg), sg->length
                );
            }

            len += sysfs_emit_at(buf, len,
                "\n  特点: 多个不连续的内存块\n"
                "         DMA控制器自动链式传输\n"
                "         IOMMU可能合并相邻条目\n"
            );
        }
        break;

    case DMA_MODE_PIO:
        len += sysfs_emit_at(buf, len,
            "模式: PIO (无DMA)\n"
            "  缓冲区地址: %px\n"
            "  大小:       %zu bytes\n"
            "  特点:       CPU直接搬运数据,无DMA参与\n",
            oled->streaming_buf.vaddr,
            oled->streaming_buf.size
        );
        break;
    }

    return len;
}
static DEVICE_ATTR_RO(dma_addresses);

/**
 * 手动触发一次显示刷新(用于测试)
 * echo 1 > /sys/devices/.../refresh
 */
static ssize_t refresh_store(struct device *dev,
                              struct device_attribute *attr,
                              const char *buf, size_t count)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);
    int ret;

    ret = oled_refresh_display(oled);
    if (ret)
        return ret;

    return count;
}
static DEVICE_ATTR_WO(refresh);

/**
 * DMA学习笔记 - 输出综合教学信息
 * cat /sys/devices/.../dma_tutorial
 */
static ssize_t dma_tutorial_show(struct device *dev,
                                  struct device_attribute *attr, char *buf)
{
    return sysfs_emit(buf,
        "╔══════════════════════════════════════════════════════════╗\n"
        "║          📚 Linux DMA 学习指南                          ║\n"
        "╠══════════════════════════════════════════════════════════╣\n"
        "║                                                          ║\n"
        "║  1. DMA是什么?                                           ║\n"
        "║     Direct Memory Access - 无需CPU参与的数据搬运         ║\n"
        "║     CPU只需告诉DMA:从哪里、到哪里、搬多少               ║\n"
        "║                                                          ║\n"
        "║  2. 为什么需要DMA?                                       ║\n"
        "║     - CPU不用等待慢速I/O设备                             ║\n"
        "║     - 数据传输期间CPU可以处理其他任务                    ║\n"
        "║     - 对于大量数据传输,DMA效率远高于PIO                 ║\n"
        "║                                                          ║\n"
        "║  3. DMA映射类型:                                         ║\n"
        "║     Coherent: 简单安全,但内存non-cacheable较慢          ║\n"
        "║     Streaming: 需手动同步,但性能更好                    ║\n"
        "║     SG: 不连续内存也能高效传输                           ║\n"
        "║                                                          ║\n"
        "║  4. 实验方法:                                            ║\n"
        "║     echo 0 > dma_mode  # 切换到Coherent模式              ║\n"
        "║     echo 1 > dma_mode  # 切换到Streaming模式             ║\n"
        "║     echo 2 > dma_mode  # 切换到SG模式                    ║\n"
        "║     echo 3 > dma_mode  # 切换到PIO模式(对比)             ║\n"
        "║     cat dma_stats      # 查看传输统计                    ║\n"
        "║     cat dma_addresses  # 查看地址映射                    ║\n"
        "║                                                          ║\n"
        "║  5. 关键API:                                             ║\n"
        "║     dma_alloc_coherent()    - 分配一致性DMA缓冲区        ║\n"
        "║     dma_map_single()        - 流式DMA映射                ║\n"
        "║     dma_unmap_single()      - 取消流式DMA映射            ║\n"
        "║     dma_map_sg()            - 映射scatter-gather表       ║\n"
        "║     dma_sync_single_for_*() - 同步缓冲区                ║\n"
        "║     dma_set_mask()          - 设置DMA地址掩码            ║\n"
        "║                                                          ║\n"
        "╚══════════════════════════════════════════════════════════╝\n"
    );
}
static DEVICE_ATTR_RO(dma_tutorial);

/* sysfs属性组 */
static struct attribute *oled_dma_attrs[] = {
    &dev_attr_dma_stats.attr,
    &dev_attr_dma_mode.attr,
    &dev_attr_dma_addresses.attr,
    &dev_attr_refresh.attr,
    &dev_attr_dma_tutorial.attr,
    NULL,
};
ATTRIBUTE_GROUPS(oled_dma);

/* ============================================================
 * 第7部分: Framebuffer接口
 * ============================================================
 */

/**
 * oled_fb_write - framebuffer写操作
 * 用户空间写入framebuffer时触发刷新
 */
static ssize_t oled_fb_write(struct fb_info *info, const char __user *buf,
                              size_t count, loff_t *ppos)
{
    struct spi_oled_device *oled = info->par;
    ssize_t ret;

    ret = fb_sys_write(info, buf, count, ppos);
    if (ret > 0) {
        /* 用户写入了数据,触发刷新 */
        schedule_delayed_work(&oled->refresh_work, 0);
    }

    return ret;
}

/**
 * oled_fb_fillrect / copyarea / imageblit
 * framebuffer绘图操作,完成后触发刷新
 */
static void oled_fb_fillrect(struct fb_info *info,
                              const struct fb_fillrect *rect)
{
    struct spi_oled_device *oled = info->par;

    sys_fillrect(info, rect);
    schedule_delayed_work(&oled->refresh_work, 0);
}

static void oled_fb_copyarea(struct fb_info *info,
                              const struct fb_copyarea *area)
{
    struct spi_oled_device *oled = info->par;

    sys_copyarea(info, area);
    schedule_delayed_work(&oled->refresh_work, 0);
}

static void oled_fb_imageblit(struct fb_info *info,
                               const struct fb_image *image)
{
    struct spi_oled_device *oled = info->par;

    sys_imageblit(info, image);
    schedule_delayed_work(&oled->refresh_work, 0);
}

/**
 * oled_fb_blank - framebuffer blank/unblank
 */
static int oled_fb_blank(int blank, struct fb_info *info)
{
    struct spi_oled_device *oled = info->par;

    switch (blank) {
    case FB_BLANK_UNBLANK:
        oled_write_cmd(oled, SSD1306_DISPLAY_ON);
        oled->powered_on = true;
        break;
    default:
        oled_write_cmd(oled, SSD1306_DISPLAY_OFF);
        oled->powered_on = false;
        break;
    }

    return 0;
}

static const struct fb_ops oled_fb_ops = {
    .owner          = THIS_MODULE,
    .fb_read        = fb_sys_read,
    .fb_write       = oled_fb_write,
    .fb_fillrect    = oled_fb_fillrect,
    .fb_copyarea    = oled_fb_copyarea,
    .fb_imageblit   = oled_fb_imageblit,
    .fb_blank       = oled_fb_blank,
};

/**
 * oled_fb_register - 注册framebuffer设备
 */
int oled_fb_register(struct spi_oled_device *oled)
{
    struct fb_info *info;
    int ret;

    /* 分配fb_info */
    info = framebuffer_alloc(0, oled->dev);
    if (!info)
        return -ENOMEM;

    oled->fb_info = info;

    /* 分配framebuffer显存 */
    oled->fb_vmem_size = (OLED_WIDTH * OLED_HEIGHT) / 8;
    oled->fb_vmem = devm_kzalloc(oled->dev, oled->fb_vmem_size, GFP_KERNEL);
    if (!oled->fb_vmem) {
        ret = -ENOMEM;
        goto err_fb_release;
    }

    /* 配置fb_info */
    info->fbops = &oled_fb_ops;
    info->screen_buffer = oled->fb_vmem;
    info->screen_size = oled->fb_vmem_size;
    info->par = oled;

    info->fix.type        = FB_TYPE_PACKED_PIXELS;
    info->fix.visual      = FB_VISUAL_MONO10;
    info->fix.line_length = OLED_WIDTH / 8;
    info->fix.smem_len    = oled->fb_vmem_size;
    strscpy(info->fix.id, "ssd1306", sizeof(info->fix.id));

    info->var.xres          = OLED_WIDTH;
    info->var.yres          = OLED_HEIGHT;
    info->var.xres_virtual  = OLED_WIDTH;
    info->var.yres_virtual  = OLED_HEIGHT;
    info->var.bits_per_pixel = 1;
    info->var.red.length    = 1;
    info->var.green.length  = 1;
    info->var.blue.length   = 1;

    ret = register_framebuffer(info);
    if (ret) {
        dev_err(oled->dev, "Failed to register framebuffer: %d\n", ret);
        goto err_fb_release;
    }

    dev_info(oled->dev, "Framebuffer注册成功: /dev/fb%d (%dx%d, 1bpp)\n",
             info->node, OLED_WIDTH, OLED_HEIGHT);

    return 0;

err_fb_release:
    framebuffer_release(info);
    oled->fb_info = NULL;
    return ret;
}

void oled_fb_unregister(struct spi_oled_device *oled)
{
    if (oled->fb_info) {
        unregister_framebuffer(oled->fb_info);
        framebuffer_release(oled->fb_info);
        oled->fb_info = NULL;
    }
}

/* ============================================================
 * 第8部分: 高级DMA功能演示 - DMA Engine API
 * ============================================================
 *
 * 📚 DMA Engine API 详解
 *
 * Linux内核有两层DMA API:
 *
 * 1. DMA映射API (我们前面用的):
 *    dma_alloc_coherent(), dma_map_single(), etc.
 *    只负责内存管理和地址转换
 *    不直接控制DMA控制器
 *
 * 2. DMA Engine API (这里演示的):
 *    dmaengine_*() 系列函数
 *    直接与DMA控制器交互
 *    可以提交DMA传输请求
 *
 * DMA Engine传输流程:
 *
 *   ┌──────────────────────────────────────────────┐
 *   │ 1. 请求DMA通道                               │
 *   │    chan = dma_request_chan(dev, "tx")          │
 *   │    从设备树中查找DMA通道绑定                   │
 *   ├──────────────────────────────────────────────┤
 *   │ 2. 配置DMA通道                               │
 *   │    dmaengine_slave_config(chan, &config)       │
 *   │    设置传输宽度、方向、FIFO地址等              │
 *   ├──────────────────────────────────────────────┤
 *   │ 3. 准备DMA描述符                              │
 *   │    desc = dmaengine_prep_slave_single(         │
 *   │             chan, dma_addr, len,               │
 *   │             DMA_MEM_TO_DEV, flags)            │
 *   │    描述符包含传输的所有参数                    │
 *   ├──────────────────────────────────────────────┤
 *   │ 4. 设置回调函数                               │
 *   │    desc->callback = my_dma_callback           │
 *   │    传输完成时调用                              │
 *   ├──────────────────────────────────────────────┤
 *   │ 5. 提交描述符                                 │
 *   │    cookie = dmaengine_submit(desc)            │
 *   │    加入DMA通道的待传输队列                     │
 *   ├──────────────────────────────────────────────┤
 *   │ 6. 启动传输                                   │
 *   │    dma_async_issue_pending(chan)               │
 *   │    DMA控制器开始实际传输                       │
 *   ├──────────────────────────────────────────────┤
 *   │ 7. 等待完成                                   │
 *   │    wait_for_completion(&done)                 │
 *   │    或轮询 dma_async_is_tx_complete()          │
 *   └──────────────────────────────────────────────┘
 */

/**
 * oled_dma_callback - DMA传输完成回调
 *
 * 📚 回调在什么上下文执行?
 *   - 通常在中断上下文 (tasklet/BH context)
 *   - 不能调用可能睡眠的函数
 *   - 不能调用mutex_lock()
 *   - 可以调用 complete() 来唤醒等待的线程
 */
static void oled_dma_callback(void *data)
{
    struct spi_oled_device *oled = data;

    dev_dbg(oled->dev, "📚 DMA传输完成回调被调用 (中断上下文)\n");

    /* 唤醒等待传输完成的线程 */
    complete(&oled->dma_completion);
}

/**
 * oled_dma_engine_transfer - 使用DMA Engine API直接控制DMA传输
 *
 * 这个函数演示了如何绕过SPI框架,直接使用DMA Engine API
 * 通常SPI框架内部会做这些,这里只是为了学习
 *
 * 注意: 实际项目中通常不需要这样做
 * SPI控制器驱动会自动使用DMA Engine
 */
static int oled_dma_engine_transfer(struct spi_oled_device *oled)
{
    struct dma_chan *chan;
    struct dma_async_tx_descriptor *desc;
    struct dma_slave_config config = {0};
    dma_cookie_t cookie;
    dma_addr_t dma_addr;
    unsigned long timeout;
    int ret;

    dev_info(oled->dev, "📚 DMA Engine API 演示传输\n");

    /*
     * 📚 步骤1: 请求DMA通道
     *
     * dma_request_chan(dev, name):
     *   从设备树中查找名为"name"的DMA通道
     *   设备树示例:
     *     dmas = <&dma_controller channel_id>;
     *     dma-names = "tx";
     *
     * 也可以用 dma_request_slave_channel() (旧API)
     */
    chan = dma_request_chan(oled->dev, "tx");
    if (IS_ERR(chan)) {
        dev_warn(oled->dev, "  无法获取DMA通道: %ld (这在很多平台上是正常的)\n",
                 PTR_ERR(chan));
        dev_info(oled->dev, "  说明: SPI控制器驱动通常已经管理了DMA通道\n");
        return PTR_ERR(chan);
    }
    oled->dma_chan_tx = chan;

    dev_info(oled->dev, "  DMA通道已获取: %s\n", dma_chan_name(chan));

    /*
     * 📚 步骤2: 配置DMA通道(slave config)
     *
     * 对于内存到外设(MEM_TO_DEV)的传输:
     *   dst_addr: 外设的FIFO物理地址(SPI TX数据寄存器)
     *   dst_addr_width: 每次传输的宽度(通常1字节=8位)
     *   dst_maxburst: 每次突发传输的最大数据个数
     *   direction: 传输方向
     *
     * 这些信息告诉DMA控制器:
     *   "把数据从内存搬到这个地址,每次搬1字节,最多突发N次"
     */
    config.direction = DMA_MEM_TO_DEV;
    config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
    config.dst_maxburst = 16;
    /* config.dst_addr = SPI_TX_FIFO_PHYS_ADDR; 通常由SPI驱动设置 */

    ret = dmaengine_slave_config(chan, &config);
    if (ret) {
        dev_err(oled->dev, "  DMA slave配置失败: %d\n", ret);
        goto err_release;
    }

    /*
     * 📚 步骤3: 映射源缓冲区
     */
    dma_addr = dma_map_single(oled->dev,
                              oled->coherent_buf.vaddr,
                              oled->coherent_buf.size,
                              DMA_TO_DEVICE);
    if (dma_mapping_error(oled->dev, dma_addr)) {
        ret = -EIO;
        goto err_release;
    }

    /*
     * 📚 步骤4: 准备DMA描述符
     *
     * dmaengine_prep_slave_single():
     *   准备一个单块内存的DMA传输描述符
     *
     * 参数:
     *   chan     - DMA通道
     *   dma_addr - 源数据的DMA地址
     *   len      - 传输长度
     *   dir      - 传输方向
     *   flags    - 标志:
     *     DMA_PREP_INTERRUPT: 传输完成后产生中断
     *     DMA_CTRL_ACK: 描述符可以被重用
     *
     * 返回: DMA传输描述符(可以看作一个"DMA任务")
     *
     * 还有其他prep函数:
     *   dmaengine_prep_slave_sg()   - SG列表传输
     *   dmaengine_prep_dma_memcpy() - 内存到内存拷贝
     *   dmaengine_prep_dma_cyclic() - 循环传输(音频DMA常用)
     */
    desc = dmaengine_prep_slave_single(
        chan,
        dma_addr,
        oled->coherent_buf.size,
        DMA_MEM_TO_DEV,
        DMA_PREP_INTERRUPT | DMA_CTRL_ACK
    );

    if (!desc) {
        dev_err(oled->dev, "  DMA描述符准备失败\n");
        ret = -ENOMEM;
        goto err_unmap;
    }

    /*
     * 📚 步骤5: 设置完成回调
     *
     * DMA传输是异步的。我们需要知道什么时候传输完成。
     * 方法1: 设置回调函数 (在中断上下文中调用)
     * 方法2: 轮询 dma_async_is_tx_complete()
     *
     * 回调函数 + completion 是最常见的模式
     */
    reinit_completion(&oled->dma_completion);

    desc->callback = oled_dma_callback;
    desc->callback_param = oled;

    /*
     * 📚 步骤6: 提交描述符
     *
     * dmaengine_submit() 将描述符加入通道的待处理队列
     * 但还没有开始传输!
     * 
     * 返回一个cookie,用于后续查询传输状态
     *
     * cookie的作用:
     *   - 标识这个特定的传输
     *   - 用于 dma_async_is_tx_complete() 查询状态
     *   - 正值表示成功,负值表示失败
     */
    cookie = dmaengine_submit(desc);
    if (dma_submit_error(cookie)) {
        dev_err(oled->dev, "  DMA提交失败\n");
        ret = -EIO;
        goto err_unmap;
    }

    dev_info(oled->dev, "  DMA描述符已提交, cookie=%d\n", cookie);

    /*
     * 📚 步骤7: 启动传输
     *
     * dma_async_issue_pending() 通知DMA控制器:
     * "队列中有新的传输任务,请开始处理"
     *
     * 这是一个触发点。之前submit只是排队,
     * 调用这个函数后DMA控制器才真正开始搬运数据。
     *
     * DMA控制器会:
     *   1. 读取描述符中的源地址、目标地址、长度
     *   2. 配置DMA通道
     *   3. 开始数据搬运
     *   4. 完成后产生中断 (因为我们设置了DMA_PREP_INTERRUPT)
     *   5. 中断处理中调用callback
     */
    dma_async_issue_pending(chan);

    dev_info(oled->dev, "  DMA传输已启动,CPU现在可以做其他事...\n");

    /*
     * 📚 步骤8: 等待传输完成
     *
     * wait_for_completion_timeout() 会让当前线程睡眠
     * 直到callback中调用complete(),或超时
     *
     * 在等待期间,CPU可以调度其他任务运行
     * 这就是DMA的核心价值:CPU不用傻等I/O!
     *
     * 等待方式的选择:
     *   wait_for_completion()         - 无限等待(危险!)
     *   wait_for_completion_timeout() - 有超时保护(推荐)
     *   poll dma_async_is_tx_complete() - 轮询模式(浪费CPU)
     *
     * 超时值的选择:
     *   1024 bytes / SPI 10MHz = ~0.8ms
     *   加上一些余量,5秒超时足够安全
     */
    timeout = wait_for_completion_timeout(&oled->dma_completion,
                                          msecs_to_jiffies(5000));

    if (timeout == 0) {
        dev_err(oled->dev, "  ⚠️ DMA传输超时!\n");
        dev_err(oled->dev, "  可能的原因:\n");
        dev_err(oled->dev, "    1. DMA控制器配置错误\n");
        dev_err(oled->dev, "    2. 中断未正确连接\n");
        dev_err(oled->dev, "    3. 外设未就绪\n");

        /*
         * 📚 超时后的恢复:
         * dmaengine_terminate_sync() 会:
         *   1. 停止DMA通道上所有正在进行的传输
         *   2. 等待所有已提交的callback完成
         *   3. 将通道恢复到空闲状态
         *
         * 还有 dmaengine_terminate_async(),
         * 不等待callback完成就返回(用于不能睡眠的上下文)
         */
        dmaengine_terminate_sync(chan);
        ret = -ETIMEDOUT;
    } else {
        /*
         * 📚 传输完成后,可选地检查最终状态
         *
         * dma_async_is_tx_complete() 返回:
         *   DMA_COMPLETE  - 传输成功完成
         *   DMA_IN_PROGRESS - 还在进行(不应该出现在这里)
         *   DMA_ERROR     - 传输出错
         *   DMA_PAUSED    - 传输被暂停
         */
        enum dma_status status;

        status = dma_async_is_tx_complete(chan, cookie, NULL, NULL);
        if (status == DMA_COMPLETE) {
            dev_info(oled->dev, "  ✅ DMA Engine传输完成!\n");
            ret = 0;
        } else {
            dev_err(oled->dev, "  ❌ DMA传输状态异常: %d\n", status);
            ret = -EIO;
        }
    }

err_unmap:
    dma_unmap_single(oled->dev, dma_addr,
                     oled->coherent_buf.size, DMA_TO_DEVICE);

err_release:
    if (oled->dma_chan_tx) {
        dma_release_channel(oled->dma_chan_tx);
        oled->dma_chan_tx = NULL;
    }

    return ret;
}

/* ============================================================
 * 第9部分: 高级DMA概念演示函数
 * ============================================================
 *
 * 以下函数不是OLED驱动必须的
 * 而是用于演示一些高级DMA概念
 */

/**
 * oled_demo_dma_pool - 演示DMA池 (DMA Pool)
 *
 * 📚 DMA Pool 概念:
 *
 * 问题: dma_alloc_coherent() 分配最少一个页面(4KB)
 *       如果我们需要很多小的DMA缓冲区(比如每个64字节)
 *       直接用dma_alloc_coherent会浪费大量内存
 *
 * 解决: DMA Pool - 预分配一大块DMA内存,然后分割使用
 *
 *   dma_alloc_coherent()     dma_pool
 *   ┌──────────────┐         ┌──────────────┐
 *   │              │         │┌────┐┌────┐  │
 *   │  4KB         │         ││ 64B││ 64B│  │
 *   │  (即使只要   │         │└────┘└────┘  │
 *   │   64字节)    │         │┌────┐┌────┐  │
 *   │              │         ││ 64B││ 64B│  │
 *   └──────────────┘         │└────┘└────┘  │
 *   浪费 3968 bytes!         │  高效利用!    │
 *                            └──────────────┘
 *
 * 适用场景:
 *   - USB请求块(URB)
 *   - 网络驱动的描述符
 *   - 需要大量小的DMA缓冲区的场景
 */
static int oled_demo_dma_pool(struct spi_oled_device *oled)
{
    struct dma_pool *pool;
    void *buf1, *buf2, *buf3;
    dma_addr_t dma1, dma2, dma3;

    dev_info(oled->dev, "\n📚 === DMA Pool 演示 ===\n");

    /*
     * 📚 dma_pool_create() - 创建DMA池
     *
     * 参数:
     *   name     - 池名称(用于调试)
     *   dev      - 设备
     *   size     - 每个分配块的大小
     *   align    - 对齐要求(通常为cache line大小或设备要求)
     *   boundary - 不能跨越的边界(0表示无限制)
     *              例如: boundary=4096 表示分配不能跨越4KB边界
     *
     * 内部实现:
     *   1. 用dma_alloc_coherent()分配一大块内存(1页或多页)
     *   2. 将大块内存切分成size大小的小块
     *   3. 用位图或链表管理空闲小块
     */
    pool = dma_pool_create(
        "oled_cmd_pool",        /* 池名称 */
        oled->dev,              /* 设备 */
        64,                     /* 每个块64字节 */
        4,                      /* 4字节对齐 */
        0                       /* 无边界限制 */
    );

    if (!pool) {
        dev_err(oled->dev, "  创建DMA池失败\n");
        return -ENOMEM;
    }

    dev_info(oled->dev, "  DMA池已创建: 块大小=64B, 对齐=4B\n");

    /*
     * 📚 dma_pool_alloc() - 从池中分配
     *
     * 比dma_alloc_coherent()快得多(不需要整页分配)
     * 同样返回虚拟地址和DMA地址
     */
    buf1 = dma_pool_alloc(pool, GFP_KERNEL, &dma1);
    buf2 = dma_pool_alloc(pool, GFP_KERNEL, &dma2);
    buf3 = dma_pool_alloc(pool, GFP_KERNEL, &dma3);

    if (buf1 && buf2 && buf3) {
        dev_info(oled->dev, "  从池中分配了3个缓冲区:\n");
        dev_info(oled->dev, "    buf1: vaddr=%px, dma=%pad\n", buf1, &dma1);
        dev_info(oled->dev, "    buf2: vaddr=%px, dma=%pad\n", buf2, &dma2);
        dev_info(oled->dev, "    buf3: vaddr=%px, dma=%pad\n", buf3, &dma3);

        /*
         * 📚 观察DMA地址的规律:
         * 连续分配的地址通常相差64字节(= 块大小)
         * 说明它们来自同一大块DMA内存的不同偏移
         */
        dev_info(oled->dev, "    地址差: buf2-buf1=%lld, buf3-buf2=%lld\n",
                 (s64)(dma2 - dma1), (s64)(dma3 - dma2));
    }

    /*
     * 📚 dma_pool_free() - 归还到池中
     * 不是真正释放内存,只是标记为可用
     * 下次dma_pool_alloc可以重用
     */
    if (buf3) dma_pool_free(pool, buf3, dma3);
    if (buf2) dma_pool_free(pool, buf2, dma2);
    if (buf1) dma_pool_free(pool, buf1, dma1);

    /*
     * 📚 dma_pool_destroy() - 销毁整个池
     * 释放所有底层的DMA一致性内存
     * 调用前必须归还所有分配的块!
     */
    dma_pool_destroy(pool);

    dev_info(oled->dev, "  DMA池已销毁\n");
    dev_info(oled->dev, "📚 === DMA Pool 演示结束 ===\n\n");

    return 0;
}

/**
 * oled_demo_bounce_buffer - 演示DMA Bounce Buffer概念
 *
 * 📚 Bounce Buffer (弹跳缓冲区) 概念:
 *
 * 问题: 有些内存区域不能直接做DMA
 *   - 栈上的数据 (可能不对齐,可能被换出)
 *   - vmalloc分配的内存 (物理不连续)
 *   - 高端内存 (high memory, 超出设备DMA范围)
 *
 * 解决: 内核自动使用bounce buffer
 *
 *   ┌──────────────┐    ┌──────────────┐    ┌──────┐
 *   │ 不可DMA的内存│─→  │ bounce buffer│─→  │ 设备 │
 *   │ (如high mem) │copy│ (低端内存)   │DMA │      │
 *   └──────────────┘    └──────────────┘    └──────┘
 *
 * 内核会:
 *   1. 在DMA可达区域分配一个临时缓冲区(bounce buffer)
 *   2. 将原始数据复制到bounce buffer
 *   3. 用bounce buffer做DMA传输
 *   4. (如果是读操作) 将结果复制回原始缓冲区
 *
 * 性能影响:
 *   - 额外的内存拷贝 (降低性能)
 *   - 通常内核会静默处理,驱动不需要特别关心
 *   - 但在性能敏感的场景中应该避免
 *
 * 如何避免bounce buffer:
 *   - 使用kmalloc而不是vmalloc
 *   - 不使用栈上的缓冲区
 *   - 设置正确的DMA掩码
 *   - 使用dma_alloc_coherent分配DMA缓冲区
 */
static int oled_demo_bounce_buffer(struct spi_oled_device *oled)
{
    void *good_buf, *vmalloc_buf;
    dma_addr_t good_dma, bad_dma;

    dev_info(oled->dev, "\n📚 === Bounce Buffer 概念演示 ===\n");

    /* 好的做法: 使用kmalloc */
    good_buf = kmalloc(256, GFP_KERNEL | __GFP_DMA);
    if (!good_buf)
        return -ENOMEM;

    good_dma = dma_map_single(oled->dev, good_buf, 256, DMA_TO_DEVICE);
    if (!dma_mapping_error(oled->dev, good_dma)) {
        dev_info(oled->dev, "  ✅ kmalloc缓冲区: 直接DMA映射成功\n");
        dev_info(oled->dev, "     vaddr=%px, dma=%pad\n", good_buf, &good_dma);
        dev_info(oled->dev, "     不需要bounce buffer\n");
        dma_unmap_single(oled->dev, good_dma, 256, DMA_TO_DEVICE);
    }
    kfree(good_buf);

    /*
     * 📚 vmalloc的内存物理不连续,不能直接做DMA
     * 在某些架构上,内核会使用bounce buffer
     * 在另一些架构上,dma_map_single会失败
     */
    vmalloc_buf = vmalloc(256);
    if (vmalloc_buf) {
        dev_info(oled->dev, "  ⚠️ vmalloc缓冲区:\n");
        dev_info(oled->dev, "     vaddr=%px\n", vmalloc_buf);
        dev_info(oled->dev, "     vmalloc内存物理上可能不连续\n");
        dev_info(oled->dev, "     不推荐用于DMA (可能需要bounce buffer)\n");

        /*
         * 注意: 实际上直接dma_map vmalloc内存是不安全的
         * 应该使用 dma_map_page() 逐页映射
         * 或使用 SG表将多个物理页组合
         * 
         * 这里只是概念演示,不做实际映射
         */
        vfree(vmalloc_buf);
    }

    /* 
     * 📚 栈上的缓冲区也不应用于DMA
     */
    dev_info(oled->dev, "  ⚠️ 栈缓冲区:\n");
    dev_info(oled->dev, "     不要使用栈上的变量做DMA!\n");
    dev_info(oled->dev, "     原因: 栈内存可能不对齐,不保证DMA安全\n");
    dev_info(oled->dev, "     示例: u8 buf[128]; dma_map_single(&buf) // 错误!\n");

    dev_info(oled->dev, "\n  📋 DMA内存分配最佳实践:\n");
    dev_info(oled->dev, "     ✅ kmalloc(size, GFP_KERNEL)     - 推荐\n");
    dev_info(oled->dev, "     ✅ dma_alloc_coherent()           - 最佳\n");
    dev_info(oled->dev, "     ✅ dma_pool_alloc()               - 小块最佳\n");
    dev_info(oled->dev, "     ❌ vmalloc()                      - 不要用\n");
    dev_info(oled->dev, "     ❌ 栈变量                          - 不要用\n");
    dev_info(oled->dev, "     ❌ 全局/静态变量                   - 不推荐\n");

    dev_info(oled->dev, "📚 === Bounce Buffer 演示结束 ===\n\n");

    return 0;
}

/**
 * oled_demo_dma_direction - 演示DMA方向的重要性
 *
 * 📚 DMA方向 (DMA Direction) 详解:
 *
 * 为什么方向很重要?
 *   因为Cache一致性操作取决于方向:
 *
 *   DMA_TO_DEVICE (CPU -> 设备, 如OLED写入):
 *     map时:   clean(flush) cache -> 确保内存有最新数据
 *     unmap时: 无需操作 (设备只读,不会修改内存)
 *
 *   DMA_FROM_DEVICE (设备 -> CPU, 如传感器读取):
 *     map时:   invalidate cache -> 确保CPU不用旧缓存
 *     unmap时: invalidate cache -> 确保CPU读到设备写入的新数据
 *
 *   DMA_BIDIRECTIONAL (双向):
 *     map时:   clean + invalidate cache
 *     unmap时: invalidate cache
 *     性能最差,但最安全
 *
 *   ⚠️ 方向错误可能导致:
 *     - 数据损坏(设备读到旧数据)
 *     - 静默错误(在某些架构上看起来正常,换个架构就出bug)
 *     - 安全漏洞(Cache中残留敏感数据)
 */
static void oled_demo_dma_direction(struct spi_oled_device *oled)
{
    dev_info(oled->dev, "\n📚 === DMA方向说明 ===\n");
    dev_info(oled->dev, "  本驱动使用 DMA_TO_DEVICE 方向\n");
    dev_info(oled->dev, "  因为我们只向OLED发送数据(写入像素)\n\n");
    dev_info(oled->dev, "  DMA方向与Cache操作的对应关系:\n");
    dev_info(oled->dev, "  ┌─────────────────┬──────────────┬──────────────┐\n");
    dev_info(oled->dev, "  │     方向         │  map时操作   │ unmap时操作  │\n");
    dev_info(oled->dev, "  ├─────────────────┼──────────────┼──────────────┤\n");
    dev_info(oled->dev, "  │ DMA_TO_DEVICE   │ flush cache  │ (无)         │\n");
    dev_info(oled->dev, "  │ DMA_FROM_DEVICE │ inv cache    │ inv cache    │\n");
    dev_info(oled->dev, "  │ DMA_BIDIRECTIONAL│flush+inv    │ inv cache    │\n");
    dev_info(oled->dev, "  └─────────────────┴──────────────┴──────────────┘\n");
    dev_info(oled->dev, "  flush = 将cache脏数据写回内存\n");
    dev_info(oled->dev, "  inv   = 使cache行无效(下次CPU读取时从内存加载)\n");
    dev_info(oled->dev, "📚 === DMA方向说明结束 ===\n\n");
}

/* ============================================================
 * 第10部分: SPI驱动探测与移除
 * ============================================================
 */

/**
 * spi_oled_probe - SPI设备探测函数
 *
 * 当内核发现匹配的SPI设备时调用
 * 这里进行所有的初始化工作
 */
static int spi_oled_probe(struct spi_device *spi)
{
    struct spi_oled_device *oled;
    int ret;

    dev_info(&spi->dev, "╔══════════════════════════════════════╗\n");
    dev_info(&spi->dev, "║  SPI OLED DMA 教学驱动 - 探测开始   ║\n");
    dev_info(&spi->dev, "╚══════════════════════════════════════╝\n");

    /* 分配设备结构 */
    oled = devm_kzalloc(&spi->dev, sizeof(*oled), GFP_KERNEL);
    if (!oled)
        return -ENOMEM;

    oled->spi = spi;
    oled->dev = &spi->dev;
    oled->dma_mode = dma_mode;  /* 从模块参数获取 */

    mutex_init(&oled->buf_lock);
    spin_lock_init(&oled->stats_lock);
    INIT_DELAYED_WORK(&oled->refresh_work, oled_refresh_work_func);

    spi_set_drvdata(spi, oled);

    /* 配置SPI模式 */
    spi->mode = SPI_MODE_0;
    spi->bits_per_word = 8;
    if (!spi->max_speed_hz)
        spi->max_speed_hz = 10000000;  /* 10MHz */

    ret = spi_setup(spi);
    if (ret) {
        dev_err(&spi->dev, "SPI setup failed: %d\n", ret);
        return ret;
    }

    dev_info(&spi->dev, "SPI配置: mode=%d, bits=%d, speed=%dHz\n",
             spi->mode, spi->bits_per_word, spi->max_speed_hz);

    /* 获取GPIO引脚 */
    oled->dc_gpio = devm_gpiod_get(&spi->dev, "dc", GPIOD_OUT_LOW);
    if (IS_ERR(oled->dc_gpio)) {
        dev_err(&spi->dev, "Failed to get DC GPIO: %ld\n",
                PTR_ERR(oled->dc_gpio));
        return PTR_ERR(oled->dc_gpio);
    }

    oled->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
                                                 GPIOD_OUT_HIGH);
    if (IS_ERR(oled->reset_gpio)) {
        dev_err(&spi->dev, "Failed to get reset GPIO: %ld\n",
                PTR_ERR(oled->reset_gpio));
        return PTR_ERR(oled->reset_gpio);
    }

    /*
     * 📚 核心: 初始化DMA资源
     * 这里会根据选择的模式分配DMA缓冲区
     */
    ret = oled_dma_init(oled);
    if (ret) {
        dev_err(&spi->dev, "DMA initialization failed: %d\n", ret);
        return ret;
    }

    /* 初始化OLED硬件 */
    ret = oled_hw_init(oled);
    if (ret) {
        dev_err(&spi->dev, "OLED hardware init failed: %d\n", ret);
        goto err_dma_cleanup;
    }

    /* 注册Framebuffer */
    ret = oled_fb_register(oled);
    if (ret) {
        dev_err(&spi->dev, "Framebuffer registration failed: %d\n", ret);
        goto err_dma_cleanup;
    }

    /* 运行DMA学习演示 */
    oled_demo_dma_direction(oled);
    oled_demo_dma_pool(oled);
    oled_demo_bounce_buffer(oled);

    /* 启动周期性刷新 */
    oled->refresh_pending = true;
    schedule_delayed_work(&oled->refresh_work,
                         msecs_to_jiffies(refresh_interval));

    dev_info(&spi->dev, "╔══════════════════════════════════════╗\n");
    dev_info(&spi->dev, "║  SPI OLED DMA 教学驱动 - 探测完成   ║\n");
    dev_info(&spi->dev, "║                                      ║\n");
    dev_info(&spi->dev, "║  查看教程: cat dma_tutorial           ║\n");
    dev_info(&spi->dev, "║  查看统计: cat dma_stats              ║\n");
    dev_info(&spi->dev, "║  切换模式: echo N > dma_mode          ║\n");
    dev_info(&spi->dev, "╚══════════════════════════════════════╝\n");

    return 0;

err_dma_cleanup:
    oled_dma_cleanup(oled);
    return ret;
}

/**
 * spi_oled_remove - SPI设备移除函数
 *
 * 📚 资源释放顺序很重要:
 *   与分配/注册相反的顺序释放
 *   先停止活动 -> 注销接口 -> 释放资源
 */
static void spi_oled_remove(struct spi_device *spi)
{
    struct spi_oled_device *oled = spi_get_drvdata(spi);

    dev_info(&spi->dev, "SPI OLED DMA 驱动移除中...\n");

    /* 1. 停止刷新工作 */
    oled->refresh_pending = false;
    cancel_delayed_work_sync(&oled->refresh_work);

    /* 2. 注销framebuffer */
    oled_fb_unregister(oled);

    /* 3. 关闭OLED */
    oled_write_cmd(oled, SSD1306_DISPLAY_OFF);
    oled->powered_on = false;

    /* 4. 清理DMA资源 */
    oled_dma_cleanup(oled);

    dev_info(&spi->dev, "SPI OLED DMA 驱动已移除\n");
}

/**
 * 电源管理
 */
static int __maybe_unused spi_oled_suspend(struct device *dev)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);

    dev_info(dev, "驱动挂起\n");

    oled->refresh_pending = false;
    cancel_delayed_work_sync(&oled->refresh_work);
    oled_write_cmd(oled, SSD1306_DISPLAY_OFF);

    return 0;
}

static int __maybe_unused spi_oled_resume(struct device *dev)
{
    struct spi_oled_device *oled = dev_get_drvdata(dev);

    dev_info(dev, "驱动恢复\n");

    oled_write_cmd(oled, SSD1306_DISPLAY_ON);
    oled->refresh_pending = true;
    schedule_delayed_work(&oled->refresh_work,
                         msecs_to_jiffies(refresh_interval));

    return 0;
}

static SIMPLE_DEV_PM_OPS(spi_oled_pm_ops, spi_oled_suspend, spi_oled_resume);

/* 设备树匹配表 */
static const struct of_device_id spi_oled_of_match[] = {
    { .compatible = "learn,spi-oled-dma" },
    { .compatible = "solomon,ssd1306" },
    { }
};
MODULE_DEVICE_TABLE(of, spi_oled_of_match);

/* SPI设备ID表 */
static const struct spi_device_id spi_oled_ids[] = {
    { "spi-oled-dma", 0 },
    { "ssd1306", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, spi_oled_ids);

/* SPI驱动结构 */
static struct spi_driver spi_oled_driver = {
    .driver = {
        .name           = "spi-oled-dma",
        .of_match_table = spi_oled_of_match,
        .pm             = &spi_oled_pm_ops,
        .dev_groups     = oled_dma_groups,  /* sysfs属性组 */
    },
    .probe    = spi_oled_probe,
    .remove   = spi_oled_remove,
    .id_table = spi_oled_ids,
};

module_spi_driver(spi_oled_driver);

MODULE_AUTHOR("DMA Learning Driver");
MODULE_DESCRIPTION("SPI OLED Driver with DMA tutorial - Learn DMA concepts");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
posted @ 2026-02-26 16:07  21code  阅读(2)  评论(0)    收藏  举报