第一部分 SATA 控制器(AHCI Platform)

第一章 SATA 控制器在 Platform Bus 中的位置

SATA (Serial ATA) 控制器是用于连接硬盘、SSD 等存储设备的接口控制器。在嵌入式 SoC 中,SATA 控制器通常作为 Platform 设备挂载在内部总线上,通过 AHCI (Advanced Host Controller Interface) 标准接口与软件交互。Linux 内核中,通用的 ahci_platform 驱动可覆盖多数 SoC 特定的 SATA 控制器。

Linux 中 SATA 子系统的核心层次:

  1. AHCI 核心层 (drivers/ata/libahci.c, drivers/ata/ahci.c):提供通用的 AHCI 操作、中断处理、DMA 管理和命令执行。

  2. AHCI Platform 驱动(本篇文章重点):基于 ahci_platform 的 SoC 特定驱动(如 ahci_imx.c, ahci_sunxi.c, ahci_tegra.c)。

  3. SCSI 层 (drivers/scsi/):将 ATA 命令转换为 SCSI 命令,供 Linux 块设备层使用。

  4. ATA 传输层 (drivers/ata/libata-core.c):管理 ATA 传输、错误处理、电源管理。


第二章 Linux 5.10 典型 SATA 控制器驱动 —— ahci_sunxi.c

Allwinner 的 SATA 控制器基于 AHCI 标准,通过 ahci_platform 框架实现。以下代码基于 Linux 5.10 drivers/ata/ahci_sunxi.c,展示核心逻辑。

2.1 硬件关键概念

  • AHCI 寄存器:HBA 控制寄存器、端口控制寄存器、命令列表、FIS 区域。

  • SATA PHY:物理层收发器,实现信号调理和速度协商。

  • 时钟:通常需要 ahb_clk(总线时钟)、sata_clk(SATA 时钟)、sata_pmclk(电源管理时钟)。

  • 中断:处理 SATA 设备连接/断开、命令完成、错误等事件。

2.2 核心代码

// 基于 Linux 5.10 drivers/ata/ahci_sunxi.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/ahci_platform.h>
#include <linux/ata.h>
#include <linux/delay.h>
#include <linux/phy/phy.h>
​
/**
 * @brief Allwinner SATA 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 SATA Controller 硬件实例。
 */
struct sunxi_sata {
    struct ahci_host_priv *ahci_hpriv;  /**< AHCI 主机私有结构 */
    struct clk *ahb_clk;                /**< AHB 总线时钟 */
    struct clk *sata_clk;               /**< SATA 核心时钟 */
    struct clk *pm_clk;                 /**< SATA 电源管理时钟 */
    struct phy *phy;                    /**< SATA PHY 设备 */
    struct device *dev;                 /**< 设备指针 */
    u32 version;                        /**< 控制器版本 */
};
​
/**
 * @brief 初始化 SATA PHY。
 * 
 * @param sunxi_sata 指向 sunxi_sata 结构。
 * @return 0 成功,负数错误。
 */
static int sunxi_sata_init_phy(struct sunxi_sata *sunxi_sata)
{
    int ret;
​
    // 1. 初始化 PHY
    ret = phy_init(sunxi_sata->phy);
    if (ret) {
        dev_err(sunxi_sata->dev, "Failed to init PHY\n");
        return ret;
    }
​
    // 2. 上电 PHY
    ret = phy_power_on(sunxi_sata->phy);
    if (ret) {
        dev_err(sunxi_sata->dev, "Failed to power on PHY\n");
        phy_exit(sunxi_sata->phy);
        return ret;
    }
​
    // 3. 等待 PHY 稳定
    msleep(100);
​
    return 0;
}
​
/**
 * @brief 关闭 SATA PHY。
 * 
 * @param sunxi_sata 指向 sunxi_sata 结构。
 */
static void sunxi_sata_deinit_phy(struct sunxi_sata *sunxi_sata)
{
    phy_power_off(sunxi_sata->phy);
    phy_exit(sunxi_sata->phy);
}
​
/**
 * @brief 初始化 SATA 控制器时钟。
 * 
 * @param sunxi_sata 指向 sunxi_sata 结构。
 * @return 0 成功,负数错误。
 */
static int sunxi_sata_init_clocks(struct sunxi_sata *sunxi_sata)
{
    int ret;
​
    // 1. 使能 AHB 总线时钟
    ret = clk_prepare_enable(sunxi_sata->ahb_clk);
    if (ret) {
        dev_err(sunxi_sata->dev, "Failed to enable ahb clock\n");
        return ret;
    }
​
    // 2. 使能 SATA 核心时钟
    ret = clk_prepare_enable(sunxi_sata->sata_clk);
    if (ret) {
        dev_err(sunxi_sata->dev, "Failed to enable sata clock\n");
        clk_disable_unprepare(sunxi_sata->ahb_clk);
        return ret;
    }
​
    // 3. 使能电源管理时钟
    if (sunxi_sata->pm_clk) {
        ret = clk_prepare_enable(sunxi_sata->pm_clk);
        if (ret) {
            dev_err(sunxi_sata->dev, "Failed to enable pm clock\n");
            clk_disable_unprepare(sunxi_sata->sata_clk);
            clk_disable_unprepare(sunxi_sata->ahb_clk);
            return ret;
        }
    }
​
    return 0;
}
​
/**
 * @brief 关闭 SATA 控制器时钟。
 * 
 * @param sunxi_sata 指向 sunxi_sata 结构。
 */
static void sunxi_sata_deinit_clocks(struct sunxi_sata *sunxi_sata)
{
    if (sunxi_sata->pm_clk)
        clk_disable_unprepare(sunxi_sata->pm_clk);
    clk_disable_unprepare(sunxi_sata->sata_clk);
    clk_disable_unprepare(sunxi_sata->ahb_clk);
}
​
/**
 * @brief 读取 AHCI 版本寄存器,识别控制器能力。
 * 
 * @param sunxi_sata 指向 sunxi_sata 结构。
 */
static void sunxi_sata_read_version(struct sunxi_sata *sunxi_sata)
{
    // AHCI HBA 寄存器基址在 ahci_hpriv 中
    void __iomem *mmio = sunxi_sata->ahci_hpriv->mmio;
    sunxi_sata->version = readl(mmio + 0x00);
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 SATA 控制器硬件,注册 AHCI 主机。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int sunxi_sata_probe(struct platform_device *pdev)
{
    struct sunxi_sata *sunxi_sata;
    struct ahci_host_priv *hpriv;
    int ret;
​
    // 1. 分配私有数据结构
    sunxi_sata = devm_kzalloc(&pdev->dev, sizeof(*sunxi_sata), GFP_KERNEL);
    if (!sunxi_sata)
        return -ENOMEM;
    platform_set_drvdata(pdev, sunxi_sata);
    sunxi_sata->dev = &pdev->dev;
​
    // 2. 获取时钟
    sunxi_sata->ahb_clk = devm_clk_get(&pdev->dev, "ahb");
    if (IS_ERR(sunxi_sata->ahb_clk)) {
        dev_err(&pdev->dev, "Failed to get ahb clock\n");
        return PTR_ERR(sunxi_sata->ahb_clk);
    }
    sunxi_sata->sata_clk = devm_clk_get(&pdev->dev, "sata");
    if (IS_ERR(sunxi_sata->sata_clk)) {
        dev_err(&pdev->dev, "Failed to get sata clock\n");
        return PTR_ERR(sunxi_sata->sata_clk);
    }
    sunxi_sata->pm_clk = devm_clk_get_optional(&pdev->dev, "pm");
    if (IS_ERR(sunxi_sata->pm_clk))
        return PTR_ERR(sunxi_sata->pm_clk);
​
    // 3. 获取 PHY
    sunxi_sata->phy = devm_phy_get(&pdev->dev, "sata");
    if (IS_ERR(sunxi_sata->phy)) {
        dev_err(&pdev->dev, "Failed to get sata phy\n");
        return PTR_ERR(sunxi_sata->phy);
    }
​
    // 4. 初始化时钟
    ret = sunxi_sata_init_clocks(sunxi_sata);
    if (ret)
        return ret;
​
    // 5. 初始化 PHY
    ret = sunxi_sata_init_phy(sunxi_sata);
    if (ret)
        goto err_clk;
​
    // 6. 注册 AHCI 平台主机
    hpriv = ahci_platform_get_resources(pdev, AHCI_PLATFORM_GET_RESETS);
    if (IS_ERR(hpriv)) {
        ret = PTR_ERR(hpriv);
        goto err_phy;
    }
    sunxi_sata->ahci_hpriv = hpriv;
​
    // 7. 设置 AHCI 操作
    hpriv->ops = &ahci_platform_ops;
    hpriv->flags |= AHCI_HFLAG_NO_PMP;
    hpriv->platform_data = (void *)sunxi_sata;
​
    // 8. 读取控制器版本
    sunxi_sata_read_version(sunxi_sata);
​
    // 9. 注册 AHCI 主机
    ret = ahci_platform_init_host(pdev, hpriv, &ahci_platform_sht);
    if (ret) {
        dev_err(&pdev->dev, "Failed to init AHCI host\n");
        goto err_phy;
    }
​
    dev_info(&pdev->dev, "Sunxi SATA controller registered (version 0x%x)\n",
             sunxi_sata->version);
    return 0;
​
err_phy:
    sunxi_sata_deinit_phy(sunxi_sata);
err_clk:
    sunxi_sata_deinit_clocks(sunxi_sata);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int sunxi_sata_remove(struct platform_device *pdev)
{
    struct sunxi_sata *sunxi_sata = platform_get_drvdata(pdev);
​
    // 1. 调用 AHCI 平台移除
    ahci_platform_remove(pdev);
​
    // 2. 关闭 PHY
    sunxi_sata_deinit_phy(sunxi_sata);
​
    // 3. 禁用时钟
    sunxi_sata_deinit_clocks(sunxi_sata);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id sunxi_sata_of_match[] = {
    { .compatible = "allwinner,sun8i-h3-ahci" },
    { .compatible = "allwinner,sun8i-a83t-ahci" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_sata_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver sunxi_sata_driver = {
    .probe = sunxi_sata_probe,
    .remove = sunxi_sata_remove,
    .driver = {
        .name = "sunxi-sata",
        .of_match_table = sunxi_sata_of_match,
    },
};
module_platform_driver(sunxi_sata_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sunxi SATA Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 SATA 调试核心难点

3.1 SATA 硬盘无法识别

现象:插入 SATA SSD/HDD 后,dmesg 无 "SATA link up" 消息,lsblk 不显示设备。

原因

  • PHY 初始化失败或信号质量差。

  • 供电不足(SATA 需要 5V 和 12V)。

  • AHCI 寄存器配置错误。

调试方法

  1. 检查 PHY 状态

    # 查看 PHY 寄存器
    devmem2 <phy_base_addr>
    # 检查 PHY 锁定状态
  2. 检查电源

    # 检查 SATA 电压
    cat /sys/kernel/debug/regulator/regulator_summary | grep sata
  3. 强制链路初始化

    # 通过 devmem2 设置 SATA 控制器的链路复位位
    devmem2 <base_addr>+0x20 0x01
  4. 查看 AHCI 状态寄存器

    # 读取端口状态寄存器
    devmem2 <base_addr>+0x118  # PORT_SCR_STATUS

3.2 传输速率远低于预期

现象hdparm -Tt /dev/sda 显示的吞吐量远低于 SATA 理论值(例如 3Gbps 仅达到 100MB/s)。

原因

  • PHY 链路协商到较低速度(如 SATA 1.5Gbps)。

  • 使用 PIO 模式而不是 DMA 模式。

  • 文件系统对齐问题。

调试方法

  1. 查看 SATA 链路速度

    # 查看端口状态
    cat /sys/class/scsi_host/host0/link_power_management_policy
  2. 强制速度

    # 强制 SATA 3.0 速度
    echo "6" > /sys/class/scsi_host/host0/ahci_host/ahci0.0/target
  3. 检查 DMA 配置

    # 检查 AHCI 是否启用 DMA
    dmesg | grep "DMA"

3.3 硬盘读写错误

现象dd if=/dev/sda of=/dev/null 报错 I/O error

原因

  • 物理坏道(硬盘损坏)。

  • 数据线问题。

  • 热插拔导致链路不稳定。

调试方法

  1. 查看 ATA 错误日志

    dmesg | grep "ATA"
    dmesg | grep "I/O error"
  2. 使用 smartctl 检查硬盘

    smartctl -a /dev/sda
  3. 检查 CRC 错误

    cat /sys/block/sda/device/error_count

3.4 热插拔不工作

现象:运行时插入 SATA 硬盘,系统没有检测到新设备。

原因

  • AHCI 热插拔中断未使能。

  • GPIO 检测引脚未配置。

  • 控制器不支持热插拔。

调试方法

  1. 检查 AHCI 热插拔支持

    cat /sys/class/scsi_host/host0/link_power_management_policy
  2. 手动扫描

    echo "- - -" > /sys/class/scsi_host/host0/scan
  3. 检查中断

    cat /proc/interrupts | grep ahci

第四章 结合性能调试场景示例

场景:通过 SATA SSD 拷贝大文件时,速度只有 80MB/s,而 SSD 宣称可达到 500MB/s。

分析流程

  1. 宏观层面(CPU 和 I/O 层):

    • iostat -x 1 显示 sda 设备 %util 高,但 svctm 较小。

    • perf top -G 显示 ahci_interruptata_sff_interrupt 占用 CPU 较高。

  2. SATA 层(Device Drivers -> SATA Controller 层):

    cat /sys/class/scsi_host/host0/ahci_host/ahci0.0/link_speed

    显示当前链路速度为 SATA 1.5 Gbps,而不是 SATA 6 Gbps

  3. 检查 PHY 配置(PHY 层):

    devmem2 <phy_base_addr>+0x04

    发现 PHY 状态寄存器指示协商到 SATA 1.5Gbps。

  4. 根本原因

    • SATA 电缆质量差或长度过长,导致信号质量不足以支持 6Gbps。

    • 电源噪声较大。

    • 固态硬盘与控制器之间的兼容性问题。

  5. 解决方案

    • 更换高质量 SATA 数据线(短距离)。

    • 在 DTS 中强制 SATA 速度为 Gen3(3.0):sata-speed = <3>;

    • 调整 SATA 控制器 PHY 驱动强度。

    • 优化后,速度提升到 280MB/s。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
PHY 物理层信号调理 信号完整性、速度协商、驱动强度
DMA 数据传输搬运 DMA 描述符链、中断聚合
PMIC 提供 SATA 电源(5V/12V) 电源稳定性、上下电时序
GPIO 电源使能或热插拔检测 GPIO 电平配置
时钟 提供 SATA 核心时钟 时钟频率匹配

第二部分 CAN 控制器 (M_CAN)

第一章 CAN 控制器在 Platform Bus 中的位置

CAN (Controller Area Network) 控制器是用于汽车电子和工业控制领域的高可靠性通信接口。在嵌入式 SoC 中,CAN 控制器通常作为 Platform 设备挂载在内部总线上,通过 M_CAN (Bosch) 或 FlexCAN (NXP) 等 IP 核实现。

Linux 中 CAN 子系统的核心层次:

  1. SocketCAN 核心层 (net/can/):提供 SocketCAN 协议接口、CAN 设备管理、网络设备抽象。

  2. CAN 控制器驱动(本篇文章重点):具体的 SoC CAN 控制器驱动(如 m_can.cflexcan.c)。

  3. CAN 硬件接口:CAN 收发器(Transceiver)将控制器信号转换为差分信号。


第二章 Linux 5.10 典型 CAN 控制器驱动 —— m_can.c

M_CAN (Bosch M_CAN) 是最广泛使用的 CAN 控制器 IP 核之一,支持 CAN 2.0 和 CAN FD (Flexible Data-rate),广泛应用于 STM32、TI、Rockchip 等 SoC。以下代码基于 Linux 5.10 drivers/net/can/m_can/m_can.c,展示核心逻辑。

2.1 硬件关键概念

  • M_CAN 核心寄存器:控制、状态、中断、位时序、消息 RAM 等。

  • 消息 RAM:用于存储 TX/RX 消息、过滤条件、FIFO 队列。

  • CAN 时钟:需要输入时钟(cclk)和总线时钟(hclk)。

  • 中断:处理 TX 完成、RX 接收、错误、总线关闭等事件。

  • CAN 收发器:外部芯片,将控制器信号转换为差分信号(通过 GPIO 控制收发器使能)。

2.2 核心代码

// 基于 Linux 5.10 drivers/net/can/m_can/m_can.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/can.h>
#include <linux/can/dev.h>
#include <linux/can/led.h>
#include <linux/can/error.h>
#include <linux/delay.h>
#include <linux/gpio.h>
​
/**
 * @brief M_CAN 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 CAN Controller 硬件实例。
 */
struct m_can_priv {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    void __iomem *msg_ram;           /**< 消息 RAM 基址 */
    int irq;                         /**< CAN 中断号 */
    struct clk *cclk;                /**< CAN 核心时钟 */
    struct clk *hclk;                /**< AHB 总线时钟 */
    struct net_device *ndev;         /**< 网络设备抽象 */
    struct can_priv can;             /**< CAN 私有数据 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    u32 version;                     /**< 控制器版本 (M_CAN 1/2/3) */
    u32 rx_fifo_len;                 /**< RX FIFO 长度 */
    u32 tx_fifo_len;                 /**< TX FIFO 长度 */
    struct m_can_rx_msg *rx_msg;     /**< 接收消息缓冲区 */
    u32 rx_fifo_head;                /**< RX FIFO 头索引 */
    u32 rx_fifo_tail;                /**< RX FIFO 尾索引 */
    int gpio_transceiver;            /**< 收发器使能 GPIO */
};
​
/* M_CAN 寄存器偏移 (核心) */
#define M_CAN_CORE_CTRL          0x00
#define M_CAN_CORE_STATUS        0x04
#define M_CAN_CORE_CCCR          0x08
#define M_CAN_CORE_BTP           0x0C
#define M_CAN_CORE_TSCC          0x10
#define M_CAN_CORE_RWD           0x14
#define M_CAN_CORE_IR            0x20
#define M_CAN_CORE_IE            0x24
#define M_CAN_CORE_ILS           0x28
#define M_CAN_CORE_ILE           0x2C
#define M_CAN_CORE_TEST          0x30
#define M_CAN_CORE_CFC           0x34
#define M_CAN_CORE_CRCC          0x38
#define M_CAN_CORE_ECR           0x3C
#define M_CAN_CORE_PSR           0x40
#define M_CAN_CORE_TDCR          0x44
​
/* 消息 RAM 布局 (软件定义) */
#define M_CAN_RX_FIFO_BASE       0x000
#define M_CAN_TX_FIFO_BASE       0x200
#define M_CAN_RX_FILTER_BASE     0x400
​
/* 消息结构 (32 字节) */
struct m_can_rx_msg {
    u32 flags;
    u32 id;
    u32 timestamp;
    u32 data[4];
};
​
/**
 * @brief 初始化 M_CAN 硬件配置。
 * 
 * @param m_can 指向 m_can_priv 结构。
 */
static void m_can_hw_init(struct m_can_priv *m_can)
{
    u32 cccr, btpr;
​
    // 1. 复位 M_CAN
    cccr = readl(m_can->base + M_CAN_CORE_CCCR);
    cccr |= (1 << 0);   // 初始化请求 (INIT)
    writel(cccr, m_can->base + M_CAN_CORE_CCCR);
    while (!(readl(m_can->base + M_CAN_CORE_CCCR) & (1 << 1)))
        ; // 等待 INIT 完成
​
    // 2. 配置位时序 (基于时钟频率和波特率)
    // 假设 500kbps,8MHz 时钟
    btpr = (1 << 24) | (2 << 16) | (4 << 8) | 2; // 时钟分频、相位段、同步跳转
    writel(btpr, m_can->base + M_CAN_CORE_BTP);
​
    // 3. 配置 TX 和 RX FIFO 长度 (在消息 RAM 中)
    u32 fcan = (m_can->tx_fifo_len << 16) | (m_can->rx_fifo_len << 0);
    writel(fcan, m_can->base + M_CAN_CORE_CFC);
​
    // 4. 退出初始化模式
    cccr &= ~(1 << 0);
    writel(cccr, m_can->base + M_CAN_CORE_CCCR);
    while (readl(m_can->base + M_CAN_CORE_CCCR) & (1 << 1))
        ;
}
​
/**
 * @brief 从 RX FIFO 读取消息。
 * 
 * @param m_can 指向 m_can_priv 结构。
 * @param rx_msg 指向接收消息缓冲区。
 */
static int m_can_read_rx_fifo(struct m_can_priv *m_can)
{
    struct m_can_rx_msg *rx_msg = m_can->rx_msg;
    u32 rx_index = m_can->rx_fifo_head % m_can->rx_fifo_len;
​
    // 从消息 RAM 复制数据
    void __iomem *rx_base = m_can->msg_ram + M_CAN_RX_FIFO_BASE + 
                            rx_index * sizeof(struct m_can_rx_msg);
    
    // 读取标志、ID、时间戳、数据
    rx_msg->flags = readl(rx_base + 0);
    rx_msg->id = readl(rx_base + 4);
    rx_msg->timestamp = readl(rx_base + 8);
    for (int i = 0; i < 4; i++) {
        rx_msg->data[i] = readl(rx_base + 12 + i * 4);
    }
​
    m_can->rx_fifo_head++;
    
    // 检查是否为扩展 ID
    if (rx_msg->flags & (1 << 18)) {
        // 扩展 ID (29 位)
    } else {
        // 标准 ID (11 位)
    }
​
    return 0;
}
​
/**
 * @brief 发送 CAN 消息。
 * 
 * @param m_can 指向 m_can_priv 结构。
 * @param msg 指向 can_frame。
 * @return 0 成功。
 */
static int m_can_tx_message(struct m_can_priv *m_can, struct can_frame *msg)
{
    struct m_can_tx_msg tx_msg;
    u32 tx_index = m_can->tx_fifo_head % m_can->tx_fifo_len;
    void __iomem *tx_base = m_can->msg_ram + M_CAN_TX_FIFO_BASE + 
                            tx_index * sizeof(struct m_can_tx_msg);
    u32 flags = 0;
​
    // 1. 准备 TX 消息
    memset(&tx_msg, 0, sizeof(tx_msg));
    
    // 设置 ID
    if (msg->can_id & CAN_EFF_FLAG) {
        tx_msg.id = msg->can_id & CAN_EFF_MASK;
        flags |= (1 << 18);   // 扩展 ID
    } else {
        tx_msg.id = msg->can_id & CAN_SFF_MASK;
        flags |= (1 << 17);   // 标准 ID
    }
    
    // 设置数据长度
    flags |= (msg->len << 8);
    
    // 设置数据
    for (int i = 0; i < msg->len; i++) {
        tx_msg.data[i] = msg->data[i];
    }
    
    tx_msg.flags = flags;
​
    // 2. 写入消息 RAM
    writel(tx_msg.flags, tx_base + 0);
    writel(tx_msg.id, tx_base + 4);
    writel(tx_msg.timestamp, tx_base + 8);
    for (int i = 0; i < 4; i++) {
        writel(tx_msg.data[i], tx_base + 12 + i * 4);
    }
​
    // 3. 设置 TX 请求
    u32 txbar = 1 << tx_index;
    writel(txbar, m_can->base + M_CAN_CORE_TXBAR);
    
    m_can->tx_fifo_head++;
    
    return 0;
}
​
/**
 * @brief CAN 中断处理函数。
 * 
 * 处理 RX 接收、TX 完成、错误等中断事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 m_can_priv 结构。
 */
static irqreturn_t m_can_irq_handler(int irq, void *dev_id)
{
    struct m_can_priv *m_can = dev_id;
    u32 irq_stat;
​
    // 1. 读取中断状态
    irq_stat = readl(m_can->base + M_CAN_CORE_IR);
​
    // 2. 处理 RX FIFO 中断 (收到新消息)
    if (irq_stat & (1 << 12)) {  // RX FIFO 有新消息
        writel(1 << 12, m_can->base + M_CAN_CORE_IR); // 清除中断
        m_can_read_rx_fifo(m_can);
        // 将消息传到 SocketCAN 上层
        struct sk_buff *skb = alloc_can_skb(m_can->ndev, NULL);
        if (skb) {
            // 填充 skb
            can_put_skb(skb, m_can->ndev);
        }
    }
​
    // 3. 处理 TX 完成中断
    if (irq_stat & (1 << 8)) {  // TX FIFO 为空
        writel(1 << 8, m_can->base + M_CAN_CORE_IR);
        // 通知上层 TX 完成
        netif_wake_queue(m_can->ndev);
    }
​
    // 4. 处理错误中断
    if (irq_stat & (1 << 3)) {  // 警告
        u32 ecr = readl(m_can->base + M_CAN_CORE_ECR);
        dev_warn(m_can->dev, "CAN warning: error count %d\n", ecr & 0xFFFF);
        writel(1 << 3, m_can->base + M_CAN_CORE_IR);
    }
    
    if (irq_stat & (1 << 5)) {  // 总线关闭
        dev_err(m_can->dev, "CAN bus off detected\n");
        writel(1 << 5, m_can->base + M_CAN_CORE_IR);
        // 需要触发总线恢复逻辑
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief CAN 网络设备操作:启动 (ifconfig up)。
 * 
 * @param ndev 指向 net_device 结构。
 * @return 0 成功。
 */
static int m_can_open(struct net_device *ndev)
{
    struct m_can_priv *m_can = netdev_priv(ndev);
    int ret;
​
    // 1. 启用硬件
    m_can_hw_init(m_can);
​
    // 2. 注册中断
    ret = request_irq(m_can->irq, m_can_irq_handler, 0, "m_can", m_can);
    if (ret) {
        dev_err(m_can->dev, "Failed to request IRQ\n");
        return ret;
    }
​
    // 3. 启用收发器
    if (gpio_is_valid(m_can->gpio_transceiver))
        gpio_set_value(m_can->gpio_transceiver, 1);
​
    // 4. 启用 CAN 中断
    writel(0x1FFF, m_can->base + M_CAN_CORE_IE); // 使能所有中断
​
    netif_start_queue(ndev);
​
    return 0;
}
​
/**
 * @brief CAN 网络设备操作:停止 (ifconfig down)。
 * 
 * @param ndev 指向 net_device 结构。
 * @return 0 成功。
 */
static int m_can_close(struct net_device *ndev)
{
    struct m_can_priv *m_can = netdev_priv(ndev);
​
    // 1. 停止队列
    netif_stop_queue(ndev);
​
    // 2. 禁用中断
    writel(0, m_can->base + M_CAN_CORE_IE);
​
    // 3. 禁用收发器
    if (gpio_is_valid(m_can->gpio_transceiver))
        gpio_set_value(m_can->gpio_transceiver, 0);
​
    // 4. 释放中断
    free_irq(m_can->irq, m_can);
​
    return 0;
}
​
/**
 * @brief CAN 网络设备操作:发送消息。
 * 
 * @param skb 指向 sk_buff。
 * @param ndev 指向 net_device。
 * @return 0 成功。
 */
static int m_can_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
    struct m_can_priv *m_can = netdev_priv(ndev);
    struct can_frame *frame = (struct can_frame *)skb->data;
    int ret;
​
    // 1. 检查 TX FIFO 是否满
    if ((m_can->tx_fifo_head - m_can->tx_fifo_tail) >= m_can->tx_fifo_len) {
        netif_stop_queue(ndev);
        return NETDEV_TX_BUSY;
    }
​
    // 2. 发送消息
    ret = m_can_tx_message(m_can, frame);
    if (ret) {
        dev_err(m_can->dev, "Failed to send CAN message\n");
        return NETDEV_TX_BUSY;
    }
​
    // 3. 释放 skb
    netdev_sent_queue(ndev, 1);
    dev_kfree_skb(skb);
​
    return NETDEV_TX_OK;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int m_can_probe(struct platform_device *pdev)
{
    struct m_can_priv *m_can;
    struct net_device *ndev;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配 CAN 网络设备
    ndev = alloc_candev(sizeof(struct m_can_priv), 32);
    if (!ndev) {
        dev_err(&pdev->dev, "Failed to allocate CAN device\n");
        return -ENOMEM;
    }
    m_can = netdev_priv(ndev);
    m_can->ndev = ndev;
    platform_set_drvdata(pdev, m_can);
    m_can->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源 (核心寄存器)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        ret = -ENXIO;
        goto err_free;
    }
    m_can->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(m_can->base)) {
        ret = PTR_ERR(m_can->base);
        goto err_free;
    }
​
    // 3. 获取消息 RAM (可选,某些 SoC 映射到独立区域)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res) {
        m_can->msg_ram = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(m_can->msg_ram)) {
            ret = PTR_ERR(m_can->msg_ram);
            goto err_free;
        }
    } else {
        // 如果硬件不支持独立消息 RAM,则使用映射在核心寄存器区域的内存
        m_can->msg_ram = m_can->base + 0x1000;
    }
​
    // 4. 获取时钟
    m_can->cclk = devm_clk_get(&pdev->dev, "cclk");
    if (IS_ERR(m_can->cclk)) {
        dev_err(&pdev->dev, "Failed to get cclk\n");
        ret = PTR_ERR(m_can->cclk);
        goto err_free;
    }
    m_can->hclk = devm_clk_get_optional(&pdev->dev, "hclk");
    if (IS_ERR(m_can->hclk)) {
        ret = PTR_ERR(m_can->hclk);
        goto err_free;
    }
    clk_prepare_enable(m_can->cclk);
    if (m_can->hclk)
        clk_prepare_enable(m_can->hclk);
​
    // 5. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    m_can->irq = irq;
​
    // 6. 获取收发器控制 GPIO
    m_can->gpio_transceiver = of_get_named_gpio(pdev->dev.of_node, 
                                                "transceiver-gpios", 0);
    if (gpio_is_valid(m_can->gpio_transceiver)) {
        ret = devm_gpio_request_one(&pdev->dev, m_can->gpio_transceiver,
                                    GPIOF_OUT_INIT_LOW, "can-transceiver");
        if (ret) {
            dev_err(&pdev->dev, "Failed to request transceiver GPIO\n");
            goto err_clk;
        }
    }
​
    // 7. 设置 CAN 设备参数
    m_can->can.bittiming_const = &m_can_bittiming_const;
    m_can->can.do_set_mode = m_can_do_set_mode;
    m_can->rx_fifo_len = 16;
    m_can->tx_fifo_len = 16;
    spin_lock_init(&m_can->lock);
​
    // 8. 设置网络设备操作
    ndev->name = "can0";
    ndev->type = ARPHRD_CAN;
    ndev->ops = &m_can_netdev_ops;
    ndev->dev.parent = &pdev->dev;
​
    // 9. 注册 CAN 设备
    ret = register_candev(ndev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register CAN device\n");
        goto err_gpio;
    }
​
    dev_info(&pdev->dev, "M_CAN controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, m_can->irq);
    return 0;
​
err_gpio:
    if (gpio_is_valid(m_can->gpio_transceiver))
        gpio_free(m_can->gpio_transceiver);
err_clk:
    if (m_can->hclk)
        clk_disable_unprepare(m_can->hclk);
    clk_disable_unprepare(m_can->cclk);
err_free:
    free_candev(ndev);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int m_can_remove(struct platform_device *pdev)
{
    struct m_can_priv *m_can = platform_get_drvdata(pdev);
​
    // 1. 注销 CAN 设备
    unregister_candev(m_can->ndev);
​
    // 2. 禁用收发器
    if (gpio_is_valid(m_can->gpio_transceiver))
        gpio_free(m_can->gpio_transceiver);
​
    // 3. 禁用时钟
    if (m_can->hclk)
        clk_disable_unprepare(m_can->hclk);
    clk_disable_unprepare(m_can->cclk);
​
    // 4. 释放网络设备
    free_candev(m_can->ndev);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id m_can_of_match[] = {
    { .compatible = "bosch,m_can" },
    { .compatible = "snps,dw-m-can" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, m_can_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver m_can_driver = {
    .probe = m_can_probe,
    .remove = m_can_remove,
    .driver = {
        .name = "m_can",
        .of_match_table = m_can_of_match,
    },
};
module_platform_driver(m_can_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("M_CAN Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 CAN 调试核心难点

3.1 CAN 总线无法通信

现象candump can0 无任何数据,cansend can0 123#DEADBEEF 无响应。

原因

  • CAN 收发器未启用(GPIO 或电源问题)。

  • 波特率不匹配(终端电阻未连接)。

  • 总线短路。

调试方法

  1. 检查收发器状态

    # 查看 GPIO 电平
    cat /sys/kernel/debug/gpio | grep transceiver
  2. 检查波特率

    ip link set can0 type can bitrate 500000
  3. 使用 cansend 测试

    # 测试环回模式
    ip link set can0 type can bitrate 500000 loopback on
    cansend can0 123#DEADBEEF
  4. 检查总线状态

    ip -d link show can0

3.2 错误帧频繁出现

现象candump can0 显示大量错误帧,netstat -i 显示 tx errorsrx errors 不断增加。

原因

  • 终端电阻缺失或错误(正确 CAN 总线需要 120Ω 终端电阻)。

  • 电源噪声。

  • 总线长度过长或分支过多。

调试方法

  1. 检查终端电阻:用万用表测量 CAN_H 和 CAN_L 之间的阻抗(应为 60Ω)。

  2. 降低波特率:从 500kbps 降低到 125kbps 测试。

  3. 使用可调终端电阻

  4. 查看错误计数器

    cat /sys/class/net/can0/error_count

3.3 总线关闭(Bus Off)

现象:CAN 设备进入 Bus Off 状态,无法恢复。

原因

  • 错误计数器超过 255。

  • 总线严重故障。

调试方法

  1. 手动恢复

    ip link set can0 down
    ip link set can0 up
  2. 查看错误计数器

    devmem2 <base_addr>+0x3C  # 读取 ECR 寄存器
  3. 启用自动恢复

    echo "autorecover" > /sys/class/net/can0/restart

第四章 结合性能调试场景示例

场景:使用 cangen 在 CAN 总线上发送 1000 帧/秒,但实际接收到的帧远少于发送的帧。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • top 显示 irq/m_can 占用 CPU 约 5%。

    • perf top -G 显示 m_can_irq_handler 占用少量 CPU。

  2. CAN 层(图谱的 Device Drivers -> CAN Controller 层):

    ip -s link show can0

    显示 tx bytes 远大于 rx bytes

  3. 检查 TX FIFO

    devmem2 <base_addr>+0x34  # 读取 CFC 寄存器

    发现 TX FIFO 长度为 16,不足以容纳高帧率。

  4. 根本原因

    • TX FIFO 深度过小(16),发送高帧率时溢出。

    • 中断处理开销导致部分帧丢弃。

  5. 解决方案

    • 在驱动中将 TX FIFO 深度扩展到 64。

    • 使用 TX FIFO 中断聚合,减少中断频率。

    • 使用 DMA 传输(如果硬件支持)。

    • 优化后,帧接收率提升到 900 帧/秒。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
GPIO 控制收发器使能、热插拔检测 GPIO 电平配置
PMIC 提供 CAN 收发器电源 电压稳定,噪声抑制
Timer 用于 CAN 时钟分频 时钟频率匹配
PINCTRL CAN 引脚配置 引脚复用不冲突

第三部分:HDMI 控制器 (DesignWare HDMI)

第一章 HDMI 控制器在 Platform Bus 中的位置

HDMI 控制器是 SoC 中负责将视频和音频数据编码为 HDMI 信号的硬件模块。它通常作为 Platform 设备挂载在内部总线上,通过 HDMI PHY 输出到外部显示设备。HDMI 控制器集成了视频数据包、音频数据包、辅助数据包(如 EDID、CEC)的打包和传输功能。

在 Linux 中,HDMI 子系统的核心层次:

  1. DRM 核心层 (drivers/gpu/drm/):为显示设备提供标准抽象。

  2. HDMI 桥接器驱动 (drivers/gpu/drm/bridge/):通过 DRM 桥接器框架提供 HDMI 输出。

  3. HDMI 控制器驱动(本篇文章重点):具体的 SoC HDMI 控制器(如 dw-hdmi.csynopsys)。

  4. HDMI PHY 驱动:负责物理层信号调理和链路管理。


第二章 Linux 5.10 典型 HDMI 控制器驱动 —— dw-hdmi.c

DesignWare HDMI 控制器是应用最广泛的 HDMI IP 核之一,广泛用于 Rockchip、Allwinner、ST 等 SoC。以下代码基于 Linux 5.10 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c,展示核心逻辑。

2.1 硬件关键概念

  • HDMI 核心寄存器:视频时序配置、数据包打包、音频采样控制、辅助数据包管理。

  • HDMI PHY:物理层收发器,负责差分信号输出。

  • 视频配置:分辨率、刷新率、色深、色彩格式(RGB/YUV)。

  • 音频配置:采样率、通道数、格式(PCM/DSD)。

  • CEC:消费电子控制总线。

  • HDCP:高清内容保护。

  • EDID:扩展显示识别数据,通过 I2C 读取显示器信息。

2.2 核心代码

// 基于 Linux 5.10 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_modes.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_edid.h>
#include <sound/hdmi-codec.h>
​
/**
 * @brief DesignWare HDMI 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 HDMI Controller 硬件实例。
 */
struct dw_hdmi {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    void __iomem *phy_base;          /**< PHY 寄存器基址 */
    int irq;                         /**< 中断号 */
    struct clk *clk;                 /**< 核心时钟 */
    struct clk *ahb_clk;             /**< AHB 总线时钟 */
    struct clk *phy_clk;             /**< PHY 时钟 */
    struct device *dev;              /**< 设备指针 */
    struct drm_bridge bridge;        /**< DRM 桥接器抽象 */
    struct drm_connector connector;  /**< DRM 连接器抽象 */
    struct drm_encoder encoder;      /**< DRM 编码器抽象 */
    struct edid *edid;               /**< EDID 数据 */
    struct i2c_adapter *ddc;         /**< DDC (I2C) 适配器用于读取 EDID */
    u32 video_mode;                  /**< 视频模式 (VIC) */
    u32 phy_bus_width;               /**< PHY 总线宽度 (RGB/YUV) */
    u32 color_depth;                 /**< 色深 (8/10/12 bit) */
    u32 hdcp_enabled;                /**< HDCP 是否启用 */
    spinlock_t lock;                 /**< 硬件保护锁 */
};
​
/* 寄存器偏移量 (核心) */
#define HDMI_VIDEO_MODE             0x00
#define HDMI_VIDEO_SYNC             0x04
#define HDMI_VIDEO_HTIMING          0x08
#define HDMI_VIDEO_VTIMING          0x0C
#define HDMI_VIDEO_POLARITY         0x10
#define HDMI_VIDEO_COLOR            0x14
#define HDMI_VIDEO_FORMAT           0x18
#define HDMI_AUDIO_SAMPLE_RATE      0x1C
#define HDMI_AUDIO_CHANNEL          0x20
#define HDMI_AUDIO_FORMAT           0x24
#define HDMI_CEC_CTRL               0x28
#define HDMI_CEC_DATA               0x2C
#define HDMI_HDCP_CTRL              0x30
#define HDMI_HDCP_STATUS            0x34
#define HDMI_EDID_ADDR              0x38
#define HDMI_EDID_DATA              0x3C
#define HDMI_CONNECTOR_STATUS       0x40
​
/* PHY 寄存器偏移量 */
#define HDMI_PHY_STATUS             0x00
#define HDMI_PHY_CONFIG             0x04
#define HDMI_PHY_TMDS_CTRL          0x08
#define HDMI_PHY_TMDS_STATUS        0x0C
​
/**
 * @brief 读取 EDID 数据。
 * 
 * @param hdmi 指向 dw_hdmi 结构。
 * @return EDID 数据指针。
 */
static struct edid *dw_hdmi_read_edid(struct dw_hdmi *hdmi)
{
    // 1. 通过 DDC 适配器读取显示器 EDID
    if (!hdmi->ddc)
        return NULL;
​
    // 2. 读取 EDID 数据
    hdmi->edid = drm_get_edid(&hdmi->connector, hdmi->ddc);
    if (!hdmi->edid) {
        dev_err(hdmi->dev, "Failed to read EDID\n");
        return NULL;
    }
​
    return hdmi->edid;
}
​
/**
 * @brief 配置 HDMI 视频时序。
 * 
 * @param hdmi 指向 dw_hdmi 结构。
 * @param mode 指向 drm_display_mode。
 */
static void dw_hdmi_set_video_mode(struct dw_hdmi *hdmi,
                                   struct drm_display_mode *mode)
{
    u32 htiming, vtiming, polarity;
​
    // 1. 配置水平时序
    htiming = (mode->htotal << 16) | mode->hdisplay;
    writel(htiming, hdmi->base + HDMI_VIDEO_HTIMING);
​
    // 2. 配置垂直时序
    vtiming = (mode->vtotal << 16) | mode->vdisplay;
    writel(vtiming, hdmi->base + HDMI_VIDEO_VTIMING);
​
    // 3. 配置极性
    polarity = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : (1 << 0);
    polarity |= (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : (1 << 1);
    writel(polarity, hdmi->base + HDMI_VIDEO_POLARITY);
​
    // 4. 配置色彩模式
    // 假设 RGB 模式
    writel(0x1, hdmi->base + HDMI_VIDEO_COLOR);
}
​
/**
 * @brief 配置 HDMI 音频格式。
 * 
 * @param hdmi 指向 dw_hdmi 结构。
 * @param sample_rate 采样率 (44100/48000/96000)。
 * @param channels 通道数 (2/6/8)。
 */
static void dw_hdmi_set_audio_format(struct dw_hdmi *hdmi,
                                     u32 sample_rate, u32 channels)
{
    u32 format;
​
    // 1. 配置采样率
    switch (sample_rate) {
    case 44100:
        format = 0x0;
        break;
    case 48000:
        format = 0x1;
        break;
    case 96000:
        format = 0x2;
        break;
    default:
        format = 0x1;
        break;
    }
    writel(format, hdmi->base + HDMI_AUDIO_SAMPLE_RATE);
​
    // 2. 配置通道数
    if (channels == 6) {
        writel(0x1, hdmi->base + HDMI_AUDIO_CHANNEL);
    } else if (channels == 8) {
        writel(0x2, hdmi->base + HDMI_AUDIO_CHANNEL);
    } else {
        writel(0x0, hdmi->base + HDMI_AUDIO_CHANNEL);
    }
​
    // 3. 配置格式 (PCM)
    writel(0x0, hdmi->base + HDMI_AUDIO_FORMAT);
}
​
/**
 * @brief 初始化 HDMI PHY。
 * 
 * @param hdmi 指向 dw_hdmi 结构。
 * @return 0 成功。
 */
static int dw_hdmi_phy_init(struct dw_hdmi *hdmi)
{
    u32 status;
​
    // 1. 上电 PHY
    writel(0x1, hdmi->phy_base + HDMI_PHY_CONFIG);
​
    // 2. 等待 PHY 稳定
    msleep(50);
​
    // 3. 检查 PHY 状态
    status = readl(hdmi->phy_base + HDMI_PHY_STATUS);
    if (!(status & (1 << 0))) {
        dev_err(hdmi->dev, "PHY not ready\n");
        return -EIO;
    }
​
    // 4. 配置 TMDS 驱动 (根据 PHY 类型)
    writel(0x3F, hdmi->phy_base + HDMI_PHY_TMDS_CTRL);
​
    return 0;
}
​
/**
 * @brief 关闭 HDMI PHY。
 * 
 * @param hdmi 指向 dw_hdmi 结构。
 */
static void dw_hdmi_phy_deinit(struct dw_hdmi *hdmi)
{
    writel(0x0, hdmi->phy_base + HDMI_PHY_CONFIG);
}
​
/**
 * @brief 启用 HDMI 输出。
 * 
 * @param hdmi 指向 dw_hdmi 结构。
 * @param enable 启用/禁用。
 */
static void dw_hdmi_enable(struct dw_hdmi *hdmi, bool enable)
{
    u32 ctrl;
​
    ctrl = readl(hdmi->base + HDMI_VIDEO_MODE);
    if (enable) {
        ctrl |= (1 << 0);   // 启用视频输出
        ctrl |= (1 << 1);   // 启用音频输出
    } else {
        ctrl &= ~(1 << 0);
        ctrl &= ~(1 << 1);
    }
    writel(ctrl, hdmi->base + HDMI_VIDEO_MODE);
}
​
/**
 * @brief HDMI 中断处理函数。
 * 
 * 处理 CEC、HDCP、连接状态变化等。
 *
 * @param irq 中断号。
 * @param dev_id 指向 dw_hdmi 结构。
 */
static irqreturn_t dw_hdmi_irq_handler(int irq, void *dev_id)
{
    struct dw_hdmi *hdmi = dev_id;
    u32 status = readl(hdmi->base + HDMI_CONNECTOR_STATUS);
​
    // 1. 连接状态变化中断
    if (status & (1 << 0)) {
        // 清除中断
        writel(1 << 0, hdmi->base + HDMI_CONNECTOR_STATUS);
        // 通知 DRM 连接状态变化
        drm_helper_hpd_irq_event(hdmi->bridge.dev);
    }
​
    // 2. CEC 中断
    if (status & (1 << 1)) {
        // 处理 CEC 消息
        writel(1 << 1, hdmi->base + HDMI_CONNECTOR_STATUS);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 检查 HDMI 连接器状态。
 * 
 * @param connector 指向 drm_connector。
 * @return 连接状态。
 */
static enum drm_connector_status
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
    struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
    u32 status;
​
    // 读取连接状态寄存器
    status = readl(hdmi->base + HDMI_CONNECTOR_STATUS);
    if (status & (1 << 2)) {
        return connector_status_connected;
    } else {
        return connector_status_disconnected;
    }
}
​
/**
 * @brief 读取 EDID。
 * 
 * @param connector 指向 drm_connector。
 * @return EDID 数据。
 */
static struct edid *dw_hdmi_connector_get_edid(struct drm_connector *connector,
                                               struct i2c_adapter *adapter)
{
    struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi, connector);
​
    return dw_hdmi_read_edid(hdmi);
}
​
/**
 * @brief 设置 HDMI 视频模式。
 * 
 * @param bridge 指向 drm_bridge。
 * @param mode 指向 drm_display_mode。
 */
static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
                                    const struct drm_display_mode *mode,
                                    const struct drm_display_mode *adj_mode)
{
    struct dw_hdmi *hdmi = bridge_to_dw_hdmi(bridge);
​
    // 1. 配置视频时序
    dw_hdmi_set_video_mode(hdmi, (struct drm_display_mode *)mode);
​
    // 2. 配置音频格式
    dw_hdmi_set_audio_format(hdmi, 48000, 2);
​
    // 3. 配置色彩空间
    // (假定 RGB)
}
​
/**
 * @brief 启用 HDMI 桥接器。
 * 
 * @param bridge 指向 drm_bridge。
 */
static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
{
    struct dw_hdmi *hdmi = bridge_to_dw_hdmi(bridge);
​
    // 1. 初始化 PHY
    if (dw_hdmi_phy_init(hdmi)) {
        dev_err(hdmi->dev, "Failed to init PHY\n");
        return;
    }
​
    // 2. 启用 HDMI 输出
    dw_hdmi_enable(hdmi, true);
}
​
/**
 * @brief 禁用 HDMI 桥接器。
 * 
 * @param bridge 指向 drm_bridge。
 */
static void dw_hdmi_bridge_disable(struct drm_bridge *bridge)
{
    struct dw_hdmi *hdmi = bridge_to_dw_hdmi(bridge);
​
    // 1. 禁用 HDMI 输出
    dw_hdmi_enable(hdmi, false);
​
    // 2. 关闭 PHY
    dw_hdmi_phy_deinit(hdmi);
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 HDMI 控制器硬件,注册到 DRM。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dw_hdmi_probe(struct platform_device *pdev)
{
    struct dw_hdmi *hdmi;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
    if (!hdmi)
        return -ENOMEM;
    platform_set_drvdata(pdev, hdmi);
    hdmi->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源 (核心寄存器)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource for core\n");
        return -ENXIO;
    }
    hdmi->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(hdmi->base))
        return PTR_ERR(hdmi->base);
​
    // 3. 获取 PHY 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res) {
        hdmi->phy_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(hdmi->phy_base))
            return PTR_ERR(hdmi->phy_base);
    } else {
        // 如果 PHY 与核心共享同一地址空间
        hdmi->phy_base = hdmi->base + 0x1000;
    }
​
    // 4. 获取时钟
    hdmi->clk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(hdmi->clk))
        return PTR_ERR(hdmi->clk);
    hdmi->ahb_clk = devm_clk_get(&pdev->dev, "ahb_clk");
    if (IS_ERR(hdmi->ahb_clk))
        hdmi->ahb_clk = NULL;
    hdmi->phy_clk = devm_clk_get(&pdev->dev, "phy_clk");
    if (IS_ERR(hdmi->phy_clk))
        hdmi->phy_clk = NULL;
​
    // 5. 使能时钟
    clk_prepare_enable(hdmi->clk);
    if (hdmi->ahb_clk)
        clk_prepare_enable(hdmi->ahb_clk);
    if (hdmi->phy_clk)
        clk_prepare_enable(hdmi->phy_clk);
​
    // 6. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    hdmi->irq = irq;
​
    // 7. 获取 I2C 适配器 (DDC)
    hdmi->ddc = i2c_get_adapter(0);  // 简化,实际需要从 DTS 获取
    if (!hdmi->ddc) {
        dev_err(&pdev->dev, "Failed to get DDC adapter\n");
        ret = -ENODEV;
        goto err_clk;
    }
​
    spin_lock_init(&hdmi->lock);
​
    // 8. 注册中断
    ret = devm_request_irq(&pdev->dev, hdmi->irq, dw_hdmi_irq_handler,
                           IRQF_SHARED, "dw-hdmi", hdmi);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_i2c;
    }
​
    // 9. 注册到 DRM
    // (省略 DRM 初始化代码,实际需要根据具体 SoC 调整)
​
    dev_info(&pdev->dev, "DesignWare HDMI controller registered\n");
    return 0;
​
err_i2c:
    i2c_put_adapter(hdmi->ddc);
err_clk:
    if (hdmi->phy_clk)
        clk_disable_unprepare(hdmi->phy_clk);
    if (hdmi->ahb_clk)
        clk_disable_unprepare(hdmi->ahb_clk);
    clk_disable_unprepare(hdmi->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int dw_hdmi_remove(struct platform_device *pdev)
{
    struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
​
    // 1. 关闭 HDMI
    dw_hdmi_bridge_disable(&hdmi->bridge);
​
    // 2. 释放 I2C
    i2c_put_adapter(hdmi->ddc);
​
    // 3. 禁用时钟
    if (hdmi->phy_clk)
        clk_disable_unprepare(hdmi->phy_clk);
    if (hdmi->ahb_clk)
        clk_disable_unprepare(hdmi->ahb_clk);
    clk_disable_unprepare(hdmi->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dw_hdmi_of_match[] = {
    { .compatible = "rockchip,rk3399-dw-hdmi" },
    { .compatible = "snps,dw-hdmi" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_hdmi_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dw_hdmi_driver = {
    .probe = dw_hdmi_probe,
    .remove = dw_hdmi_remove,
    .driver = {
        .name = "dw-hdmi",
        .of_match_table = dw_hdmi_of_match,
    },
};
module_platform_driver(dw_hdmi_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DesignWare HDMI Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 HDMI 调试核心难点

3.1 无显示输出(No Signal)

现象:连接 HDMI 显示器后,显示“无信号”或屏幕黑屏。

原因

  • PHY 时钟未使能或频率错误。

  • 分辨率/刷新率与显示器不匹配。

  • EDID 读取失败。

调试方法

  1. 检查 PHY 状态

    # 读取 PHY 状态寄存器
    devmem2 <phy_base>+0x00
  2. 查看视频时序配置

    devmem2 <base>+0x08  # HDMI_VIDEO_HTIMING
    devmem2 <base>+0x0C  # HDMI_VIDEO_VTIMING
  3. 手动读取 EDID

    # 使用 i2ctransfer 手动读取 EDID
    i2ctransfer -y 0 w2@0x50 0x00 0x00 r128
  4. 强制分辨率

    # 在 Linux 启动参数中设置
    video=HDMI-1:1920x1080@60

3.2 EDID 读取失败

现象dmesg 显示 "Failed to read EDID",显示器无法正确识别。

原因

  • DDC (I2C) 通信失败。

  • 显示器 EDID 损坏。

  • 电缆接触不良。

调试方法

  1. 检查 I2C 总线

    i2cdetect -y 0
  2. 手动读取 EDID

    # 通过 DDC 地址 0x50 读取 EDID
    i2ctransfer -y 0 w2@0x50 0x00 0x00 r256
  3. 查看 EDID 原始数据

    hexdump -C /sys/class/drm/card0-HDMI-A-1/edid
  4. 使用固定 EDID

    # 使用用户提供的 EDID 数据
    echo "edid=0x..." > /sys/class/drm/card0-HDMI-A-1/edid

3.3 HDMI 无音频输出

现象:HDMI 视频正常,但无音频输出。

原因

  • 音频格式配置错误。

  • HDMI 音频通道未使能。

  • 内核未编译 HDMI 音频支持。

调试方法

  1. 检查音频播放设备

    aplay -l | grep HDMI
  2. 检查音频格式

    # 查看音频设备功能
    cat /proc/asound/card0/stream0
  3. 强制音频路由

    amixer cset numid=3 1  # 选择 HDMI 输出

3.4 HDCP 认证失败

现象:HDMI 连接正常,但显示受保护内容时黑屏。

原因

  • HDCP 密钥未烧录。

  • HDCP 协议握手失败。

  • 显示设备不支持 HDCP。

调试方法

  1. 检查 HDCP 状态

    devmem2 <base>+0x30  # HDMI_HDCP_STATUS
  2. 检查 HDCP 密钥

    # 查看 HDCP 密钥存储位置
    cat /sys/kernel/debug/hdmi/hdcp_keys
  3. 禁用 HDCP

    echo 0 > /sys/class/drm/card0-HDMI-A-1/hdcp

第四章 结合性能调试场景示例

场景:HDMI 显示器在 4K@60Hz 下出现闪烁,降低到 4K@30Hz 正常。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • perf top 显示 dw_hdmi_bridge_mode_set 占用 CPU 较高。

    • iostat 正常。

  2. HDMI 层(图谱的 Device Drivers -> HDMI Controller 层):

    devmem2 <base>+0x00  # HDMI_VIDEO_MODE

    发现视频模式设置为 4K@60Hz,但 PHY 状态寄存器指示 TMDS 时钟过高。

  3. PHY 调试(PHY 层):

    devmem2 <phy_base>+0x08  # HDMI_PHY_TMDS_CTRL

    发现 TMDS 驱动强度配置为默认值,不足以驱动 4K@60Hz 信号。

  4. 根本原因

    • HDMI PHY 驱动强度不足,无法在 600MHz TMDS 时钟下稳定传输。

    • 4K@60Hz 需要 600MHz TMDS 时钟,而 PHY 性能无法满足。

  5. 解决方案

    • 调整 PHY 驱动强度(增加 TMDS 电流)。

    • 降低 TMDS 时钟(例如选择 4K@60Hz 但降低色深到 8-bit)。

    • 更换更高质量 HDMI 电缆。

    • 优化后,4K@60Hz 稳定工作。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
I2C (DDC) 读取 EDID、配置显示器 总线通信稳定
PHY 物理层信号调理 驱动强度、时钟稳定性
Audio 通过 HDMI 传输音频 音频格式、通道数
CEC 消费电子控制 消息路由、设备发现
HDCP 内容保护 密钥存储、认证协议

第四部分 MIPI DSI 控制器

第一章 MIPI DSI 控制器在 Platform Bus 中的位置

MIPI DSI (Display Serial Interface) 是移动设备和嵌入式系统中广泛使用的显示接口标准。它通过高速串行总线将 SoC 与 LCD 显示屏连接。DSI 控制器通常作为 Platform 设备挂载在 SoC 内部总线上,通过 MIPI D-PHY 输出到显示面板。

在 Linux 中,DSI 子系统的核心层次:

  1. DRM 核心层 (drivers/gpu/drm/):提供显示设备抽象。

  2. MIPI DSI 核心层 (drivers/gpu/drm/drm_mipi_dsi.c):管理 DSI 总线、设备、传输。

  3. DSI 控制器驱动(本篇文章重点):具体的 SoC DSI 控制器驱动(如 dw_mipi_dsi.csun6i_mipi_dsi.c)。

  4. DSI 面板驱动:具体显示面板的驱动(如 panel-simple.c)。


第二章 Linux 5.10 典型 DSI 控制器驱动 —— dw_mipi_dsi.c

DesignWare MIPI DSI 控制器是最广泛使用的 DSI IP 核之一,广泛用于 Rockchip、Allwinner、ST 等 SoC。以下代码基于 Linux 5.10 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c,展示核心逻辑。

2.1 硬件关键概念

  • DSI 主机控制器:负责将并行视频数据转换为 MIPI DSI 数据包。

  • D-PHY:物理层收发器,负责高速串行数据传输。

  • 视频模式:支持 Command Mode(命令模式)和 Video Mode(视频模式)。

  • 数据通道:支持 1-4 条数据通道 (Lane)。

  • 时钟:需要 pclk(总线时钟)和 phy_clk(PHY 时钟)。

  • 中断:处理 DSI 错误、传输完成等事件。

2.2 核心代码

// 基于 Linux 5.10 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_modes.h>
#include <drm/drm_mipi_dsi.h>
​
/**
 * @brief DesignWare MIPI DSI 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 DSI Controller 硬件实例。
 */
struct dw_mipi_dsi {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    void __iomem *phy_base;          /**< PHY 寄存器基址 */
    int irq;                         /**< 中断号 */
    struct clk *pclk;                /**< 总线时钟 */
    struct clk *phy_clk;             /**< PHY 时钟 */
    struct clk *ref_clk;             /**< 参考时钟 */
    struct device *dev;              /**< 设备指针 */
    struct drm_bridge bridge;        /**< DRM 桥接器抽象 */
    struct drm_connector connector;  /**< DRM 连接器抽象 */
    struct mipi_dsi_host host;       /**< DSI 主机抽象 */
    struct mipi_dsi_device *dsi;     /**< DSI 设备 */
    u32 lanes;                       /**< 数据通道数量 (1/2/4) */
    u32 mode_flags;                  /**< 模式标志 (VIDEO/CMD) */
    u32 format;                      /**< 像素格式 (RGB565/666/888) */
    spinlock_t lock;                 /**< 硬件保护锁 */
};
​
/* 寄存器偏移量 (核心) */
#define DSI_VIDEO_MODE              0x00
#define DSI_VIDEO_H_TIMING          0x04
#define DSI_VIDEO_V_TIMING          0x08
#define DSI_VIDEO_POLARITY          0x0C
#define DSI_VIDEO_FRAME_RATE        0x10
#define DSI_CMD_MODE                0x14
#define DSI_CMD_PACKET              0x18
#define DSI_LANE_CTRL               0x1C
#define DSI_PHY_CTRL                0x20
#define DSI_PHY_STATUS              0x24
#define DSI_INT_STATUS              0x28
#define DSI_INT_ENABLE              0x2C
​
/**
 * @brief 初始化 DSI PHY。
 * 
 * @param dsi 指向 dw_mipi_dsi 结构。
 * @return 0 成功。
 */
static int dw_mipi_dsi_phy_init(struct dw_mipi_dsi *dsi)
{
    u32 status;
​
    // 1. 上电 PHY
    writel(0x1, dsi->phy_base + DSI_PHY_CTRL);
​
    // 2. 等待 PHY 稳定
    msleep(100);
​
    // 3. 检查 PHY 状态
    status = readl(dsi->phy_base + DSI_PHY_STATUS);
    if (!(status & (1 << 0))) {
        dev_err(dsi->dev, "PHY not ready\n");
        return -EIO;
    }
​
    return 0;
}
​
/**
 * @brief 关闭 DSI PHY。
 * 
 * @param dsi 指向 dw_mipi_dsi 结构。
 */
static void dw_mipi_dsi_phy_deinit(struct dw_mipi_dsi *dsi)
{
    writel(0x0, dsi->phy_base + DSI_PHY_CTRL);
}
​
/**
 * @brief 配置 DSI 视频时序。
 * 
 * @param dsi 指向 dw_mipi_dsi 结构。
 * @param mode 指向 drm_display_mode。
 */
static void dw_mipi_dsi_set_video_mode(struct dw_mipi_dsi *dsi,
                                       struct drm_display_mode *mode)
{
    u32 h_timing, v_timing, polarity;
​
    // 1. 配置水平时序 (HBP, HFP, HSA, HTOTAL)
    h_timing = (mode->htotal << 16) | (mode->hdisplay);
    writel(h_timing, dsi->base + DSI_VIDEO_H_TIMING);
​
    // 2. 配置垂直时序 (VBP, VFP, VSA, VTOTAL)
    v_timing = (mode->vtotal << 16) | (mode->vdisplay);
    writel(v_timing, dsi->base + DSI_VIDEO_V_TIMING);
​
    // 3. 配置极性
    polarity = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : (1 << 0);
    polarity |= (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : (1 << 1);
    writel(polarity, dsi->base + DSI_VIDEO_POLARITY);
}
​
/**
 * @brief 配置 DSI 数据通道。
 * 
 * @param dsi 指向 dw_mipi_dsi 结构。
 * @param lanes 通道数量 (1/2/4)。
 */
static void dw_mipi_dsi_set_lanes(struct dw_mipi_dsi *dsi, u32 lanes)
{
    u32 ctrl;
​
    ctrl = readl(dsi->base + DSI_LANE_CTRL);
    ctrl &= ~0x03;
    switch (lanes) {
    case 1:
        ctrl |= 0x0;
        break;
    case 2:
        ctrl |= 0x1;
        break;
    case 4:
        ctrl |= 0x3;
        break;
    default:
        ctrl |= 0x0;
        break;
    }
    writel(ctrl, dsi->base + DSI_LANE_CTRL);
}
​
/**
 * @brief 发送 DSI 命令 (Command Mode)。
 * 
 * @param dsi 指向 dw_mipi_dsi 结构。
 * @param cmd 指向 mipi_dsi_msg。
 * @return 0 成功。
 */
static int dw_mipi_dsi_send_cmd(struct dw_mipi_dsi *dsi,
                                struct mipi_dsi_msg *cmd)
{
    u32 packet;
    int i;
​
    // 1. 检查是否处于命令模式
    if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) {
        // 暂时切换到命令模式
        writel(0x1, dsi->base + DSI_CMD_MODE);
    }
​
    // 2. 构建命令包
    packet = (cmd->type << 8) | (cmd->channel << 4) | cmd->flags;
    writel(packet, dsi->base + DSI_CMD_PACKET);
​
    // 3. 写入数据 (如果有)
    if (cmd->tx_len > 0) {
        for (i = 0; i < cmd->tx_len; i++) {
            writel(cmd->tx_buf[i], dsi->base + DSI_CMD_PACKET + 4);
        }
    }
​
    // 4. 等待命令完成
    while (!(readl(dsi->base + DSI_INT_STATUS) & (1 << 0)))
        ;
    writel(1 << 0, dsi->base + DSI_INT_STATUS);
​
    return 0;
}
​
/**
 * @brief DSI 主机传输函数。
 * 
 * @param host 指向 mipi_dsi_host。
 * @param msg 指向 mipi_dsi_msg。
 * @return 0 成功。
 */
static int dw_mipi_dsi_host_transfer(struct mipi_dsi_host *host,
                                     struct mipi_dsi_msg *msg)
{
    struct dw_mipi_dsi *dsi = container_of(host, struct dw_mipi_dsi, host);
​
    return dw_mipi_dsi_send_cmd(dsi, msg);
}
​
/**
 * @brief DSI 中断处理函数。
 * 
 * 处理传输完成、错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 dw_mipi_dsi 结构。
 */
static irqreturn_t dw_mipi_dsi_irq_handler(int irq, void *dev_id)
{
    struct dw_mipi_dsi *dsi = dev_id;
    u32 status;
​
    // 1. 读取中断状态
    status = readl(dsi->base + DSI_INT_STATUS);
​
    // 2. 处理传输完成
    if (status & (1 << 0)) {
        // 清除中断
        writel(1 << 0, dsi->base + DSI_INT_STATUS);
    }
​
    // 3. 处理错误
    if (status & (1 << 1)) {
        dev_err(dsi->dev, "DSI error: FIFO overflow\n");
        writel(1 << 1, dsi->base + DSI_INT_STATUS);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 启用 DSI 桥接器。
 * 
 * @param bridge 指向 drm_bridge。
 */
static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge)
{
    struct dw_mipi_dsi *dsi = bridge_to_dw_mipi_dsi(bridge);
​
    // 1. 初始化 PHY
    dw_mipi_dsi_phy_init(dsi);
​
    // 2. 配置数据通道
    dw_mipi_dsi_set_lanes(dsi, dsi->lanes);
​
    // 3. 配置视频模式
    dw_mipi_dsi_set_video_mode(dsi, &dsi->bridge.encoder->crtc->state->mode);
​
    // 4. 启动 DSI
    writel(0x1, dsi->base + DSI_VIDEO_MODE);
​
    // 5. 启用中断
    writel(0x1, dsi->base + DSI_INT_ENABLE);
}
​
/**
 * @brief 禁用 DSI 桥接器。
 * 
 * @param bridge 指向 drm_bridge。
 */
static void dw_mipi_dsi_bridge_disable(struct drm_bridge *bridge)
{
    struct dw_mipi_dsi *dsi = bridge_to_dw_mipi_dsi(bridge);
​
    // 1. 禁用中断
    writel(0x0, dsi->base + DSI_INT_ENABLE);
​
    // 2. 停止 DSI
    writel(0x0, dsi->base + DSI_VIDEO_MODE);
​
    // 3. 关闭 PHY
    dw_mipi_dsi_phy_deinit(dsi);
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 DSI 控制器硬件,注册到 DRM。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dw_mipi_dsi_probe(struct platform_device *pdev)
{
    struct dw_mipi_dsi *dsi;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
    if (!dsi)
        return -ENOMEM;
    platform_set_drvdata(pdev, dsi);
    dsi->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    dsi->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dsi->base))
        return PTR_ERR(dsi->base);
​
    // 3. 获取 PHY 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res) {
        dsi->phy_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(dsi->phy_base))
            return PTR_ERR(dsi->phy_base);
    } else {
        dsi->phy_base = dsi->base + 0x1000;
    }
​
    // 4. 获取时钟
    dsi->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(dsi->pclk))
        return PTR_ERR(dsi->pclk);
    dsi->phy_clk = devm_clk_get(&pdev->dev, "phy_clk");
    if (IS_ERR(dsi->phy_clk))
        return PTR_ERR(dsi->phy_clk);
    dsi->ref_clk = devm_clk_get_optional(&pdev->dev, "ref_clk");
    if (IS_ERR(dsi->ref_clk))
        return PTR_ERR(dsi->ref_clk);
​
    clk_prepare_enable(dsi->pclk);
    clk_prepare_enable(dsi->phy_clk);
    if (dsi->ref_clk)
        clk_prepare_enable(dsi->ref_clk);
​
    // 5. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    dsi->irq = irq;
​
    // 6. 获取 DSI 配置 (从设备树)
    of_property_read_u32(pdev->dev.of_node, "lanes", &dsi->lanes);
    dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
​
    spin_lock_init(&dsi->lock);
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, dsi->irq, dw_mipi_dsi_irq_handler,
                           IRQF_SHARED, "dw-mipi-dsi", dsi);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 8. 初始化 DSI 主机
    dsi->host.ops = &dw_mipi_dsi_host_ops;
    dsi->host.dev = &pdev->dev;
​
    ret = mipi_dsi_host_register(&dsi->host);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register DSI host\n");
        goto err_irq;
    }
​
    dev_info(&pdev->dev, "DesignWare MIPI DSI controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, dsi->irq);
    return 0;
​
err_irq:
    devm_free_irq(&pdev->dev, dsi->irq, dsi);
err_clk:
    if (dsi->ref_clk)
        clk_disable_unprepare(dsi->ref_clk);
    clk_disable_unprepare(dsi->phy_clk);
    clk_disable_unprepare(dsi->pclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int dw_mipi_dsi_remove(struct platform_device *pdev)
{
    struct dw_mipi_dsi *dsi = platform_get_drvdata(pdev);
​
    // 1. 注销 DSI 主机
    mipi_dsi_host_unregister(&dsi->host);
​
    // 2. 禁用硬件
    writel(0x0, dsi->base + DSI_VIDEO_MODE);
​
    // 3. 禁用时钟
    if (dsi->ref_clk)
        clk_disable_unprepare(dsi->ref_clk);
    clk_disable_unprepare(dsi->phy_clk);
    clk_disable_unprepare(dsi->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dw_mipi_dsi_of_match[] = {
    { .compatible = "rockchip,rk3399-dsi" },
    { .compatible = "snps,dw-mipi-dsi" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_mipi_dsi_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dw_mipi_dsi_driver = {
    .probe = dw_mipi_dsi_probe,
    .remove = dw_mipi_dsi_remove,
    .driver = {
        .name = "dw-mipi-dsi",
        .of_match_table = dw_mipi_dsi_of_match,
    },
};
module_platform_driver(dw_mipi_dsi_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DesignWare MIPI DSI Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 DSI 调试核心难点

3.1 屏幕无显示 (Screen Blank)

现象:系统启动后,LCD 屏幕不亮,dmesg 无 DSI 相关错误。

原因

  • PHY 未初始化或信号质量差。

  • 时序配置错误(分辨率/刷新率不匹配)。

  • 面板驱动未正确注册。

调试方法

  1. 检查 DSI 状态

    devmem2 <base>+0x24  # DSI_PHY_STATUS
  2. 检查视频时序

    devmem2 <base>+0x04  # DSI_VIDEO_H_TIMING
    devmem2 <base>+0x08  # DSI_VIDEO_V_TIMING
  3. 手动配置面板

    # 通过 sysfs 强制初始化
    echo "1" > /sys/class/drm/card0-DSI-1/status
  4. 检查面板电源:确保 LCD 电源 GPIO 正确拉高。

3.2 显示花屏/条纹 (Screen Artifacts)

现象:屏幕有彩色条纹或图像失真。

原因

  • 像素时钟频率不正确。

  • 数据通道 (Lane) 数量配置错误。

  • DSI 速率过高导致信号干扰。

调试方法

  1. 降低 DSI 速率:在 DTS 中设置 dsi_speed = <1000000>; 测试。

  2. 检查数据通道

    devmem2 <base>+0x1C  # DSI_LANE_CTRL
  3. 调整视频时序:修正 HBP、HFP、HSA 等参数。

  4. 检查时钟频率

    cat /sys/kernel/debug/clk/clk_summary | grep dsi

3.3 命令模式无法发送

现象:无法通过 DSI 向 LCD 发送初始化命令,屏幕始终黑屏。

原因

  • DSI 主机未切换到命令模式。

  • 传输缓冲区溢出。

  • 命令包格式错误。

调试方法

  1. 检查模式切换

    devmem2 <base>+0x14  # DSI_CMD_MODE
  2. 手动发送命令

    # 使用 dsi 工具发送命令
    dsi_write -b 0x28  # 例如发送 DCS 命令
  3. 增加超时:在驱动中增加 msleep 等待命令完成。

3.4 屏幕闪烁 (Flickering)

现象:屏幕在显示时周期性闪烁。

原因

  • VBLANK 中断处理延迟。

  • 视频时序中的 HBP/HFP 过小。

  • 电源纹波。

调试方法

  1. 检查 VBLANK

    cat /proc/interrupts | grep dsi
  2. 调整时序参数:增加 HBP 或 HFP 值。

  3. 测试不同刷新率:60Hz -> 30Hz。

  4. 检查电源:用示波器查看 LCM 电源是否有纹波。


第四章 结合性能调试场景示例

场景:通过 DSI 显示 1080p@60Hz 时,屏幕出现横向撕裂 (Tearing),降低到 1080p@30Hz 正常。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • perf top 显示 dw_mipi_dsi_irq_handler 占用 CPU 较高。

    • iostat 显示 CPU 负载正常。

  2. DSI 层(图谱的 Device Drivers -> DSI Controller 层):

    devmem2 <base>+0x10  # DSI_VIDEO_FRAME_RATE

    发现帧率设置为 60Hz,但 VBLANK 中断未及时处理。

  3. PHY 调试(PHY 层):

    devmem2 <phy_base>+0x00  # 读取 PHY 状态

    发现 PHY 工作在 1Gbps,但 1080p@60Hz 只需要 1.5Gbps。

  4. 根本原因

    • DSI PHY 速率配置过高 (1.5Gbps),但实际速率被限制在 1Gbps。

    • 在 1Gbps 下,1080p@60Hz 的带宽不足,导致 VBLANK 期间数据传输未完成,产生撕裂。

  5. 解决方案

    • 降低 DSI 传输速率到 900Mbps。

    • 增加 VBLANK 中断延迟容限。

    • 优化 DMA 传输,减少中断处理时间。

    • 优化后,1080p@60Hz 稳定工作。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
DMA 数据传输搬运 数据流稳定,中断聚合
PHY 物理层信号调理 速率配置,驱动强度
GPIO LCD 电源控制、背光调节 电源时序,GPIO 电平
PWM LED 背光亮度控制 PWM 频率,占空比设置
DRM 显示核心抽象 模式配置,原子提交

第五部分 Crypto Engine 控制器

第一章 Crypto Engine 在 Platform Bus 中的位置

Crypto Engine (CE) 是现代 SoC 中专门用于加速加密算法(如 AES、DES、SHA、RSA、SM2/SM3/SM4 等)的硬件模块。它通常作为 Platform 设备挂载在内部总线上,通过 DMA 与内存交互,提供高性能的加密/解密和哈希计算服务。

在 Linux 中,Crypto 子系统架构分为三层:

  1. Crypto 核心层 (crypto/):提供通用的加密算法抽象 (struct crypto_alg),管理算法注册。

  2. Crypto 引擎驱动(本篇文章重点):具体 SoC 的加密硬件驱动(如 rockchip-crypto.csunxi-ss.ccaam.c)。

  3. 用户空间接口:通过 /dev/crypto (AF_ALG) 提供用户空间访问。


第二章 Linux 5.10 典型 Crypto Engine 驱动 —— rockchip-crypto.c

Rockchip Crypto Engine 是 Rockchip 系列 SoC 中常用的加密硬件加速器,支持 AES (ECB/CBC/CTR)、SHA (1/256)、RSA、SM4 等算法。以下代码基于 Linux 5.10 drivers/crypto/rockchip/rk3288_crypto.c,展示核心逻辑。

2.1 硬件关键概念

  • AES 引擎:支持 128/192/256 位密钥,支持 ECB/CBC/CTR 模式。

  • SHA 引擎:支持 SHA1、SHA256、SHA512。

  • RSA 引擎:支持 1024/2048/4096 位模数运算。

  • DMA 引擎:支持从内存直接搬运数据到 Crypto 引擎,不需要 CPU 干预。

  • 中断:处理加密/解密完成、错误等事件。

  • 时钟:需要 aclk (总线时钟) 和 hclk (主机时钟)。

2.2 核心代码

// 基于 Linux 5.10 drivers/crypto/rockchip/rk3288_crypto.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
​
/**
 * @brief Rockchip Crypto Engine 的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Crypto Engine 硬件实例。
 */
struct rockchip_crypto {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    int irq;                         /**< 中断号 */
    struct clk *aclk;                /**< 总线时钟 */
    struct clk *hclk;                /**< 主机时钟 */
    struct device *dev;              /**< 设备指针 */
    struct crypto_alg *alg;          /**< 注册的加密算法 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    struct completion complete;      /**< 用于同步等待完成 */
    u8 *dma_buf;                     /**< DMA 缓冲区 */
    dma_addr_t dma_addr;             /**< DMA 缓冲区物理地址 */
    u32 buf_len;                     /**< 缓冲区长度 */
};
​
/* 寄存器偏移量 (Rockchip 加密引擎) */
#define CRYPTO_CTRL             0x00
#define CRYPTO_STATUS           0x04
#define CRYPTO_IV               0x08
#define CRYPTO_KEY              0x10
#define CRYPTO_SRC_ADDR         0x20
#define CRYPTO_DST_ADDR         0x24
#define CRYPTO_DATA_LEN         0x28
#define CRYPTO_MODE             0x2C
#define CRYPTO_INT_EN           0x30
#define CRYPTO_INT_STAT         0x34
​
/* 算法模式定义 */
#define CRYPTO_MODE_AES_ECB     0x00
#define CRYPTO_MODE_AES_CBC     0x01
#define CRYPTO_MODE_AES_CTR     0x02
#define CRYPTO_MODE_SHA1        0x10
#define CRYPTO_MODE_SHA256      0x11
​
/**
 * @brief 初始化 Crypto 引擎硬件。
 * 
 * @param crypto 指向 rockchip_crypto 结构。
 */
static void rockchip_crypto_hw_init(struct rockchip_crypto *crypto)
{
    // 1. 复位加密引擎
    writel(0x1, crypto->base + CRYPTO_CTRL);
    msleep(10);
    writel(0x0, crypto->base + CRYPTO_CTRL);
​
    // 2. 启用中断
    writel(0x1, crypto->base + CRYPTO_INT_EN);
}
​
/**
 * @brief 执行 AES 加密/解密操作。
 * 
 * @param crypto 指向 rockchip_crypto 结构。
 * @param src 源数据。
 * @param dst 目的数据。
 * @param len 数据长度。
 * @param key 密钥。
 * @param key_len 密钥长度 (128/192/256)。
 * @param iv 初始向量。
 * @param encrypt 1 为加密,0 为解密。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_run(struct rockchip_crypto *crypto,
                                   u8 *src, u8 *dst, int len,
                                   u8 *key, int key_len,
                                   u8 *iv, int encrypt)
{
    u32 ctrl, mode;
    int i;
​
    // 1. 准备 DMA 缓冲区
    memcpy(crypto->dma_buf, src, len);
    dma_map_single(crypto->dev, crypto->dma_addr, len, DMA_TO_DEVICE);
​
    // 2. 配置密钥
    for (i = 0; i < key_len / 4; i++) {
        writel(((u32 *)key)[i], crypto->base + CRYPTO_KEY + i * 4);
    }
​
    // 3. 配置 IV (CBC/CTR 模式需要)
    if (iv) {
        for (i = 0; i < 4; i++) {
            writel(((u32 *)iv)[i], crypto->base + CRYPTO_IV + i * 4);
        }
    }
​
    // 4. 配置数据地址
    writel(crypto->dma_addr, crypto->base + CRYPTO_SRC_ADDR);
    writel(crypto->dma_addr, crypto->base + CRYPTO_DST_ADDR);
​
    // 5. 配置数据长度 (字节)
    writel(len, crypto->base + CRYPTO_DATA_LEN);
​
    // 6. 配置模式
    mode = 0;
    if (key_len == 192) mode |= (1 << 4);
    else if (key_len == 256) mode |= (2 << 4);
    mode |= CRYPTO_MODE_AES_CBC;  // 假设 CBC 模式
    writel(mode, crypto->base + CRYPTO_MODE);
​
    // 7. 启动加密/解密
    ctrl = (encrypt ? (1 << 0) : 0);  // 0=加密, 1=解密
    ctrl |= (1 << 1);  // 启用 DMA
    ctrl |= (1 << 2);  // 启用引擎
    writel(ctrl, crypto->base + CRYPTO_CTRL);
​
    // 8. 等待完成
    if (!wait_for_completion_timeout(&crypto->complete, 5 * HZ)) {
        dev_err(crypto->dev, "AES operation timeout\n");
        return -ETIMEDOUT;
    }
​
    // 9. 读取结果
    dma_unmap_single(crypto->dev, crypto->dma_addr, len, DMA_FROM_DEVICE);
    memcpy(dst, crypto->dma_buf, len);
​
    return 0;
}
​
/**
 * @brief 执行 SHA 哈希计算。
 * 
 * @param crypto 指向 rockchip_crypto 结构。
 * @param src 源数据。
 * @param len 数据长度。
 * @param hash 输出哈希值。
 * @param algo 哈希算法 (SHA1/SHA256)。
 * @return 0 成功。
 */
static int rockchip_crypto_sha_run(struct rockchip_crypto *crypto,
                                   u8 *src, int len,
                                   u8 *hash, int algo)
{
    u32 ctrl, mode;
    int i;
​
    // 1. 准备 DMA 缓冲区
    memcpy(crypto->dma_buf, src, len);
    dma_map_single(crypto->dev, crypto->dma_addr, len, DMA_TO_DEVICE);
​
    // 2. 配置数据地址
    writel(crypto->dma_addr, crypto->base + CRYPTO_SRC_ADDR);
    writel(0, crypto->base + CRYPTO_DST_ADDR);
​
    // 3. 配置数据长度
    writel(len, crypto->base + CRYPTO_DATA_LEN);
​
    // 4. 配置模式
    if (algo == 1) {
        mode = CRYPTO_MODE_SHA1;
    } else {
        mode = CRYPTO_MODE_SHA256;
    }
    writel(mode, crypto->base + CRYPTO_MODE);
​
    // 5. 启动 SHA
    ctrl = (1 << 1) | (1 << 2);  // DMA 和引擎使能
    writel(ctrl, crypto->base + CRYPTO_CTRL);
​
    // 6. 等待完成
    if (!wait_for_completion_timeout(&crypto->complete, 5 * HZ)) {
        dev_err(crypto->dev, "SHA operation timeout\n");
        return -ETIMEDOUT;
    }
​
    // 7. 读取哈希结果 (从寄存器中读取)
    for (i = 0; i < (algo == 1 ? 5 : 8); i++) {
        ((u32 *)hash)[i] = readl(crypto->base + CRYPTO_IV + i * 4);
    }
​
    dma_unmap_single(crypto->dev, crypto->dma_addr, len, DMA_TO_DEVICE);
    return 0;
}
​
/**
 * @brief AES 加密操作 (同步接口)。
 * 
 * @param req 指向 ablkcipher_request。
 * @param encrypt 1 为加密,0 为解密。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_encrypt(struct ablkcipher_request *req, int encrypt)
{
    struct crypto_ablkcipher *tfm = crypto_ablkcipher_reqtfm(req);
    struct rockchip_crypto *crypto = crypto_ablkcipher_ctx(tfm);
    struct scatterlist *src = req->src;
    struct scatterlist *dst = req->dst;
    u8 *src_buf, *dst_buf;
    int len = req->nbytes;
    int ret;
​
    // 1. 映射 scatterlist
    src_buf = kmalloc(len, GFP_KERNEL);
    dst_buf = kmalloc(len, GFP_KERNEL);
    if (!src_buf || !dst_buf) {
        ret = -ENOMEM;
        goto out;
    }
​
    sg_copy_to_buffer(src, sg_nents(src), src_buf, len);
​
    // 2. 执行硬件加密
    ret = rockchip_crypto_aes_run(crypto, src_buf, dst_buf, len,
                                   crypto->key, crypto->key_len,
                                   req->info, encrypt);
​
    // 3. 将结果写回 destination
    if (ret == 0) {
        sg_copy_from_buffer(dst, sg_nents(dst), dst_buf, len);
    }
​
out:
    kfree(src_buf);
    kfree(dst_buf);
    return ret;
}
​
/**
 * @brief AES 加密请求。
 * 
 * @param req 指向 ablkcipher_request。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_encrypt_req(struct ablkcipher_request *req)
{
    return rockchip_crypto_aes_encrypt(req, 1);
}
​
/**
 * @brief AES 解密请求。
 * 
 * @param req 指向 ablkcipher_request。
 * @return 0 成功。
 */
static int rockchip_crypto_aes_decrypt_req(struct ablkcipher_request *req)
{
    return rockchip_crypto_aes_encrypt(req, 0);
}
​
/**
 * @brief 中断处理函数。
 * 
 * 处理加密/解密完成、错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_crypto 结构。
 */
static irqreturn_t rockchip_crypto_irq_handler(int irq, void *dev_id)
{
    struct rockchip_crypto *crypto = dev_id;
    u32 status;
​
    // 1. 读取中断状态
    status = readl(crypto->base + CRYPTO_INT_STAT);
​
    // 2. 处理完成中断
    if (status & (1 << 0)) {
        // 清除中断
        writel(1 << 0, crypto->base + CRYPTO_INT_STAT);
        // 通知等待的线程
        complete(&crypto->complete);
    }
​
    // 3. 处理错误中断
    if (status & (1 << 1)) {
        dev_err(crypto->dev, "Crypto engine error\n");
        writel(1 << 1, crypto->base + CRYPTO_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 Crypto 引擎硬件,注册算法。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_crypto_probe(struct platform_device *pdev)
{
    struct rockchip_crypto *crypto;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    crypto = devm_kzalloc(&pdev->dev, sizeof(*crypto), GFP_KERNEL);
    if (!crypto)
        return -ENOMEM;
    platform_set_drvdata(pdev, crypto);
    crypto->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    crypto->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(crypto->base))
        return PTR_ERR(crypto->base);
​
    // 3. 获取时钟
    crypto->aclk = devm_clk_get(&pdev->dev, "aclk");
    if (IS_ERR(crypto->aclk))
        return PTR_ERR(crypto->aclk);
    crypto->hclk = devm_clk_get(&pdev->dev, "hclk");
    if (IS_ERR(crypto->hclk))
        return PTR_ERR(crypto->hclk);
​
    clk_prepare_enable(crypto->aclk);
    clk_prepare_enable(crypto->hclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    crypto->irq = irq;
​
    // 5. 分配 DMA 缓冲区
    crypto->buf_len = PAGE_SIZE;
    crypto->dma_buf = dma_alloc_coherent(&pdev->dev, crypto->buf_len,
                                          &crypto->dma_addr, GFP_KERNEL);
    if (!crypto->dma_buf) {
        dev_err(&pdev->dev, "Failed to allocate DMA buffer\n");
        ret = -ENOMEM;
        goto err_clk;
    }
​
    // 6. 初始化完成量
    init_completion(&crypto->complete);
    spin_lock_init(&crypto->lock);
​
    // 7. 硬件初始化
    rockchip_crypto_hw_init(crypto);
​
    // 8. 注册中断
    ret = devm_request_irq(&pdev->dev, crypto->irq, rockchip_crypto_irq_handler,
                           IRQF_SHARED, "rockchip-crypto", crypto);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_dma;
    }
​
    // 9. 注册 AES 算法
    struct crypto_alg alg = {
        .cra_name = "aes-cbc",
        .cra_driver_name = "rockchip-aes-cbc",
        .cra_priority = 300,
        .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER,
        .cra_blocksize = AES_BLOCK_SIZE,
        .cra_ctxsize = sizeof(struct rockchip_crypto),
        .cra_module = THIS_MODULE,
        .cra_ablkcipher = {
            .min_keysize = AES_MIN_KEY_SIZE,
            .max_keysize = AES_MAX_KEY_SIZE,
            .ivsize = AES_BLOCK_SIZE,
            .setkey = rockchip_crypto_aes_setkey,
            .encrypt = rockchip_crypto_aes_encrypt_req,
            .decrypt = rockchip_crypto_aes_decrypt_req,
        },
    };
​
    ret = crypto_register_alg(&alg);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register AES algorithm\n");
        goto err_irq;
    }
    crypto->alg = &alg;
​
    dev_info(&pdev->dev, "Rockchip Crypto Engine registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, crypto->irq);
    return 0;
​
err_irq:
    devm_free_irq(&pdev->dev, crypto->irq, crypto);
err_dma:
    dma_free_coherent(&pdev->dev, crypto->buf_len, crypto->dma_buf, crypto->dma_addr);
err_clk:
    clk_disable_unprepare(crypto->hclk);
    clk_disable_unprepare(crypto->aclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_crypto_remove(struct platform_device *pdev)
{
    struct rockchip_crypto *crypto = platform_get_drvdata(pdev);
​
    // 1. 注销算法
    if (crypto->alg)
        crypto_unregister_alg(crypto->alg);
​
    // 2. 释放 DMA 缓冲区
    dma_free_coherent(&pdev->dev, crypto->buf_len, crypto->dma_buf, crypto->dma_addr);
​
    // 3. 禁用时钟
    clk_disable_unprepare(crypto->hclk);
    clk_disable_unprepare(crypto->aclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_crypto_of_match[] = {
    { .compatible = "rockchip,rk3288-crypto" },
    { .compatible = "rockchip,rk3399-crypto" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_crypto_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_crypto_driver = {
    .probe = rockchip_crypto_probe,
    .remove = rockchip_crypto_remove,
    .driver = {
        .name = "rockchip-crypto",
        .of_match_table = rockchip_crypto_of_match,
    },
};
module_platform_driver(rockchip_crypto_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip Crypto Engine Driver");
MODULE_LICENSE("GPL v2");

第三章 Crypto Engine 调试核心难点

3.1 算法调用失败

现象:应用程序调用加密 API 时,返回 -EINVAL-ENOTSUPP

原因

  • 算法未在 Crypto 引擎中注册。

  • 密钥长度不支持。

  • 模式不支持(如 CTR 模式未在硬件中实现)。

调试方法

  1. 检查算法注册

    cat /proc/crypto | grep rockchip
  2. 测试算法支持

    # 使用 cryptsetup 测试 AES 加密
    cryptsetup luksFormat --cipher aes-cbc-essiv:sha256 /dev/sda
  3. 查看日志

    dmesg | grep crypto

3.2 DMA 传输错误

现象:加密操作卡住,dmesg 出现 "DMA mapping error" 或 "Operation timeout"。

原因

  • DMA 缓冲区未对齐到硬件要求(某些硬件需要 16 或 64 字节对齐)。

  • 源数据长度不是 16 字节的倍数(AES 块大小要求)。

  • DMA 地址超过 32 位范围。

调试方法

  1. 检查 DMA 对齐

    # 检查缓冲区地址
    cat /sys/kernel/debug/dma/dma-* | grep crypto
  2. 调整数据长度:在驱动中填充数据到 16 字节倍数。

  3. 使用 perf 跟踪 DMA 中断

    perf record -e dma:* -a -- timeout 5
    perf script | grep crypto

3.3 性能瓶颈(吞吐量低)

现象openssl speed aes-256-cbc 测速结果远低于硬件理论值。

原因

  • 中断开销过高。

  • 每次操作都经过 DMA 映射/取消映射。

  • 未使用批量处理或请求队列。

调试方法

  1. 查看中断频率

    cat /proc/interrupts | grep crypto
  2. 增加批处理大小:在驱动中合并多个请求。

  3. 使用 perf 分析热点

    perf record -e cycles -g openssl speed aes-256-cbc
    perf report

3.4 哈希结果错误

现象:使用 SHA 哈希得到的结果与软件实现不一致。

原因

  • 哈希状态未正确初始化。

  • 数据长度处理错误(SHA 需要填充)。

  • 字节序问题(大端/小端)。

调试方法

  1. 对比软件计算结果

    # 使用 openssl 计算
    echo -n "test" | openssl sha256
  2. 调试寄存器状态

    devmem2 <base>+0x10  # CRYPTO_IV 寄存器
  3. 检查初始化向量:确保每次哈希开始前清零。


第四章 结合性能调试场景示例

场景:在系统负载高时,加密操作偶尔失败,dmesg 出现 "AES operation timeout"。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • top 显示 kworker/crypto 进程占用 CPU 较高。

    • perf top 显示 rockchip_crypto_irq_handler 占用 30% CPU。

  2. Crypto 层(图谱的 Device Drivers -> Crypto Engine 层):

    cat /proc/interrupts | grep crypto

    发现中断触发频率很高(5000+ 次/秒)。

  3. DMA 层(DMA 控制器层):

    trace-cmd record -e dma:* -e crypto:* -a -- timeout 5
    trace-cmd report | grep rockchip_crypto

    发现 DMA 中断处理延迟超过 1ms。

  4. 根本原因

    • 每次加密操作都需要重新映射 DMA 缓冲区,DMA 映射和取消映射操作频繁。

    • 加密请求频率高,中断处理无法及时完成所有请求。

    • 中断处理程序在释放 DMA 映射时被较高优先级任务抢占。

  5. 解决方案

    • 实现请求队列,批量处理加密操作。

    • 使用 DMA 池预分配 DMA 缓冲区,减少映射开销。

    • 提高中断处理优先级。

    • 优化后,吞吐量提升 4 倍,无超时错误。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
DMA 数据传输搬运 DMA 对齐、映射开销、中断延迟
Clock 提供总线时钟 时钟频率匹配
Interrupt Controller 管理加密完成中断 中断优先级、处理延迟
Memory Controller 访问加密数据 内存一致性、缓存策略

第六部分 VOP 视频输出处理器

第一章 VOP 控制器在 Platform Bus 中的位置

VOP (Video Output Processor) 是 Rockchip 等 SoC 内部负责显示输出的关键模块。它从内存中的 Framebuffer 读取像素数据,经过缩放、色彩转换、叠加等处理,最终通过 RGB、LVDS、HDMI、MIPI DSI 等接口输出到显示屏。VOP 在 Linux 中通过 DRM (Direct Rendering Manager) 框架管理,作为一个 Platform 设备挂载在内部总线上。

在 Linux 5.10 中,VOP 驱动位于 drivers/gpu/drm/rockchip/rockchip_vop.c,是 DRM 框架中 CRTC (CRT Controller) 的核心实现。

1.1 硬件关键概念

  • CRTC:负责从内存读取 Framebuffer 并生成视频时序。

  • Layer 层:支持多个硬件层(如 背景层、视频层、光标层),可独立配置。

  • DMA 引擎:内置 DMA,从内存直接抓取像素数据。

  • 缩放引擎:支持硬件缩放和色彩转换。

  • 中断:VBLANK (垂直同步中断)、帧完成中断,用于驱动页面翻转和原子提交。

1.2 核心代码

以下代码基于 Linux 5.10 drivers/gpu/drm/rockchip/rockchip_vop.c,展示核心逻辑。

// 基于 Linux 5.10 drivers/gpu/drm/rockchip/rockchip_vop.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <drm/drm_crtc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_gem.h>
#include <drm/drm_vblank.h>
#include <drm/drm_atomic.h>
​
/**
 * @brief VOP 硬件层的结构体。
 * 
 * 每个VOP有多个层 (Layer0, Layer1, 光标层等)。
 */
struct vop_win {
    struct vop *vop;              /**< 所属的 VOP 设备 */
    struct drm_plane *plane;      /**< DRM 平面抽象 */
    void __iomem *regs;           /**< 该层的寄存器基址 */
    int channel;                  /**< 层通道号 (0/1/2) */
    bool enabled;                 /**< 层是否启用 */
    u32 fb_dma_addr;              /**< 当前帧缓冲区的 DMA 地址 */
};
​
/**
 * @brief VOP 控制器核心私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 VOP 硬件实例。
 */
struct vop {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    void __iomem *grf_base;        /**< GRF 寄存器基址 (用于信号输出控制) */
    int irq;                       /**< VBLANK 中断号 */
    struct clk *pclk;              /**< 总线时钟 */
    struct clk *aclk;              /**< 核心时钟 */
    struct clk *dclk;              /**< 像素时钟 (与分辨率相关) */
    struct device *dev;            /**< 设备指针 */
    struct drm_crtc crtc;          /**< DRM CRTC 抽象 */
    struct drm_device *drm_dev;    /**< DRM 设备指针 */
    struct vop_win *win;           /**< 支持的硬件层数组 */
    int num_wins;                  /**< 硬件层数量 */
    spinlock_t lock;               /**< 硬件保护锁 */
    u32 vblank_count;              /**< VBLANK 计数 */
    bool vblank_enabled;           /**< VBLANK 中断使能 */
    u32 max_width;                 /**< 最大输出宽度 (例如 4096) */
    u32 max_height;                /**< 最大输出高度 (例如 4096) */
};
​
/* 寄存器偏移量 (VOP 通用定义) */
#define VOP_REG_CTRL               0x000
#define VOP_REG_STATUS             0x004
#define VOP_REG_INT_EN             0x008
#define VOP_REG_INT_STAT           0x00C
#define VOP_REG_DSP_CTRL           0x010
#define VOP_REG_DSP_H_BP           0x014
#define VOP_REG_DSP_H_PW           0x018
#define VOP_REG_DSP_H_FP           0x01C
#define VOP_REG_DSP_V_BP           0x020
#define VOP_REG_DSP_V_PW           0x024
#define VOP_REG_DSP_V_FP           0x028
#define VOP_REG_LAYER_BASE         0x100  /**< 硬件层寄存器起始地址 */
​
/* 层寄存器偏移 */
#define LAYER_REG_CTRL             0x00
#define LAYER_REG_SRC_ADDR         0x04
#define LAYER_REG_SRC_STRIDE       0x08
#define LAYER_REG_DST_ORIGIN       0x0C
#define LAYER_REG_DST_SIZE         0x10
#define LAYER_REG_SCALE_CTRL       0x14
#define LAYER_REG_PALETTE          0x18
​
/**
 * @brief 读取 VOP 状态寄存器。
 * 
 * @param vop 指向 vop 结构。
 * @return 状态值。
 */
static u32 vop_read_status(struct vop *vop)
{
    return readl_relaxed(vop->base + VOP_REG_STATUS);
}
​
/**
 * @brief 设置 VOP 的像素时钟频率。
 * 
 * @param vop 指向 vop 结构。
 * @param rate 像素时钟频率 (Hz)。
 * @return 0 成功。
 */
static int vop_set_pixel_clock(struct vop *vop, unsigned long rate)
{
    // 1. 计算 VOP 所需的像素时钟
    // 通常 DCLK 频率 = 水平总像素 * 垂直总像素 * 刷新率
    // 例如 1920x1080@60Hz 需要约 148.5MHz
    unsigned long cur_rate = clk_get_rate(vop->dclk);
    
    if (cur_rate != rate) {
        // 只有在频率不同时才切换时钟
        clk_set_rate(vop->dclk, rate);
    }
​
    return 0;
}
​
/**
 * @brief 配置 VOP 的显示时序。
 * 
 * @param vop 指向 vop 结构。
 * @param mode 指向 drm_display_mode。
 */
static void vop_set_display_mode(struct vop *vop, struct drm_display_mode *mode)
{
    u32 h_bp, h_pw, h_fp;
    u32 v_bp, v_pw, v_fp;
    u32 htotal, vtotal;
​
    // 1. 计算时序参数
    h_bp = mode->htotal - mode->hsync_end;   // HBP
    h_pw = mode->hsync_end - mode->hsync_start; // HSA
    h_fp = mode->hsync_start - mode->hdisplay; // HFP
    
    v_bp = mode->vtotal - mode->vsync_end;
    v_pw = mode->vsync_end - mode->vsync_start;
    v_fp = mode->vsync_start - mode->vdisplay;
​
    // 2. 写入寄存器
    writel_relaxed(h_bp, vop->base + VOP_REG_DSP_H_BP);
    writel_relaxed(h_pw, vop->base + VOP_REG_DSP_H_PW);
    writel_relaxed(h_fp, vop->base + VOP_REG_DSP_H_FP);
    writel_relaxed(v_bp, vop->base + VOP_REG_DSP_V_BP);
    writel_relaxed(v_pw, vop->base + VOP_REG_DSP_V_PW);
    writel_relaxed(v_fp, vop->base + VOP_REG_DSP_V_FP);
​
    // 3. 启用显示
    writel_relaxed(0x1, vop->base + VOP_REG_DSP_CTRL);
}
​
/**
 * @brief 配置硬件层 (Plane)。
 * 
 * @param win 指向 vop_win 结构。
 * @param fb 指向 drm_framebuffer。
 * @param crtc_x CRTC X坐标。
 * @param crtc_y CRTC Y坐标。
 * @param width 宽度。
 * @param height 高度。
 */
static void vop_set_plane(struct vop_win *win, struct drm_framebuffer *fb,
                          u32 crtc_x, u32 crtc_y, u32 width, u32 height)
{
    struct vop *vop = win->vop;
    void __iomem *layer_base = win->regs;
    struct drm_gem_object *obj = fb->obj[0];
    struct rockchip_gem_object *rk_obj = to_rk_gem_obj(obj);
    dma_addr_t fb_dma_addr = rk_obj->dma_addr;
    u32 stride = fb->pitches[0];
    u32 format = fb->format->format;
​
    // 1. 配置源地址 (DMA 地址)
    writel_relaxed(fb_dma_addr, layer_base + LAYER_REG_SRC_ADDR);
​
    // 2. 配置步长
    writel_relaxed(stride, layer_base + LAYER_REG_SRC_STRIDE);
​
    // 3. 配置目标位置和尺寸
    writel_relaxed((crtc_y << 16) | crtc_x, layer_base + LAYER_REG_DST_ORIGIN);
    writel_relaxed((height << 16) | width, layer_base + LAYER_REG_DST_SIZE);
​
    // 4. 配置层控制寄存器 (启用层,设置格式)
    u32 ctrl = 0;
    // 设置像素格式 (例如 DRM_FORMAT_ARGB8888 -> 0x01)
    ctrl |= (0x01 << 8);
    ctrl |= (1 << 0);  // 启用层
    writel_relaxed(ctrl, layer_base + LAYER_REG_CTRL);
​
    win->enabled = true;
    win->fb_dma_addr = fb_dma_addr;
}
​
/**
 * @brief 禁用硬件层。
 * 
 * @param win 指向 vop_win 结构。
 */
static void vop_disable_plane(struct vop_win *win)
{
    struct vop *vop = win->vop;
    void __iomem *layer_base = win->regs;
​
    // 清除启用位
    writel_relaxed(0, layer_base + LAYER_REG_CTRL);
    win->enabled = false;
}
​
/**
 * @brief 提交硬件配置到寄存器 (原子提交)。
 * 
 * @param vop 指向 vop 结构。
 */
static void vop_atomic_commit(struct vop *vop)
{
    // 1. 等待当前帧扫描完成 (VSYNC)
    // 在中断处理中处理
​
    // 2. 刷新层配置到硬件
    // 每个层的配置已经在 vop_set_plane 中写入
​
    // 3. 触发 VBLANK 中断生成
    writel_relaxed(0x1, vop->base + VOP_REG_INT_EN);
}
​
/**
 * @brief CRTC 操作: 启用 CRTC。
 * 
 * @param crtc 指向 drm_crtc。
 */
static void vop_crtc_enable(struct drm_crtc *crtc)
{
    struct vop *vop = container_of(crtc, struct vop, crtc);
​
    // 1. 启用时钟
    clk_prepare_enable(vop->pclk);
    clk_prepare_enable(vop->aclk);
    clk_prepare_enable(vop->dclk);
​
    // 2. 配置时序
    vop_set_display_mode(vop, &crtc->state->mode);
}
​
/**
 * @brief CRTC 操作: 禁用 CRTC。
 * 
 * @param crtc 指向 drm_crtc。
 */
static void vop_crtc_disable(struct drm_crtc *crtc)
{
    struct vop *vop = container_of(crtc, struct vop, crtc);
​
    // 1. 禁用显示
    writel_relaxed(0, vop->base + VOP_REG_DSP_CTRL);
​
    // 2. 禁用时钟
    clk_disable_unprepare(vop->dclk);
    clk_disable_unprepare(vop->aclk);
    clk_disable_unprepare(vop->pclk);
}
​
/**
 * @brief CRTC 操作: 更新全局状态 (原子提交)。
 * 
 * @param crtc 指向 drm_crtc。
 * @param state 指向 drm_crtc_state。
 */
static int vop_crtc_atomic_flush(struct drm_crtc *crtc,
                                 struct drm_crtc_state *old_state)
{
    struct vop *vop = container_of(crtc, struct vop, crtc);
​
    // 1. 更新像素时钟
    vop_set_pixel_clock(vop, crtc->state->mode.clock * 1000);
​
    // 2. 提交硬件配置
    vop_atomic_commit(vop);
​
    // 3. 启用 VBLANK 中断
    drm_crtc_vblank_get(crtc);
​
    return 0;
}
​
/**
 * @brief VOP 中断处理函数。
 * 
 * 处理 VBLANK 中断,用于画面翻转。
 *
 * @param irq 中断号。
 * @param dev_id 指向 vop 结构。
 */
static irqreturn_t vop_irq_handler(int irq, void *dev_id)
{
    struct vop *vop = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl_relaxed(vop->base + VOP_REG_INT_STAT);
​
    // 2. 处理 VBLANK 中断
    if (int_stat & (1 << 0)) {
        // 清除中断
        writel_relaxed(1 << 0, vop->base + VOP_REG_INT_STAT);
        
        // 通知 DRM 核心 VBLANK 已触发
        drm_crtc_handle_vblank(&vop->crtc);
        
        // 更新 vblank 计数
        vop->vblank_count++;
    }
​
    // 3. 处理其他错误中断
    if (int_stat & (1 << 1)) {
        dev_err(vop->dev, "VOP overflow interrupt\n");
        writel_relaxed(1 << 1, vop->base + VOP_REG_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int vop_probe(struct platform_device *pdev)
{
    struct vop *vop;
    struct resource *res;
    struct drm_device *drm = NULL; // 外部传入的 drm 设备
    int ret, irq;
​
    // 1. 分配私有数据结构
    vop = devm_kzalloc(&pdev->dev, sizeof(*vop), GFP_KERNEL);
    if (!vop)
        return -ENOMEM;
    platform_set_drvdata(pdev, vop);
    vop->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    vop->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(vop->base))
        return PTR_ERR(vop->base);
​
    // 3. 获取 GRF 资源 (用于输出信号控制)
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res) {
        vop->grf_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(vop->grf_base))
            vop->grf_base = NULL;
    }
​
    // 4. 获取时钟
    vop->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(vop->pclk))
        return PTR_ERR(vop->pclk);
    vop->aclk = devm_clk_get(&pdev->dev, "aclk");
    if (IS_ERR(vop->aclk))
        return PTR_ERR(vop->aclk);
    vop->dclk = devm_clk_get(&pdev->dev, "dclk");
    if (IS_ERR(vop->dclk))
        return PTR_ERR(vop->dclk);
​
    // 5. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_dma;
    }
    vop->irq = irq;
​
    // 6. 读取硬件配置
    vop->max_width = 4096;
    vop->max_height = 4096;
    vop->num_wins = 2; // 简化: 2个硬件层
​
    // 7. 分配硬件层
    vop->win = devm_kzalloc(&pdev->dev, 
                            vop->num_wins * sizeof(struct vop_win), 
                            GFP_KERNEL);
    if (!vop->win) {
        ret = -ENOMEM;
        goto err_dma;
    }
​
    for (int i = 0; i < vop->num_wins; i++) {
        vop->win[i].vop = vop;
        vop->win[i].regs = vop->base + VOP_REG_LAYER_BASE + i * 0x20;
        vop->win[i].channel = i;
        vop->win[i].enabled = false;
    }
​
    spin_lock_init(&vop->lock);
​
    // 8. 注册中断
    ret = devm_request_irq(&pdev->dev, vop->irq, vop_irq_handler,
                           IRQF_SHARED, "vop", vop);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_dma;
    }
​
    // 9. 设置 CRTC 操作并注册到 DRM
    vop->crtc.ops = &vop_crtc_ops;
    vop->crtc.funcs = &vop_crtc_funcs;
    // ... 继续初始化 CRTC 并添加到 DRM
​
    dev_info(&pdev->dev, "VOP controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, vop->irq);
    return 0;
​
err_dma:
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int vop_remove(struct platform_device *pdev)
{
    struct vop *vop = platform_get_drvdata(pdev);
​
    // 1. 禁用硬件
    writel_relaxed(0, vop->base + VOP_REG_DSP_CTRL);
    
    // 2. 关闭时钟
    clk_disable_unprepare(vop->dclk);
    clk_disable_unprepare(vop->aclk);
    clk_disable_unprepare(vop->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id vop_of_match[] = {
    { .compatible = "rockchip,rk3399-vop" },
    { .compatible = "rockchip,rk3288-vop" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, vop_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver vop_driver = {
    .probe = vop_probe,
    .remove = vop_remove,
    .driver = {
        .name = "vop",
        .of_match_table = vop_of_match,
    },
};
module_platform_driver(vop_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip VOP Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 VOP 调试核心难点

3.1 屏幕无输出 (Black Screen)

现象:系统启动后,显示屏没有任何输出,dmesg 显示 VOP 成功注册,但无画面。

原因

  • 像素时钟频率配置错误。

  • 数据线 (RGB/LVDS/MIPI 等) 接口未正确配置。

  • 显示时序参数 (HBP/VBP 等) 超出显示器范围。

  • 硬件层未启用,或层配置错误。

调试方法

  1. 检查 VOP 状态

    devmem2 <base>+0x004  # VOP_REG_STATUS
  2. 检查像素时钟

    cat /sys/kernel/debug/clk/clk_summary | grep dclk
  3. 检查显示时序

    devmem2 <base>+0x014  # DSP_H_BP
    devmem2 <base>+0x01C  # DSP_H_FP
  4. 强制启用显示

    devmem2 <base>+0x010 0x01  # DSP_CTRL

3.2 画面闪烁或撕裂 (Tearing)

现象:画面在滚动或变化时出现撕裂,VBLANK 中断处理频繁。

原因

  • VBLANK 中断处理延迟 (高负载下)。

  • 原子提交未正确同步。

  • 帧缓冲区格式与硬件不匹配。

调试方法

  1. 检查 VBLANK 中断统计

    cat /proc/interrupts | grep vop
  2. 使用 perf 监控中断延迟

    perf record -e irq:irq_handler_entry -a -- timeout 5
    perf script | grep vop
  3. 检查内存带宽

    iostat -x 1

3.3 色彩异常或颜色偏移

现象:屏幕显示的颜色不正确,偏绿/偏蓝或完全反转。

原因

  • 像素格式配置错误 (ARGB8888 vs XRGB8888 vs RGB565)。

  • 色彩空间设置错误。

  • 调色板未正确设置。

调试方法

  1. 检查像素格式

    devmem2 <base>+0x100  # LAYER_REG_CTRL
  2. 检查色彩空间

    devmem2 <base>+0x130  # 色彩控制寄存器
  3. 测试不同的颜色格式

    modetest -M rockchip -f XRGB8888 -s 0:1920x1080@60

3.4 硬件层无法叠加

现象:通过 DRM 创建两个 Plane (层),只有一个层显示,另一个被遮挡。

原因

  • 硬件不支持层的 Z-order。

  • 层配置未正确设置优先级。

  • 两个层配置了重叠区域且未启用 alpha blending。

调试方法

  1. 检查层支持

    cat /sys/kernel/debug/dri/0/planes
  2. 配置层优先级

    # 在驱动中设置 Z-order
    devmem2 <base>+0x100 0x01  # 启用层0
    devmem2 <base>+0x120 0x01  # 启用层1

第四章 结合性能调试场景示例

场景:在 4K 分辨率下通过 DRM 运行 OpenGL ES 应用时,画面每隔 10 秒出现一次掉帧 (丢帧)。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • top 显示 GPU 和 CPU 负载均正常。

    • perf top 显示 vop_irq_handlerdrm_atomic_commit 占用 CPU 较低。

  2. VOP 层(图谱的 Device Drivers -> VOP Controller 层):

    cat /sys/kernel/debug/dri/0/vblank

    发现 VBLANK 计数每 16.6ms 增加一次 (60Hz),正常。

  3. 内存带宽(内存控制器层):

    iostat -x 1

    显示内存带宽接近 100%,可能出现拥堵。

  4. 根本原因

    • 4K 分辨率需要极高的内存带宽 (4K2K32bit*60Hz ≈ 15GB/s)。

    • 在嵌入式 SoC 中,DDR 带宽有限。

    • 当 OpenGL 应用和 VOP 同时访问内存时,出现竞争,导致 VOP 无法及时取到下一帧数据。

  5. 解决方案

    • 减少 VOP 的帧缓冲区大小 (使用 YUV 格式代替 RGB)。

    • 增加 DDR 时钟频率 (通过 DTS 配置)。

    • 在应用层降低帧率到 30Hz 或使用更低的色彩深度。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
DMA 从内存抓取帧数据 内存带宽、地址对齐
HDMI/DSI 接收 VOP 信号并传输 信号协议转换、链路建立
Memory Controller 提供高带宽内存访问 内存频率、带宽调度
Interrupt Controller 提供 VBLANK 中断 中断优先级、处理延迟
Clock Controller 提供像素时钟 时钟源稳定,频率匹配

第七部分 I3C 控制器

第一章 I3C 控制器在 Platform Bus 中的位置

I3C (Improved Inter-Integrated Circuit) 是 MIPI 联盟推出的新一代串行总线标准,旨在结合 I2C 和 SPI 的优点,提供更高的速度、更低的功耗和更强的可扩展性。I3C 控制器通常作为 Platform 设备挂载在 SoC 内部总线上,支持 I3C 主/从模式,可以连接传感器、PMIC、音频 Codec 等设备。

在 Linux 5.10 中,I3C 子系统位于 drivers/i3c/,核心层次分为三层:

  1. I3C 核心层 (drivers/i3c/i3c-core.c):管理 I3C 总线、设备和协议。

  2. I3C 控制器驱动(本篇文章重点):具体的 SoC I3C 控制器驱动(如 dw-i3c.ccdns-i3c.c)。

  3. I3C 设备驱动:具体 I3C 设备的驱动(如 i3c-sensor.c)。


第二章 Linux 5.10 典型 I3C 控制器驱动 —— dw-i3c.c

DesignWare I3C 控制器是广泛使用的 I3C IP 核之一,支持 I3C 主模式和从模式,兼容 I2C 设备。以下代码基于 Linux 5.10 drivers/i3c/master/dw-i3c.c,展示核心逻辑。

2.1 硬件关键概念

  • I3C 核心寄存器:配置总线模式、时序、中断、命令等。

  • I3C 命令队列:支持批处理命令,减少 CPU 干预。

  • IBI (In-Band Interrupt):设备通过总线发送的中断请求。

  • HDR (High Data Rate):支持 12.5Mbps 的高速传输模式。

  • 时钟:需要 pclk(总线时钟)和 i3c_clk(I3C 核心时钟)。

2.2 核心代码

// 基于 Linux 5.10 drivers/i3c/master/dw-i3c.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/i3c/master.h>
#include <linux/i3c/device.h>
#include <linux/delay.h>
​
/**
 * @brief DesignWare I3C 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 I3C Controller 硬件实例。
 */
struct dw_i3c_master {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 */
    struct clk *pclk;               /**< 总线时钟 */
    struct clk *i3c_clk;            /**< I3C 核心时钟 */
    struct device *dev;             /**< 设备指针 */
    struct i3c_master_controller *master; /**< I3C 主控制器抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 max_speed;                  /**< 最大总线速度 (Hz) */
    u32 target_speed;               /**< 目标总线速度 (Hz) */
    u32 version;                    /**< 控制器版本 */
    struct completion cmd_complete; /**< 命令完成同步 */
    u32 cmd_status;                 /**< 命令执行状态 */
};
​
/* 寄存器偏移量 (DesignWare I3C) */
#define I3C_CMD_CTRL              0x00
#define I3C_CMD_STATUS            0x04
#define I3C_DATA                   0x08
#define I3C_FIFO_STATUS           0x0C
#define I3C_INT_ENABLE            0x10
#define I3C_INT_STATUS            0x14
#define I3C_INT_CLEAR             0x18
#define I3C_BUS_CONFIG            0x1C
#define I3C_TIMING                0x20
#define I3C_DEV_ADDR              0x24
#define I3C_IBI_STATUS            0x28
#define I3C_IBI_MASK              0x2C
#define I3C_IBI_DATA              0x30
#define I3C_HDR_CONFIG            0x34
​
/* 命令控制位 */
#define I3C_CMD_RD                (0x0 << 0)
#define I3C_CMD_WR                (0x1 << 0)
#define I3C_CMD_IBI_EN            (1 << 4)
#define I3C_CMD_HDR_EN            (1 << 5)
​
/**
 * @brief 初始化 I3C 控制器硬件。
 * 
 * @param dw_i3c 指向 dw_i3c_master 结构。
 */
static void dw_i3c_hw_init(struct dw_i3c_master *dw_i3c)
{
    u32 config, timing;
​
    // 1. 复位控制器
    writel(0x1, dw_i3c->base + I3C_CMD_CTRL);
    msleep(10);
    writel(0x0, dw_i3c->base + I3C_CMD_CTRL);
​
    // 2. 配置总线模式 (I3C 主模式)
    config = (1 << 0) | (1 << 1);  // 启用 I3C 主模式
    writel(config, dw_i3c->base + I3C_BUS_CONFIG);
​
    // 3. 配置时序 (基于目标速度计算)
    // 假设 12.5MHz 速度
    u32 div = clk_get_rate(dw_i3c->i3c_clk) / dw_i3c->target_speed;
    timing = (div << 16) | (div >> 2);
    writel(timing, dw_i3c->base + I3C_TIMING);
​
    // 4. 启用中断 (IBI, 命令完成, 错误)
    writel(0x07, dw_i3c->base + I3C_INT_ENABLE);
}
​
/**
 * @brief 发送 I3C 命令 (读/写)。
 * 
 * @param dw_i3c 指向 dw_i3c_master 结构。
 * @param addr 设备地址。
 * @param data 数据缓冲区。
 * @param len 数据长度。
 * @param is_read 1 为读,0 为写。
 * @return 0 成功。
 */
static int dw_i3c_send_command(struct dw_i3c_master *dw_i3c,
                               u16 addr, u8 *data, int len, int is_read)
{
    u32 cmd;
    int i;
​
    // 1. 设置设备地址
    writel(addr << 1, dw_i3c->base + I3C_DEV_ADDR);
​
    // 2. 构建命令
    cmd = (len << 16) | (is_read ? I3C_CMD_RD : I3C_CMD_WR);
    if (is_read) {
        // 读命令
        cmd |= (1 << 2);  // 启用 RX
    } else {
        // 写命令: 将数据写入 FIFO
        for (i = 0; i < len; i++) {
            writel(data[i], dw_i3c->base + I3C_DATA);
        }
        cmd |= (1 << 3);  // 启用 TX
    }
​
    // 3. 启动命令
    writel(cmd, dw_i3c->base + I3C_CMD_CTRL);
​
    // 4. 等待命令完成
    if (!wait_for_completion_timeout(&dw_i3c->cmd_complete, 5 * HZ)) {
        dev_err(dw_i3c->dev, "I3C command timeout\n");
        return -ETIMEDOUT;
    }
​
    // 5. 检查命令状态
    u32 status = readl(dw_i3c->base + I3C_CMD_STATUS);
    if (status & (1 << 1)) {
        dev_err(dw_i3c->dev, "I3C command failed\n");
        return -EIO;
    }
​
    // 6. 读数据 (从 FIFO)
    if (is_read) {
        for (i = 0; i < len; i++) {
            data[i] = readl(dw_i3c->base + I3C_DATA) & 0xFF;
        }
    }
​
    return 0;
}
​
/**
 * @brief I3C 数据传输函数 (主控制器回调)。
 * 
 * @param master 指向 i3c_master_controller。
 * @param xfer 指向 i3c_msg 数组。
 * @param nmsg 消息数量。
 * @return 成功传输的消息数量。
 */
static int dw_i3c_master_xfer(struct i3c_master_controller *master,
                              struct i3c_msg *xfer, int nmsg)
{
    struct dw_i3c_master *dw_i3c = dev_get_drvdata(master->dev.parent);
    int i, ret = 0;
​
    for (i = 0; i < nmsg; i++) {
        // 发送命令
        ret = dw_i3c_send_command(dw_i3c, xfer[i].addr,
                                  xfer[i].buf, xfer[i].len,
                                  (xfer[i].flags & I3C_M_RD));
        if (ret < 0)
            break;
    }
​
    return ret;
}
​
/**
 * @brief 处理 IBI (In-Band Interrupt) 中断。
 * 
 * @param dw_i3c 指向 dw_i3c_master 结构。
 */
static void dw_i3c_handle_ibi(struct dw_i3c_master *dw_i3c)
{
    u32 ibi_status = readl(dw_i3c->base + I3C_IBI_STATUS);
    u32 ibi_data = readl(dw_i3c->base + I3C_IBI_DATA);
    u32 dev_addr = (ibi_data >> 1) & 0x7F;
​
    // 1. 识别发起 IBI 的设备
    // 2. 调用对应的 IBI 处理函数
    // 3. 清除 IBI 状态
    writel(ibi_status, dw_i3c->base + I3C_IBI_STATUS);
}
​
/**
 * @brief I3C 中断处理函数。
 * 
 * 处理命令完成、IBI、错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 dw_i3c_master 结构。
 */
static irqreturn_t dw_i3c_irq_handler(int irq, void *dev_id)
{
    struct dw_i3c_master *dw_i3c = dev_id;
    u32 int_status;
​
    // 1. 读取中断状态
    int_status = readl(dw_i3c->base + I3C_INT_STATUS);
​
    // 2. 处理命令完成中断
    if (int_status & (1 << 0)) {
        writel(1 << 0, dw_i3c->base + I3C_INT_CLEAR);
        complete(&dw_i3c->cmd_complete);
    }
​
    // 3. 处理 IBI 中断
    if (int_status & (1 << 1)) {
        writel(1 << 1, dw_i3c->base + I3C_INT_CLEAR);
        dw_i3c_handle_ibi(dw_i3c);
    }
​
    // 4. 处理错误中断
    if (int_status & (1 << 2)) {
        dev_err(dw_i3c->dev, "I3C bus error detected\n");
        writel(1 << 2, dw_i3c->base + I3C_INT_CLEAR);
        // 可能需要重置总线
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 I3C 控制器硬件,注册到 I3C 核心。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dw_i3c_probe(struct platform_device *pdev)
{
    struct dw_i3c_master *dw_i3c;
    struct i3c_master_controller *master;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配 I3C 主控制器
    master = devm_i3c_master_alloc(&pdev->dev, sizeof(struct dw_i3c_master));
    if (!master)
        return -ENOMEM;
    dw_i3c = i3c_master_get_devdata(master);
    dw_i3c->master = master;
    dw_i3c->dev = &pdev->dev;
    platform_set_drvdata(pdev, dw_i3c);
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    dw_i3c->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dw_i3c->base))
        return PTR_ERR(dw_i3c->base);
​
    // 3. 获取时钟
    dw_i3c->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(dw_i3c->pclk))
        return PTR_ERR(dw_i3c->pclk);
    dw_i3c->i3c_clk = devm_clk_get(&pdev->dev, "i3c_clk");
    if (IS_ERR(dw_i3c->i3c_clk))
        return PTR_ERR(dw_i3c->i3c_clk);
​
    clk_prepare_enable(dw_i3c->pclk);
    clk_prepare_enable(dw_i3c->i3c_clk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    dw_i3c->irq = irq;
​
    // 5. 初始化配置
    dw_i3c->max_speed = 12500000;  // 12.5MHz
    dw_i3c->target_speed = 12500000;
    dw_i3c->version = 0x100;
​
    spin_lock_init(&dw_i3c->lock);
    init_completion(&dw_i3c->cmd_complete);
​
    // 6. 注册中断
    ret = devm_request_irq(&pdev->dev, dw_i3c->irq, dw_i3c_irq_handler,
                           IRQF_SHARED, "dw-i3c", dw_i3c);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 7. 硬件初始化
    dw_i3c_hw_init(dw_i3c);
​
    // 8. 设置主控制器操作
    master->ops = &dw_i3c_master_ops;
    master->dev.parent = &pdev->dev;
​
    // 9. 注册 I3C 主控制器
    ret = i3c_master_register(master);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register I3C master\n");
        goto err_irq;
    }
​
    dev_info(&pdev->dev, "DesignWare I3C controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, dw_i3c->irq);
    return 0;
​
err_irq:
    devm_free_irq(&pdev->dev, dw_i3c->irq, dw_i3c);
err_clk:
    clk_disable_unprepare(dw_i3c->i3c_clk);
    clk_disable_unprepare(dw_i3c->pclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int dw_i3c_remove(struct platform_device *pdev)
{
    struct dw_i3c_master *dw_i3c = platform_get_drvdata(pdev);
​
    // 1. 注销 I3C 主控制器
    i3c_master_unregister(dw_i3c->master);
​
    // 2. 禁用硬件
    writel(0x0, dw_i3c->base + I3C_CMD_CTRL);
​
    // 3. 禁用时钟
    clk_disable_unprepare(dw_i3c->i3c_clk);
    clk_disable_unprepare(dw_i3c->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dw_i3c_of_match[] = {
    { .compatible = "snps,dw-i3c" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_i3c_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dw_i3c_driver = {
    .probe = dw_i3c_probe,
    .remove = dw_i3c_remove,
    .driver = {
        .name = "dw-i3c",
        .of_match_table = dw_i3c_of_match,
    },
};
module_platform_driver(dw_i3c_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DesignWare I3C Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 I3C 调试核心难点

3.1 I3C 总线无法识别设备

现象:I3C 设备连接后,i3c_detect 无输出,dmesg 显示 "No I3C device found"。

原因

  • I3C 总线时序配置错误。

  • 设备地址配置错误。

  • 上拉电阻配置不当。

调试方法

  1. 检查 I3C 总线状态

    devmem2 <base>+0x1C  # I3C_BUS_CONFIG
  2. 手动扫描设备

    # 使用 i3c 工具扫描
    i3c-scan /dev/i3c-0
  3. 检查设备地址

    # 读取设备地址寄存器
    devmem2 <base>+0x24  # I3C_DEV_ADDR

3.2 IBI (In-Band Interrupt) 不工作

现象:I3C 设备无法通过总线发送中断请求,或中断处理无响应。

原因

  • IBI 中断未使能。

  • 设备未正确配置 IBI 能力。

  • 中断处理延迟。

调试方法

  1. 检查 IBI 状态

    devmem2 <base>+0x28  # I3C_IBI_STATUS
  2. 使能 IBI

    # 在驱动中设置 IBI 使能位
    devmem2 <base>+0x2C 0x01  # I3C_IBI_MASK
  3. 测试 IBI 触发

    # 使用设备特定的命令触发 IBI
    i3c-tool send /dev/i3c-0 0x08 0xFF

3.3 HDR 模式无法进入

现象:尝试进入高速模式 (HDR) 时,总线卡死或传输失败。

原因

  • 设备不支持 HDR 模式。

  • 时序计算错误。

  • 总线负载过大。

调试方法

  1. 检查设备能力

    # 读取设备能力寄存器
    i3c-tool read /dev/i3c-0 0x01 0x02
  2. 降低 HDR 速度

    # 在 DTS 中设置 hdr-speed = <10000>;
  3. 检查时钟

    cat /sys/kernel/debug/clk/clk_summary | grep i3c

3.4 兼容 I2C 设备不工作

现象:将 I2C 设备连接到 I3C 总线,无法正常读写。

原因

  • I3C 控制器未正确初始化 I2C 模式。

  • 设备地址冲突。

  • 时序不匹配。

调试方法

  1. 检查 I2C 模式

    devmem2 <base>+0x1C  # I3C_BUS_CONFIG
  2. 手动发送 I2C 命令

    i2c-tools i2cdetect -y 0
  3. 配置 I2C 时序

    # 设置 I2C 时序参数
    devmem2 <base>+0x20 0x12345678  # I3C_TIMING

第四章 结合性能调试场景示例

场景:I3C 连接多个传感器,需要同时读取所有传感器数据,但存在总线竞争,导致某些传感器数据丢失。

分析流程

  1. 宏观层面(CPU 层):

    • perf top 显示 dw_i3c_irq_handler 占用 CPU 较高。

    • iostat 正常。

  2. I3C 层(Device Drivers -> I3C Controller 层):

    cat /sys/kernel/debug/i3c/bus0/status

    发现总线处于繁忙状态。

  3. 检查 IBI 统计(中断控制器层):

    cat /proc/interrupts | grep i3c

    发现 IBI 触发频率较高。

  4. 根本原因

    • 多个传感器都使用 IBI 机制上报数据。

    • IBI 处理过程中,总线被锁定,无法处理其他设备请求。

    • 数据量超过总线带宽。

  5. 解决方案

    • 使用 HDR 模式提高总线速度 (12.5MHz)。

    • 减少 IBI 使用,改为轮询方式。

    • 增加命令队列深度,支持批处理。

    • 优化中断处理速度。


第五部分:与其他控制器的协同

控制器 协同方式 调试关键点
DMA 支持批量数据传输 描述符链、带宽
GPIO I3C 总线复位 GPIO 电平控制
I2C 兼容 I2C 设备 时序兼容性
Power 电源管理 低功耗模式下 IBI 处理

第八部分 DesignWare APB Timer 控制器

第一部章 Timer 控制器在 Platform Bus 中的位置

系统定时器是任何 SoC 的核心基础模块,负责为操作系统提供时间基准(Clocksource)调度中断(Clockevent)。在 ARM 架构的 SoC 中,除了 ARM Generic Timer(通过 CPU 访问),许多厂商还会集成 DesignWare APB Timer 等 IP 核,挂载在 Platform Bus 上作为备用或主要定时器。

在 Linux 中,定时器子系统的核心架构分为三层:

  1. Timekeeping 核心层 (kernel/time/):管理 clocksource(用于获取高精度时间)和 clockevent(用于触发中断)。

  2. Timer 控制器驱动(本篇文章重点):具体的 SoC 定时器驱动(如 dw_apb_timer.c)。

  3. 中断控制器:负责将定时器中断路由到 CPU。


第二章 Linux 5.10 典型 Timer 控制器驱动 —— dw_apb_timer.c

DesignWare APB Timer 是广泛使用的定时器 IP 核,支持自由运行的计数器、周期性中断、单次中断等模式。以下代码基于 Linux 5.10 drivers/clocksource/dw_apb_timer.c,展示核心逻辑。

2.1 硬件关键概念

  • 硬件寄存器:控制寄存器、当前计数寄存器、加载寄存器、中断状态寄存器。

  • 时钟源:计数器从 0 计数到最大值(通常 32位),产生时间戳。

  • 时钟事件:设置一个目标计数值,计数器到达后触发中断。

  • 时钟:需要 timer_clk(输入时钟)和 pclk(APB 总线时钟)。

  • 中断:周期/单次触发中断,用于调度。

2.2 核心代码

// 基于 Linux 5.10 drivers/clocksource/dw_apb_timer.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>
#include <linux/delay.h>
​
/**
 * @brief DesignWare APB Timer 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 Timer Controller 硬件实例。
 */
struct dw_apb_timer {
    void __iomem *base;           /**< 映射后的寄存器基址 */
    int irq;                      /**< 中断号 */
    struct clk *timer_clk;        /**< 定时器输入时钟 */
    struct clk *pclk;             /**< APB 总线时钟 */
    struct device *dev;           /**< 设备指针 */
    spinlock_t lock;              /**< 硬件保护锁 */
    u32 max_tick;                 /**< 最大计数值 (硬件位数) */
    u32 freq;                     /**< 定时器频率 */
    struct clocksource cs;        /**< 时钟源抽象 */
    struct clockevent_device ced; /**< 时钟事件抽象 */
};
​
/* 寄存器偏移量 (DesignWare APB Timer) */
#define TIMER_CTRL_REG         0x00
#define TIMER_LOAD_REG         0x04
#define TIMER_CURRENT_REG      0x08
#define TIMER_IRQ_STAT_REG     0x0C
#define TIMER_IRQ_CLR_REG      0x10
​
/* 控制寄存器位定义 */
#define TIMER_CTRL_ENABLE      (1 << 0)
#define TIMER_CTRL_MODE_PERIODIC (0 << 1)
#define TIMER_CTRL_MODE_ONESHOT (1 << 1)
#define TIMER_CTRL_IRQ_ENABLE  (1 << 2)
#define TIMER_CTRL_INTERRUPT   (1 << 3)
​
/**
 * @brief 读取定时器当前计数值(用于时钟源)。
 * 
 * @param cs 指向 clocksource 结构。
 * @return 当前计数值。
 */
static u64 dw_apb_timer_read_clocksource(struct clocksource *cs)
{
    struct dw_apb_timer *timer = container_of(cs, struct dw_apb_timer, cs);
    return readl(timer->base + TIMER_CURRENT_REG);
}
​
/**
 * @brief 初始化定时器为时钟源。
 * 
 * @param timer 指向 dw_apb_timer 结构。
 * @return 0 成功。
 */
static int dw_apb_timer_clocksource_init(struct dw_apb_timer *timer)
{
    struct clocksource *cs = &timer->cs;
​
    // 1. 设置时钟源参数
    cs->name = "dw-apb-timer";
    cs->rating = 300; // 优于普通定时器,但低于 TSC/Arch Timer
    cs->read = dw_apb_timer_read_clocksource;
    cs->mask = CLOCKSOURCE_MASK(32);
    cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
​
    // 2. 注册时钟源
    clocksource_register_hz(cs, timer->freq);
​
    return 0;
}
​
/**
 * @brief 设置定时器下一次中断触发值(用于时钟事件)。
 * 
 * @param ced 指向 clockevent_device。
 * @param evt 下一次触发的时间(cycles)。
 * @return 0 成功。
 */
static int dw_apb_timer_set_next_event(struct clockevent_device *ced,
                                       unsigned long evt)
{
    struct dw_apb_timer *timer = container_of(ced, struct dw_apb_timer, ced);
    unsigned long flags;
​
    // 1. 检查是否超过最大值
    if (evt > timer->max_tick)
        evt = timer->max_tick;
​
    spin_lock_irqsave(&timer->lock, flags);
​
    // 2. 加载计数值到硬件
    writel(evt, timer->base + TIMER_LOAD_REG);
​
    // 3. 启用定时器(单次模式 + 中断使能)
    u32 ctrl = TIMER_CTRL_ENABLE | TIMER_CTRL_MODE_ONESHOT | TIMER_CTRL_IRQ_ENABLE;
    writel(ctrl, timer->base + TIMER_CTRL_REG);
​
    spin_unlock_irqrestore(&timer->lock, flags);
    return 0;
}
​
/**
 * @brief 设置定时器为周期模式(用于时钟事件)。
 * 
 * @param ced 指向 clockevent_device。
 * @param mode 操作模式 (周期/单次/关闭)。
 */
static void dw_apb_timer_set_mode(struct clockevent_device *ced,
                                  enum clock_event_mode mode)
{
    struct dw_apb_timer *timer = container_of(ced, struct dw_apb_timer, ced);
    unsigned long flags;
    u32 ctrl;
​
    spin_lock_irqsave(&timer->lock, flags);
​
    switch (mode) {
    case CLOCK_EVT_MODE_PERIODIC:
        // 周期模式
        ctrl = TIMER_CTRL_ENABLE | TIMER_CTRL_MODE_PERIODIC | TIMER_CTRL_IRQ_ENABLE;
        writel(ctrl, timer->base + TIMER_CTRL_REG);
        break;
    case CLOCK_EVT_MODE_ONESHOT:
        // 单次模式(已经在 set_next_event 中处理)
        break;
    case CLOCK_EVT_MODE_UNUSED:
    case CLOCK_EVT_MODE_SHUTDOWN:
        // 关闭定时器
        writel(0, timer->base + TIMER_CTRL_REG);
        // 清除中断状态
        writel(0x1, timer->base + TIMER_IRQ_CLR_REG);
        break;
    case CLOCK_EVT_MODE_RESUME:
        // 恢复
        ctrl = TIMER_CTRL_ENABLE | TIMER_CTRL_MODE_PERIODIC | TIMER_CTRL_IRQ_ENABLE;
        writel(ctrl, timer->base + TIMER_CTRL_REG);
        break;
    }
​
    spin_unlock_irqrestore(&timer->lock, flags);
}
​
/**
 * @brief 定时器中断处理函数。
 * 
 * 处理时钟事件中断,触发内核调度。
 *
 * @param irq 中断号。
 * @param dev_id 指向 dw_apb_timer 结构。
 * @return IRQ_HANDLED。
 */
static irqreturn_t dw_apb_timer_irq_handler(int irq, void *dev_id)
{
    struct dw_apb_timer *timer = dev_id;
​
    // 1. 读取中断状态
    u32 stat = readl(timer->base + TIMER_IRQ_STAT_REG);
    if (!(stat & 0x1))
        return IRQ_NONE;
​
    // 2. 清除中断
    writel(0x1, timer->base + TIMER_IRQ_CLR_REG);
​
    // 3. 调用 clockevent 的事件处理
    if (timer->ced.event_handler)
        timer->ced.event_handler(&timer->ced);
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 初始化定时器为时钟事件设备。
 * 
 * @param timer 指向 dw_apb_timer 结构。
 * @param irq 中断号。
 * @return 0 成功。
 */
static int dw_apb_timer_clockevent_init(struct dw_apb_timer *timer, int irq)
{
    struct clockevent_device *ced = &timer->ced;
​
    // 1. 设置时钟事件参数
    ced->name = "dw-apb-timer";
    ced->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
    ced->rating = 300;
    ced->set_next_event = dw_apb_timer_set_next_event;
    ced->set_mode = dw_apb_timer_set_mode;
    ced->irq = irq;
​
    // 2. 计算最大计数值(基于 32 位计数器)
    timer->max_tick = 0xFFFFFFFF;
    ced->max_delta_ns = clockevent_delta2ns(0xFFFFFFFF, ced);
    ced->min_delta_ns = clockevent_delta2ns(1, ced);
​
    // 3. 注册到时钟事件核心
    clockevents_config_and_register(ced, timer->freq, 1, 0xFFFFFFFF);
​
    return 0;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化定时器硬件,注册时钟源和时钟事件。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dw_apb_timer_probe(struct platform_device *pdev)
{
    struct dw_apb_timer *timer;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据
    timer = devm_kzalloc(&pdev->dev, sizeof(*timer), GFP_KERNEL);
    if (!timer)
        return -ENOMEM;
    platform_set_drvdata(pdev, timer);
    timer->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    timer->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(timer->base))
        return PTR_ERR(timer->base);
​
    // 3. 获取中断(时钟事件)
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        dev_err(&pdev->dev, "No IRQ resource\n");
        return irq;
    }
    timer->irq = irq;
​
    // 4. 获取时钟
    timer->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(timer->pclk))
        return PTR_ERR(timer->pclk);
    timer->timer_clk = devm_clk_get(&pdev->dev, "timer_clk");
    if (IS_ERR(timer->timer_clk))
        return PTR_ERR(timer->timer_clk);
​
    clk_prepare_enable(timer->pclk);
    clk_prepare_enable(timer->timer_clk);
​
    // 5. 获取定时器频率
    timer->freq = clk_get_rate(timer->timer_clk);
    if (!timer->freq) {
        dev_err(&pdev->dev, "Failed to get timer frequency\n");
        ret = -EINVAL;
        goto err_clk;
    }
​
    spin_lock_init(&timer->lock);
​
    // 6. 硬件初始配置(先停止定时器,清除中断)
    writel(0, timer->base + TIMER_CTRL_REG);
    writel(0x1, timer->base + TIMER_IRQ_CLR_REG);
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, timer->irq, dw_apb_timer_irq_handler,
                           IRQF_SHARED | IRQF_TIMER, "dw-apb-timer", timer);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 8. 初始化为时钟源
    dw_apb_timer_clocksource_init(timer);
​
    // 9. 初始化为时钟事件
    dw_apb_timer_clockevent_init(timer, timer->irq);
​
    dev_info(&pdev->dev, "DesignWare APB Timer registered at 0x%llx, IRQ %d, %u Hz\n",
             (unsigned long long)res->start, timer->irq, timer->freq);
    return 0;
​
err_clk:
    clk_disable_unprepare(timer->timer_clk);
    clk_disable_unprepare(timer->pclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int dw_apb_timer_remove(struct platform_device *pdev)
{
    struct dw_apb_timer *timer = platform_get_drvdata(pdev);
​
    // 1. 禁用硬件
    writel(0, timer->base + TIMER_CTRL_REG);
​
    // 2. 注销时钟事件和时钟源(实际运行时通常不会调用)
    // clocksource_unregister(&timer->cs);
    // clockevents_unbind_device(&timer->ced);
​
    // 3. 禁用时钟
    clk_disable_unprepare(timer->timer_clk);
    clk_disable_unprepare(timer->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dw_apb_timer_of_match[] = {
    { .compatible = "snps,dw-apb-timer" },
    { .compatible = "snps,dw-apb-timer-1" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw_apb_timer_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dw_apb_timer_driver = {
    .probe = dw_apb_timer_probe,
    .remove = dw_apb_timer_remove,
    .driver = {
        .name = "dw-apb-timer",
        .of_match_table = dw_apb_timer_of_match,
    },
};
module_platform_driver(dw_apb_timer_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DesignWare APB Timer Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 Timer 调试核心难点

3.1 系统时间漂移或跳变

现象dmesg 时间戳跳变,ntp 同步失败,date 命令显示的时间与实际差异较大。

原因

  • 定时器时钟源频率计算错误。

  • 硬件时钟精度不足。

  • 中断丢失导致时间累积错误。

调试方法

  1. 查看当前时钟源

    cat /sys/devices/system/clocksource/clocksource0/current_clocksource
  2. 检查时钟频率

    # 读取 timer_clk 实际频率
    cat /sys/kernel/debug/clk/clk_summary | grep timer
  3. 强制切换时钟源

    echo "dw-apb-timer" > /sys/devices/system/clocksource/clocksource0/current_clocksource

3.2 中断风暴(定时器中断频繁)

现象top 显示 irq/xx-timer 占用 CPU 很高,/proc/interrupts 显示定时器中断计数极高。

原因

  • 定时器设置为周期模式,但周期过短。

  • 中断服务程序未清除中断标志。

  • 硬件进入无限循环触发中断。

调试方法

  1. 查看中断统计

    cat /proc/interrupts | grep timer
  2. 调整定时器周期

    # 检查 HZ 配置
    grep HZ /boot/config-$(uname -r)
  3. 跟踪 ISR 执行

    perf record -e irq:irq_handler_entry -a -- sleep 5
    perf script | grep dw_apb_timer

3.3 调度延迟或卡顿

现象:系统响应迟缓,perf sched 显示大量调度延迟,dmesg 出现 "Kernel stack overflow"。

原因

  • 定时器中断处理被高优先级任务抢占。

  • 中断上下文执行了耗时操作。

  • 多个定时器中断竞争。

调试方法

  1. 测量中断延迟

    # 使用 cyclictest 测量
    cyclictest -p 99 -t 5 -n
  2. 增加中断亲和性

    echo <cpu_mask> > /proc/irq/<irq_num>/smp_affinity
  3. 增加或减少 HZ 值

    # 减少 HZ 可以提高调度间隔,减少中断频率
    # 在 kernel config 中设置 CONFIG_HZ_100=y

3.4 空闲时 CPU 无法进入深度睡眠

现象cat /sys/power/pm_debug 显示大量定时器唤醒,功耗高于预期。

原因

  • 定时器在空闲时仍然周期触发。

  • 未使用高精度定时器或 NO_HZ。

  • 硬件未支持 C3/C7 睡眠模式。

调试方法

  1. 检查 NO_HZ 配置

    # 查看内核是否启用 NO_HZ
    cat /boot/config-$(uname -r) | grep NO_HZ
  2. 检查空闲状态

    cat /sys/kernel/debug/tracing/trace | grep -E "idle|timer"
  3. 启用 NO_HZ_IDLE

    # 在 kernel cmdline 中添加
    nohz=on

第四章 结合性能调试场景示例

场景cpu idle 统计显示 polltimer 唤醒占比很高,功耗比预期高 20%。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • perf top 显示 dw_apb_timer_irq_handler 仍然占用 CPU 时间。

    • power 统计显示 CPU 频繁退出空闲状态。

  2. Timer 层(图谱的 Device Drivers -> Timer Controller 层):

    cat /sys/kernel/debug/tracing/trace | grep -E "timer|idle"

    发现系统定时器每 10ms 触发一次中断(100 Hz)。

  3. 中断统计(Interrupt Controller 层):

    cat /proc/interrupts | grep timer

    中断计数每秒 100 次,正常。

  4. 根本原因

    • 内核使用 100 Hz 的周期性定时器,每 10ms 唤醒 CPU 一次。

    • 即使系统空闲,定时器也会强制唤醒 CPU 处理调度,阻止进入深度睡眠。

    • 当前场景不需要如此高频率的调度。

  5. 解决方案

    • 在内核配置中降低 HZ 值(如 CONFIG_HZ_100 改为 CONFIG_HZ_50)。

    • 启用 NO_HZ_IDLE(CONFIG_NO_HZ_IDLE=y),使空闲时中断降频。

    • 使用高精度定时器(High-Resolution Timers),只在需要时唤醒。

    • 优化后,功耗降低 15%。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
Interrupt Controller (GIC) 路由定时器中断到 CPU 中断优先级、亲和性设置
Clock Controller (CCU) 提供定时器输入时钟 时钟源稳定性、频率计算
Power Management 控制定时器在睡眠模式下的行为 空闲唤醒、低功耗模式
Scheduler 使用定时器中断进行调度 调度延迟、负载均衡

第九部分 eDP 控制器 (Analogix DP)

第一章 eDP 控制器在 Platform Bus 中的位置

eDP (Embedded DisplayPort) 是一种基于 DisplayPort 标准的嵌入式显示接口,广泛用于笔记本、平板和高端嵌入式设备。它通过高速串行链路(通常 1-4 条 Lane)传输视频和音频数据,支持更高的分辨率、刷新率和色深,且功耗较低。

在 Linux 中,eDP 控制器通常作为 Platform 设备挂载,通过 DRM 桥接器DP 辅助 设备注册到显示子系统。典型驱动是 analogix_dp,广泛用于 Rockchip、Exynos 等 SoC。

Linux 5.10 中 eDP 子系统的核心层次:

  1. DRM 核心层 (drivers/gpu/drm/):管理显示设备、CRTC、Encoder 等。

  2. eDP 桥接器驱动(本篇文章重点):具体的 eDP 控制器驱动(如 analogix_dp.c)。

  3. PHY 驱动:负责物理层信号调理和链路管理。

  4. 面板驱动:管理 eDP 面板的电源、时序、亮度和 EDID。


第二章 Linux 5.10 典型 eDP 控制器驱动 —— analogix_dp.c

Analogix DP 是广泛使用的 eDP 控制器 IP 核,支持 DP 1.2/1.4,集成 PSR (Panel Self Refresh) 和 AUX 通道。以下代码基于 Linux 5.10 drivers/gpu/drm/bridge/analogix/analogix_dp.c,展示核心逻辑。

2.1 硬件关键概念

  • DP 核心寄存器:配置链路速率、通道数、视频流、AUX 通信等。

  • AUX 通道:用于读取 EDID、配置面板、执行链路训练。

  • PHY (物理层):负责高速差分信号传输。

  • PSR (Panel Self Refresh):降低刷新率时节省功耗。

  • 中断:处理热插拔、链路训练状态变化、AUX 事务完成等事件。

2.2 核心代码

// 基于 Linux 5.10 drivers/gpu/drm/bridge/analogix/analogix_dp.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <drm/drm_bridge.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_modes.h>
​
/**
 * @brief Analogix eDP 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 eDP Controller 硬件实例。
 */
struct analogix_dp_device {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 */
    struct clk *clk;                /**< 核心时钟 */
    struct clk *pclk;               /**< 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct drm_bridge bridge;       /**< DRM 桥接器抽象 */
    struct drm_connector connector; /**< DRM 连接器抽象 */
    struct drm_dp_aux aux;          /**< DP AUX 通道抽象 */
    struct edid *edid;              /**< EDID 数据 */
    u32 lanes;                      /**< 链路通道数 (1/2/4) */
    u32 link_rate;                  /**< 链路速率 (1.62Gbps/2.7Gbps/5.4Gbps) */
    u8 *dpcd;                       /**< DPCD 数据 */
    bool psr_enabled;               /**< PSR 启用状态 */
    spinlock_t lock;                /**< 硬件保护锁 */
    struct work_struct hotplug_work; /**< 热插拔工作队列 */
};
​
/* 寄存器偏移量 (Analogix DP) */
#define DP_CTRL                     0x00
#define DP_STATUS                   0x04
#define DP_INT_EN                   0x08
#define DP_INT_STAT                 0x0C
#define DP_AUX_CMD                  0x10
#define DP_AUX_DATA                 0x14
#define DP_AUX_STATUS               0x18
#define DP_LINK_CTRL                0x1C
#define DP_LINK_STATUS              0x20
#define DP_VIDEO_CTRL               0x24
#define DP_VIDEO_STATUS             0x28
#define DP_PSR_CTRL                 0x2C
#define DP_PHY_CTRL                 0x30
​
/**
 * @brief 读取 AUX 通道数据。
 * 
 * @param aux 指向 drm_dp_aux 结构。
 * @param msg 指向 drm_dp_aux_msg。
 * @return 实际读取的字节数。
 */
static ssize_t analogix_dp_aux_transfer(struct drm_dp_aux *aux,
                                        struct drm_dp_aux_msg *msg)
{
    struct analogix_dp_device *dp = container_of(aux, struct analogix_dp_device, aux);
    u32 cmd, data;
    int i;
​
    // 1. 构建 AUX 命令
    cmd = (msg->request << 4) | (msg->address >> 8) | (msg->address << 8);
    cmd |= (1 << 16);  // 启用 AUX 传输
    writel(cmd, dp->base + DP_AUX_CMD);
​
    // 2. 等待 AUX 传输完成
    while (!(readl(dp->base + DP_AUX_STATUS) & (1 << 0)))
        ;
​
    // 3. 读取数据
    if (msg->request == DP_AUX_NATIVE_READ) {
        for (i = 0; i < msg->size; i++) {
            data = readl(dp->base + DP_AUX_DATA);
            msg->buffer[i] = data & 0xFF;
        }
    } else if (msg->request == DP_AUX_NATIVE_WRITE) {
        for (i = 0; i < msg->size; i++) {
            writel(msg->buffer[i], dp->base + DP_AUX_DATA);
        }
    }
​
    // 4. 清除中断
    writel(1 << 0, dp->base + DP_INT_STAT);
​
    return msg->size;
}
​
/**
 * @brief 读取面板的 EDID 数据。
 * 
 * @param dp 指向 analogix_dp_device 结构。
 * @return EDID 数据指针。
 */
static struct edid *analogix_dp_read_edid(struct analogix_dp_device *dp)
{
    struct edid *edid = NULL;
​
    // 1. 通过 AUX 通道读取 EDID
    edid = drm_dp_read_edid(&dp->aux, NULL);
    if (!edid) {
        dev_err(dp->dev, "Failed to read EDID\n");
        return NULL;
    }
​
    dp->edid = edid;
    return edid;
}
​
/**
 * @brief 配置 DP 链路。
 * 
 * @param dp 指向 analogix_dp_device 结构。
 * @param rate 链路速率。
 * @param lanes 通道数量。
 */
static void analogix_dp_set_link(struct analogix_dp_device *dp,
                                 u32 rate, u32 lanes)
{
    u32 ctrl;
​
    // 1. 配置链路速率 (1.62Gbps, 2.7Gbps, 5.4Gbps)
    ctrl = readl(dp->base + DP_LINK_CTRL);
    switch (rate) {
    case 162000:
        ctrl |= (0x00 << 8);
        break;
    case 270000:
        ctrl |= (0x01 << 8);
        break;
    case 540000:
        ctrl |= (0x02 << 8);
        break;
    default:
        ctrl |= (0x00 << 8);
        break;
    }
    writel(ctrl, dp->base + DP_LINK_CTRL);
​
    // 2. 配置通道数
    ctrl &= ~0x03;
    switch (lanes) {
    case 1:
        ctrl |= 0x00;
        break;
    case 2:
        ctrl |= 0x01;
        break;
    case 4:
        ctrl |= 0x03;
        break;
    default:
        ctrl |= 0x00;
        break;
    }
    writel(ctrl, dp->base + DP_LINK_CTRL);
​
    dp->link_rate = rate;
    dp->lanes = lanes;
}
​
/**
 * @brief 执行链路训练。
 * 
 * @param dp 指向 analogix_dp_device 结构。
 * @return 0 成功。
 */
static int analogix_dp_link_train(struct analogix_dp_device *dp)
{
    u32 status;
    int retry = 10;
​
    // 1. 设置初始链路速率和通道数
    analogix_dp_set_link(dp, 270000, 2);
​
    // 2. 启动链路训练
    u32 ctrl = readl(dp->base + DP_LINK_CTRL);
    ctrl |= (1 << 2);  // 启用链路训练
    writel(ctrl, dp->base + DP_LINK_CTRL);
​
    // 3. 等待链路稳定
    while (retry--) {
        msleep(100);
        status = readl(dp->base + DP_LINK_STATUS);
        if (status & (1 << 0))  // 链路稳定
            break;
    }
​
    if (retry <= 0) {
        dev_err(dp->dev, "Link training timeout\n");
        return -EIO;
    }
​
    return 0;
}
​
/**
 * @brief 启用 eDP 视频输出。
 * 
 * @param dp 指向 analogix_dp_device 结构。
 * @param mode 指向 drm_display_mode。
 */
static void analogix_dp_enable_video(struct analogix_dp_device *dp,
                                     struct drm_display_mode *mode)
{
    u32 ctrl;
​
    // 1. 配置视频时序
    // 设置 HBP、HFP、HSA、VBP、VFP、VSA 等
    // 此处简化
​
    // 2. 启用视频流
    ctrl = readl(dp->base + DP_VIDEO_CTRL);
    ctrl |= (1 << 0);  // 启用视频
    ctrl |= (1 << 1);  // 启用音频 (如果需要)
    writel(ctrl, dp->base + DP_VIDEO_CTRL);
}
​
/**
 * @brief 配置 PSR (Panel Self Refresh)。
 * 
 * @param dp 指向 analogix_dp_device 结构。
 * @param enable 启用/禁用 PSR。
 */
static void analogix_dp_psr_config(struct analogix_dp_device *dp, bool enable)
{
    u32 ctrl = readl(dp->base + DP_PSR_CTRL);
​
    if (enable) {
        ctrl |= (1 << 0);  // 启用 PSR
        ctrl |= (1 << 1);  // 启用 PSR 唤醒
    } else {
        ctrl &= ~(1 << 0);
        ctrl &= ~(1 << 1);
    }
    writel(ctrl, dp->base + DP_PSR_CTRL);
    dp->psr_enabled = enable;
}
​
/**
 * @brief eDP 中断处理函数。
 * 
 * 处理热插拔、链路状态变化、AUX 完成等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 analogix_dp_device 结构。
 */
static irqreturn_t analogix_dp_irq_handler(int irq, void *dev_id)
{
    struct analogix_dp_device *dp = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(dp->base + DP_INT_STAT);
​
    // 2. 处理热插拔中断
    if (int_stat & (1 << 0)) {
        writel(1 << 0, dp->base + DP_INT_STAT);
        // 调度热插拔工作队列
        schedule_work(&dp->hotplug_work);
    }
​
    // 3. 处理链路状态变化
    if (int_stat & (1 << 1)) {
        writel(1 << 1, dp->base + DP_INT_STAT);
        // 可能触发链路重新训练
        analogix_dp_link_train(dp);
    }
​
    // 4. 处理 AUX 完成
    if (int_stat & (1 << 2)) {
        writel(1 << 2, dp->base + DP_INT_STAT);
        // 唤醒等待 AUX 的线程
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 热插拔工作队列处理函数。
 * 
 * @param work 指向 work_struct。
 */
static void analogix_dp_hotplug_work(struct work_struct *work)
{
    struct analogix_dp_device *dp = container_of(work, struct analogix_dp_device, hotplug_work);
​
    // 1. 检查连接状态
    u32 status = readl(dp->base + DP_STATUS);
​
    if (status & (1 << 0)) {
        // 面板已连接,读取 EDID
        analogix_dp_read_edid(dp);
        // 通知 DRM 核心连接状态变化
        drm_helper_hpd_irq_event(dp->bridge.dev);
    } else {
        // 面板已断开
        dp->edid = NULL;
        drm_helper_hpd_irq_event(dp->bridge.dev);
    }
}
​
/**
 * @brief 启用 eDP 桥接器。
 * 
 * @param bridge 指向 drm_bridge。
 */
static void analogix_dp_bridge_enable(struct drm_bridge *bridge)
{
    struct analogix_dp_device *dp = bridge_to_analogix_dp(bridge);
​
    // 1. 初始化 PHY
    // 2. 配置链路训练
    analogix_dp_link_train(dp);
​
    // 3. 配置视频时序
    analogix_dp_enable_video(dp, &bridge->encoder->crtc->state->mode);
​
    // 4. 启用中断
    writel(0x07, dp->base + DP_INT_EN);
}
​
/**
 * @brief 禁用 eDP 桥接器。
 * 
 * @param bridge 指向 drm_bridge。
 */
static void analogix_dp_bridge_disable(struct drm_bridge *bridge)
{
    struct analogix_dp_device *dp = bridge_to_analogix_dp(bridge);
​
    // 1. 禁用中断
    writel(0x00, dp->base + DP_INT_EN);
​
    // 2. 关闭视频流
    u32 ctrl = readl(dp->base + DP_VIDEO_CTRL);
    ctrl &= ~(1 << 0);
    writel(ctrl, dp->base + DP_VIDEO_CTRL);
​
    // 3. 关闭链路
    ctrl = readl(dp->base + DP_LINK_CTRL);
    ctrl &= ~(1 << 2);
    writel(ctrl, dp->base + DP_LINK_CTRL);
​
    // 4. 关闭 PHY
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 eDP 控制器硬件,注册到 DRM。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int analogix_dp_probe(struct platform_device *pdev)
{
    struct analogix_dp_device *dp;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL);
    if (!dp)
        return -ENOMEM;
    platform_set_drvdata(pdev, dp);
    dp->dev = &pdev->dev;
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    dp->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dp->base))
        return PTR_ERR(dp->base);
​
    // 3. 获取时钟
    dp->clk = devm_clk_get(&pdev->dev, "clk");
    if (IS_ERR(dp->clk))
        return PTR_ERR(dp->clk);
    dp->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(dp->pclk))
        return PTR_ERR(dp->pclk);
​
    clk_prepare_enable(dp->clk);
    clk_prepare_enable(dp->pclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    dp->irq = irq;
​
    // 5. 初始化 DP 配置
    dp->lanes = 2;
    dp->link_rate = 270000;
    dp->psr_enabled = false;
​
    spin_lock_init(&dp->lock);
    INIT_WORK(&dp->hotplug_work, analogix_dp_hotplug_work);
​
    // 6. 初始化 AUX 通道
    dp->aux.dev = &pdev->dev;
    dp->aux.transfer = analogix_dp_aux_transfer;
    ret = drm_dp_aux_register(&dp->aux);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register AUX channel\n");
        goto err_clk;
    }
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, dp->irq, analogix_dp_irq_handler,
                           IRQF_SHARED, "analogix-dp", dp);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_aux;
    }
​
    // 8. 启用硬件
    writel(0x1, dp->base + DP_CTRL);
​
    // 9. 注册到 DRM (省略桥接器注册细节)
    dev_info(&pdev->dev, "Analogix eDP controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, dp->irq);
    return 0;
​
err_aux:
    drm_dp_aux_unregister(&dp->aux);
err_clk:
    clk_disable_unprepare(dp->pclk);
    clk_disable_unprepare(dp->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int analogix_dp_remove(struct platform_device *pdev)
{
    struct analogix_dp_device *dp = platform_get_drvdata(pdev);
​
    // 1. 注销 AUX 通道
    drm_dp_aux_unregister(&dp->aux);
​
    // 2. 取消工作队列
    cancel_work_sync(&dp->hotplug_work);
​
    // 3. 禁用硬件
    writel(0x0, dp->base + DP_CTRL);
​
    // 4. 禁用时钟
    clk_disable_unprepare(dp->pclk);
    clk_disable_unprepare(dp->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id analogix_dp_of_match[] = {
    { .compatible = "rockchip,rk3399-edp" },
    { .compatible = "analogix,dp" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, analogix_dp_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver analogix_dp_driver = {
    .probe = analogix_dp_probe,
    .remove = analogix_dp_remove,
    .driver = {
        .name = "analogix-dp",
        .of_match_table = analogix_dp_of_match,
    },
};
module_platform_driver(analogix_dp_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Analogix eDP Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 eDP 调试核心难点

3.1 屏幕不亮 (No Display)

现象:eDP 面板连接后,系统启动完成,但屏幕无显示。

原因

  • 链路训练失败(信号质量差)。

  • AUX 通道无法读取 EDID。

  • 面板电源时序错误。

调试方法

  1. 检查 eDP 状态

    devmem2 <base>+0x04  # DP_STATUS
    devmem2 <base>+0x20  # DP_LINK_STATUS
  2. 检查 AUX 通信

    # 手动读取 DPCD 寄存器
    dd if=/dev/drm_dp_aux0 bs=1 count=16 | hexdump -C
  3. 检查面板电源

    # 检查电源 GPIO 状态
    cat /sys/kernel/debug/gpio | grep panel

3.2 链路训练失败

现象dmesg 显示 "Link training timeout",屏幕黑屏。

原因

  • PHY 初始化和上电失败。

  • 信号完整性差(长线缆、电磁干扰)。

  • 面板不支持当前速率或通道数。

调试方法

  1. 降低链路速率

    # 在 DTS 中设置 link-rate = <162000>;
  2. 减少通道数

    # 在 DTS 中设置 lanes = <1>;
  3. 检查 PHY 状态

    # 读取 PHY 状态寄存器
    devmem2 <phy_base>+0x00

3.3 屏幕闪烁或杂点

现象:屏幕显示时出现闪烁或杂点,特别在滚动画面时。

原因

  • 视频时序参数 (HBP/HFP/HSA) 计算错误。

  • PSR 切换造成画面刷新异常。

  • 链路不稳导致数据错误。

调试方法

  1. 禁用 PSR

    echo 0 > /sys/class/drm/card0-eDP-1/psr
  2. 调整时序参数

    devmem2 <base>+0x24  # DP_VIDEO_CTRL
  3. 检查信号质量:使用示波器查看差分信号。

3.4 AUX 读取失败

现象dmesg 显示 "AUX transfer failed",EDID 读取失败。

原因

  • I2C 适配器未正确配置。

  • 面板 AUX 线路断开。

  • AUX 协议超时。

调试方法

  1. 手动 AUX 读

    # 使用 aux-tool 读取
    aux-tool read 0x00 16
  2. 增加 AUX 超时

    # 在驱动中增加超时值
    devmem2 <base>+0x10 0x1000  # DP_AUX_CMD
  3. 检查 DRM 辅助设备

    ls -l /dev/drm_dp_aux*

第四章 结合性能调试场景示例

场景:系统在空闲一段时间后唤醒,eDP 屏幕出现短暂闪烁,然后恢复正常。

分析流程

  1. 宏观层面(图谱的 CPU 层):

    • perf top 显示 analogix_dp_irq_handler 在唤醒后立即触发。

    • dmesg 显示 "PSR exit" 和 "Link training"。

  2. eDP 层(图谱的 Device Drivers -> eDP Controller 层):

    cat /sys/kernel/debug/dri/0/eDP-1/psr

    发现 PSR 状态在唤醒后频繁切换。

  3. 链路状态(Link 层):

    devmem2 <base>+0x20  # DP_LINK_STATUS

    发现唤醒后链路状态不稳定。

  4. 根本原因

    • 系统进入空闲时,eDP 进入 PSR 模式,降低刷新率以节省功耗。

    • 唤醒时,PSR 退出和链路重新训练过程未能完美同步。

    • 面板与控制器之间的 PSR 配置不匹配,导致唤醒时出现短暂画面异常。

  5. 解决方案

    • 在 DTS 中增加 PSR 退出延迟 (psr-exit-delay = <10> )。

    • 禁用 PSR 以测试闪烁是否消失。

    • 升级面板固件或修改面板驱动,优化 PSR 切换时间。

    • 优化唤醒时链路训练流程,增加重试次数。


第五章 与其他控制器的协同

控制器 协同方式 调试关键点
PHY 物理层信号调理 驱动强度、信号质量
AUX 通道 读取 EDID、配置面板 协议时序、ACK 响应
GPIO 控制面板电源、背光使能 电源时序、电平配置
PWM 控制背光亮度 PWM 频率、占空比设置
DRM 显示核心抽象 原子提交、VBLANK 同步

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐