第一部分 ISP 图像信号处理器

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

ISP (Image Signal Processor) 是 SoC 中专门用于处理摄像头图像数据的硬件模块。它接收来自 CSI 控制器的原始 Bayer 数据(RAW),经过黑电平校正、去马赛克、白平衡、色彩校正、降噪、锐化等处理,输出高质量的 YUV 或 RGB 图像供 CPU 或编码器使用。

在 Linux 5.10 中,ISP 子系统通常集成在 V4L2 框架下,位于 drivers/media/platform/,典型驱动包括 rockchip-isp.comap3isp.c 等。

1.1 硬件关键概念

  • ISP 流水线:多个硬件处理模块串联(黑电平、去马赛克、增益、色彩转换等)。

  • 统计引擎:收集曝光、白平衡统计数据,供 3A 算法使用。

  • DMA 引擎:支持多路输出(RAW、YUV、统计信息)。

  • 中断:帧开始/结束、统计完成、错误等。

  • V4L2 子设备:通过 V4L2 框架暴露控制接口。

1.2 核心代码

// 基于 Linux 5.10 drivers/media/platform/rockchip/isp/rockchip_isp.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-dma-contig.h>
​
/**
 * @brief Rockchip ISP 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 ISP 硬件实例。
 */
struct rkisp_device {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    int irq;                         /**< 中断号 */
    struct clk *aclk;                /**< 总线时钟 */
    struct clk *pclk;                /**< 总线时钟 (APB) */
    struct clk *cclk;                /**< 核心时钟 */
    struct device *dev;              /**< 设备指针 */
    struct v4l2_device v4l2_dev;     /**< V4L2 设备抽象 */
    struct video_device *vdev;       /**< 视频设备节点 */
    struct vb2_queue queue;          /**< 视频缓冲区队列 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    struct rkisp_buffer *curr_buf;   /**< 当前正在处理的缓冲区 */
    struct list_head buffers;        /**< 缓冲队列 */
    u32 frame_count;                 /**< 帧计数 */
    u32 isp_version;                 /**< ISP 硬件版本 */
};
​
/* 寄存器偏移量 (Rockchip ISP) */
#define ISP_CTRL                    0x00
#define ISP_STATUS                  0x04
#define ISP_INT_EN                  0x08
#define ISP_INT_STAT                0x0C
#define ISP_RAW_CFG                 0x10
#define ISP_DEMOSAIC_CFG            0x14
#define ISP_GAIN_CFG                0x18
#define ISP_CCM_CFG                 0x1C
#define ISP_DMA_CTRL                0x20
#define ISP_DMA_ADDR_Y              0x24
#define ISP_DMA_ADDR_CB             0x28
#define ISP_DMA_ADDR_CR             0x2C
#define ISP_STATS_BASE              0x30
#define ISP_STATS_LEN               0x34
​
/**
 * @brief 配置 ISP 黑电平校正。
 * 
 * @param isp 指向 rkisp_device 结构。
 * @param r 红色通道黑电平。
 * @param gr 绿色-红色通道黑电平。
 * @param gb 绿色-蓝色通道黑电平。
 * @param b 蓝色通道黑电平。
 */
static void rkisp_set_black_level(struct rkisp_device *isp,
                                  u16 r, u16 gr, u16 gb, u16 b)
{
    u32 reg = (r << 0) | (gr << 8) | (gb << 16) | (b << 24);
    writel(reg, isp->base + ISP_RAW_CFG);
}
​
/**
 * @brief 配置 ISP 增益。
 * 
 * @param isp 指向 rkisp_device 结构。
 * @param gain_r 红色增益。
 * @param gain_g 绿色增益。
 * @param gain_b 蓝色增益。
 */
static void rkisp_set_gain(struct rkisp_device *isp,
                           u16 gain_r, u16 gain_g, u16 gain_b)
{
    u32 reg = (gain_r << 0) | (gain_g << 8) | (gain_b << 16);
    writel(reg, isp->base + ISP_GAIN_CFG);
}
​
/**
 * @brief 配置 ISP 颜色校正矩阵 (CCM)。
 * 
 * @param isp 指向 rkisp_device 结构。
 * @param ccm 3x3 矩阵系数。
 */
static void rkisp_set_ccm(struct rkisp_device *isp, u32 ccm[3][3])
{
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            writel(ccm[i][j], isp->base + ISP_CCM_CFG + (i * 3 + j) * 4);
        }
    }
}
​
/**
 * @brief 启动 ISP 视频流。
 * 
 * @param isp 指向 rkisp_device 结构。
 */
static void rkisp_start_streaming(struct rkisp_device *isp)
{
    u32 ctrl;
​
    // 1. 检查是否有缓冲区可用
    if (list_empty(&isp->buffers)) {
        dev_err(isp->dev, "No buffers available\n");
        return;
    }
​
    // 2. 设置 DMA 地址
    isp->curr_buf = list_first_entry(&isp->buffers,
                                     struct rkisp_buffer, list);
    list_del_init(&isp->curr_buf->list);
​
    dma_addr_t dma_addr = vb2_dma_contig_plane_dma_addr(&isp->curr_buf->vbuf.vb2_buf, 0);
    writel(dma_addr, isp->base + ISP_DMA_ADDR_Y);
​
    // 3. 启用 ISP 流水线
    ctrl = (1 << 0) | (1 << 1) | (1 << 2);  // 启用 RAW、去马赛克、CCM
    writel(ctrl, isp->base + ISP_CTRL);
​
    // 4. 启用中断
    writel(0x1F, isp->base + ISP_INT_EN);
}
​
/**
 * @brief 停止 ISP 视频流。
 * 
 * @param isp 指向 rkisp_device 结构。
 */
static void rkisp_stop_streaming(struct rkisp_device *isp)
{
    // 1. 禁用中断
    writel(0x0, isp->base + ISP_INT_EN);
​
    // 2. 禁用 ISP
    writel(0x0, isp->base + ISP_CTRL);
​
    // 3. 等待硬件停止
    while (readl(isp->base + ISP_STATUS) & 0x1)
        ;
}
​
/**
 * @brief ISP 中断处理函数。
 * 
 * 处理帧完成、统计完成、错误等。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rkisp_device 结构。
 */
static irqreturn_t rkisp_irq_handler(int irq, void *dev_id)
{
    struct rkisp_device *isp = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(isp->base + ISP_INT_STAT);
​
    // 2. 处理帧完成中断
    if (int_stat & (1 << 0)) {
        writel(1 << 0, isp->base + ISP_INT_STAT);
​
        // 完成当前缓冲区
        if (isp->curr_buf) {
            isp->curr_buf->vbuf.sequence = isp->frame_count++;
            vb2_buffer_done(&isp->curr_buf->vbuf.vb2_buf, VB2_BUF_STATE_DONE);
        }
​
        // 取出下一个缓冲区
        if (!list_empty(&isp->buffers)) {
            isp->curr_buf = list_first_entry(&isp->buffers,
                                             struct rkisp_buffer, list);
            list_del_init(&isp->curr_buf->list);
            dma_addr_t dma_addr = vb2_dma_contig_plane_dma_addr(&isp->curr_buf->vbuf.vb2_buf, 0);
            writel(dma_addr, isp->base + ISP_DMA_ADDR_Y);
        } else {
            isp->curr_buf = NULL;
        }
    }
​
    // 3. 处理统计完成中断
    if (int_stat & (1 << 1)) {
        writel(1 << 1, isp->base + ISP_INT_STAT);
        // 读取统计数据
    }
​
    // 4. 处理错误中断
    if (int_stat & (1 << 2)) {
        dev_err(isp->dev, "ISP error detected\n");
        writel(1 << 2, isp->base + ISP_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 ISP 硬件,注册 V4L2 设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rkisp_probe(struct platform_device *pdev)
{
    struct rkisp_device *isp;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);
    if (!isp)
        return -ENOMEM;
    platform_set_drvdata(pdev, isp);
    isp->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;
    }
    isp->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(isp->base))
        return PTR_ERR(isp->base);
​
    // 3. 获取时钟
    isp->aclk = devm_clk_get(&pdev->dev, "aclk");
    if (IS_ERR(isp->aclk))
        return PTR_ERR(isp->aclk);
    isp->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(isp->pclk))
        return PTR_ERR(isp->pclk);
    isp->cclk = devm_clk_get(&pdev->dev, "cclk");
    if (IS_ERR(isp->cclk))
        return PTR_ERR(isp->cclk);
​
    clk_prepare_enable(isp->aclk);
    clk_prepare_enable(isp->pclk);
    clk_prepare_enable(isp->cclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    isp->irq = irq;
​
    // 5. 初始化 V4L2
    ret = v4l2_device_register(&pdev->dev, &isp->v4l2_dev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register V4L2 device\n");
        goto err_clk;
    }
​
    spin_lock_init(&isp->lock);
    INIT_LIST_HEAD(&isp->buffers);
​
    // 6. 注册中断
    ret = devm_request_irq(&pdev->dev, isp->irq, rkisp_irq_handler,
                           IRQF_SHARED, "rkisp", isp);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_v4l2;
    }
​
    // 7. 初始化 VB2 队列
    isp->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    isp->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
    isp->queue.ops = &rkisp_qops;
    isp->queue.mem_ops = &vb2_dma_contig_memops;
    isp->queue.drv_priv = isp;
    ret = vb2_queue_init(&isp->queue);
    if (ret) {
        dev_err(&pdev->dev, "Failed to init vb2 queue\n");
        goto err_v4l2;
    }
​
    // 8. 注册视频设备
    isp->vdev = video_device_alloc();
    if (!isp->vdev) {
        dev_err(&pdev->dev, "Failed to alloc video device\n");
        ret = -ENOMEM;
        goto err_v4l2;
    }
    isp->vdev->v4l2_dev = &isp->v4l2_dev;
    isp->vdev->queue = &isp->queue;
    isp->vdev->fops = &rkisp_fops;
    isp->vdev->ioctl_ops = &rkisp_ioctl_ops;
    ret = video_register_device(isp->vdev, VFL_TYPE_VIDEO, -1);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register video device\n");
        goto err_video;
    }
​
    dev_info(&pdev->dev, "Rockchip ISP registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, isp->irq);
    return 0;
​
err_video:
    video_device_release(isp->vdev);
err_v4l2:
    v4l2_device_unregister(&isp->v4l2_dev);
err_clk:
    clk_disable_unprepare(isp->cclk);
    clk_disable_unprepare(isp->pclk);
    clk_disable_unprepare(isp->aclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rkisp_remove(struct platform_device *pdev)
{
    struct rkisp_device *isp = platform_get_drvdata(pdev);
​
    // 1. 停止 ISP
    rkisp_stop_streaming(isp);
​
    // 2. 注销视频设备
    video_unregister_device(isp->vdev);
​
    // 3. 注销 V4L2 设备
    v4l2_device_unregister(&isp->v4l2_dev);
​
    // 4. 禁用时钟
    clk_disable_unprepare(isp->cclk);
    clk_disable_unprepare(isp->pclk);
    clk_disable_unprepare(isp->aclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rkisp_of_match[] = {
    { .compatible = "rockchip,rk3399-isp" },
    { .compatible = "rockchip,rk3568-isp" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rkisp_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rkisp_driver = {
    .probe = rkisp_probe,
    .remove = rkisp_remove,
    .driver = {
        .name = "rkisp",
        .of_match_table = rkisp_of_match,
    },
};
module_platform_driver(rkisp_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip ISP Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 ISP 调试核心难点

3.1 图像颜色异常

现象:输出的图像整体偏色,或某些区域颜色错误。

原因

  • 白平衡参数错误。

  • 颜色校正矩阵 (CCM) 配置错误。

  • 黑电平校正值不正确。

调试方法

  1. 检查黑电平

    devmem2 <base>+0x10  # ISP_RAW_CFG
  2. 检查 CCM 矩阵

    # 读取 CCM 寄存器
    devmem2 <base>+0x1C
    devmem2 <base>+0x20
  3. 获取原始数据

    # 直接捕获 RAW 数据,用工具查看
    v4l2-ctl --device /dev/video0 --stream-mmap --stream-to=/tmp/raw.raw

3.2 图像过曝或欠曝

现象:图像整体太亮或太暗,细节丢失。

原因

  • 自动曝光算法未正确收敛。

  • 增益设置错误。

  • 曝光时间设置错误。

调试方法

  1. 检查增益

    devmem2 <base>+0x18  # ISP_GAIN_CFG
  2. 检查曝光时间

    # 从传感器驱动读取曝光时间
    cat /sys/class/video4linux/video0/controls
  3. 手动设置曝光

    v4l2-ctl -d /dev/v4l-subdev0 -c exposure=1000

3.3 统计数据读取失败

现象:3A 算法无法获取统计数据,自动对焦/曝光不工作。

原因

  • 统计 DMA 地址未设置。

  • 统计缓冲区未准备好。

  • 统计中断未触发。

调试方法

  1. 检查统计状态

    devmem2 <base>+0x30  # ISP_STATS_BASE
  2. 检查统计中断

    cat /proc/interrupts | grep isp

3.4 ISP 处理延时过高

现象:V4L2 缓冲区长时间未返回,perf 显示处理延迟。

原因

  • 缓冲区不足。

  • 硬件处理流水线被阻塞。

  • DMA 传输效率低。

调试方法

  1. 检查缓冲区队列

    # 查看 VB2 队列状态
    cat /sys/kernel/debug/v4l2/dev0/queue
  2. 减少处理负载

    # 降低输出分辨率
    v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080

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

场景:ISP 处理 4K 视频时,帧率只能达到 15fps,预期 30fps。

分析流程

  1. 宏观层面(CPU 层):

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

    • iostat 显示内存带宽接近饱和。

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

    devmem2 <base>+0x04  # ISP_STATUS

    显示 ISP 流水线处于忙碌状态。

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

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

    显示 DMA 传输延迟较高。

  4. 根本原因

    • 4K 分辨率需要大量内存带宽 (4096216016bit*30fps ≈ 4.2GB/s)。

    • 嵌入式 DDR 带宽有限,与 GPU 和 CPU 竞争。

    • ISP DMA 请求被其他高优先级设备抢占。

  5. 解决方案

    • 降低帧率到 30fps 或降低输出分辨率。

    • 使用 YUV422 格式代替 RAW 格式减少带宽。

    • 调整 ISP 时钟频率,提高处理速度。

    • 使用 DMA 缓冲池减少分配开销。


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

控制器 协同方式 调试关键点
CSI 接收传感器原始数据 分辨率匹配、格式转换
DMA 传输处理后的图像数据 带宽分配、地址对齐
Memory Controller 提供高带宽内存 频率设置、QoS 控制
Video Encoder 处理 ISP 输出数据 编码质量、帧率同步

第二部分 SDHCI 控制器 (Synopsys DWC MSHC)

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

SDHCI (Secure Digital Host Controller Interface) 是 SD 卡和 eMMC 存储设备的标准化主机接口。在嵌入式 SoC 中,SDHCI 控制器通常作为 Platform 设备挂载在内部总线上,通过 SD 总线协议与存储设备通信。

在 Linux 5.10 中,SDHCI 子系统是 MMC 子系统下的一个标准框架,位于 drivers/mmc/host/,核心层次分为三层:

  1. MMC 核心层 (drivers/mmc/core/):管理 MMC/SD 设备状态、分区、块 I/O 请求。

  2. SDHCI 控制器驱动(本篇文章重点):具体的 SoC SDHCI 主机驱动(如 sdhci-of-dwcmshc.csdhci-pltfm.c)。

  3. SDHCI 标准层 (drivers/mmc/host/sdhci.c):提供通用的 SDHCI 操作、中断处理、DMA 管理。


第二章 Linux 5.10 典型 SDHCI 控制器驱动 —— sdhci-of-dwcmshc.c

DesignWare DWC MSHC (Mobile Storage Host Controller) 是最广泛使用的 eMMC/SD 控制器 IP 核之一。以下代码基于 Linux 5.10 drivers/mmc/host/sdhci-of-dwcmshc.c,展示核心逻辑。

2.1 硬件关键概念

  • SDHCI 标准寄存器:控制寄存器、状态寄存器、命令寄存器、数据寄存器。

  • SDHCI 扩展寄存器:DWC 特有的寄存器,用于配置时钟、DMA、中断等。

  • ADMA2 引擎:支持高级 DMA 传输,使用描述符链。

  • 时钟:需要 ahb_clk(总线时钟)和 core_clk(SD 核心时钟)。

  • 中断:处理命令完成、数据传输、错误等事件。

2.2 核心代码

// 基于 Linux 5.10 drivers/mmc/host/sdhci-of-dwcmshc.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mmc/host.h>
#include <linux/dma-mapping.h>
#include "sdhci-pltfm.h"
​
/**
 * @brief DesignWare MSHC SDHCI 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 SDHCI Controller 硬件实例。
 */
struct dwcmshc_priv {
    void __iomem *base;            /**< 映射后的寄存器基址 */
    int irq;                       /**< 中断号 */
    struct clk *ahb_clk;           /**< AHB 总线时钟 */
    struct clk *core_clk;          /**< SD 核心时钟 */
    struct clk *bus_clk;           /**< 总线时钟 */
    struct sdhci_host *host;       /**< SDHCI 主机抽象 */
    struct device *dev;            /**< 设备指针 */
    spinlock_t lock;               /**< 硬件保护锁 */
    u32 version;                   /**< 控制器版本 */
    struct sdhci_pltfm_data *pdata; /**< 平台特定数据 */
};
​
/* 寄存器偏移量 (DWC MSHC 扩展) */
#define DWC_MSHC_CTRL               0x30
#define DWC_MSHC_CLK_SRC            0x34
#define DWC_MSHC_PWR_EN             0x38
#define DWC_MSHC_TMOUT              0x3C
#define DWC_MSHC_BLK_CNT            0x40
#define DWC_MSHC_AXI_CTRL           0x44
#define DWC_MSHC_CAP                0x48
#define DWC_MSHC_ADMA_CTRL          0x4C
#define DWC_MSHC_ADMA_LOW           0x50
#define DWC_MSHC_ADMA_HIGH          0x54
​
/**
 * @brief 初始化 DWC MSHC 硬件。
 * 
 * @param priv 指向 dwcmshc_priv 结构。
 */
static void dwcmshc_hw_init(struct dwcmshc_priv *priv)
{
    u32 ctrl;
​
    // 1. 启用 ADMA2 模式
    ctrl = readl(priv->base + DWC_MSHC_ADMA_CTRL);
    ctrl |= (1 << 0);
    writel(ctrl, priv->base + DWC_MSHC_ADMA_CTRL);
​
    // 2. 设置 ADMA 描述符表地址 (由 sdhci 核心分配)
    dma_addr_t adma_addr = sdhci_get_adma_addr(priv->host);
    writel(lower_32_bits(adma_addr), priv->base + DWC_MSHC_ADMA_LOW);
    writel(upper_32_bits(adma_addr), priv->base + DWC_MSHC_ADMA_HIGH);
​
    // 3. 配置 AXI 总线控制 (提升性能)
    ctrl = readl(priv->base + DWC_MSHC_AXI_CTRL);
    ctrl |= (0x03 << 16);  // 增加 AXI 突发长度
    writel(ctrl, priv->base + DWC_MSHC_AXI_CTRL);
}
​
/**
 * @brief 设置 SD 时钟频率。
 * 
 * @param host 指向 sdhci_host 结构。
 * @param clock 目标时钟频率 (Hz)。
 */
static void dwcmshc_set_clock(struct sdhci_host *host, unsigned int clock)
{
    struct dwcmshc_priv *priv = sdhci_priv(host);
    unsigned long clk_rate;
    u32 div, ctrl;
​
    if (clock == 0) {
        // 关闭时钟
        writel(0, priv->base + DWC_MSHC_CLK_SRC);
        return;
    }
​
    clk_rate = clk_get_rate(priv->core_clk);
    div = clk_rate / clock;
    if (div < 1) div = 1;
    if (div > 0xFF) div = 0xFF;
​
    // 设置分频器
    ctrl = readl(priv->base + DWC_MSHC_CLK_SRC);
    ctrl &= ~0xFF;
    ctrl |= div;
    writel(ctrl, priv->base + DWC_MSHC_CLK_SRC);
​
    // 启用时钟
    ctrl |= (1 << 8);
    writel(ctrl, priv->base + DWC_MSHC_CLK_SRC);
}
​
/**
 * @brief SDHCI 平台探测函数。
 * 
 * 初始化 SDHCI 控制器硬件,注册到 MMC 核心。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int dwcmshc_probe(struct platform_device *pdev)
{
    struct dwcmshc_priv *priv;
    struct sdhci_host *host;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    platform_set_drvdata(pdev, priv);
    priv->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;
    }
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);
​
    // 3. 获取时钟
    priv->ahb_clk = devm_clk_get(&pdev->dev, "ahb");
    if (IS_ERR(priv->ahb_clk))
        return PTR_ERR(priv->ahb_clk);
    priv->core_clk = devm_clk_get(&pdev->dev, "core");
    if (IS_ERR(priv->core_clk))
        return PTR_ERR(priv->core_clk);
    priv->bus_clk = devm_clk_get_optional(&pdev->dev, "bus");
    if (IS_ERR(priv->bus_clk))
        return PTR_ERR(priv->bus_clk);
​
    clk_prepare_enable(priv->ahb_clk);
    clk_prepare_enable(priv->core_clk);
    if (priv->bus_clk)
        clk_prepare_enable(priv->bus_clk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    priv->irq = irq;
​
    // 5. 初始化 SDHCI 主机
    host = sdhci_pltfm_init(pdev, &priv->pdata, sizeof(*priv));
    if (IS_ERR(host)) {
        ret = PTR_ERR(host);
        goto err_clk;
    }
    priv->host = host;
    host->mmc->parent = &pdev->dev;
​
    // 6. 设置 SDHCI 操作回调
    host->ops = &dwcmshc_ops;
    host->mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
    host->mmc->caps |= MMC_CAP_ERASE | MMC_CAP_CMD23;
    host->mmc->max_seg_size = PAGE_SIZE * 64;
    host->mmc->max_req_size = PAGE_SIZE * 64;
​
    spin_lock_init(&priv->lock);
​
    // 7. 硬件初始化
    dwcmshc_hw_init(priv);
​
    // 8. 注册中断
    ret = devm_request_irq(&pdev->dev, priv->irq, sdhci_irq_handler,
                           IRQF_SHARED, "dwcmshc", host);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_sdhci;
    }
​
    // 9. 注册 MMC 主机
    ret = sdhci_add_host(host);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add MMC host\n");
        goto err_sdhci;
    }
​
    dev_info(&pdev->dev, "DWC MSHC SDHCI controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, priv->irq);
    return 0;
​
err_sdhci:
    sdhci_pltfm_free(pdev);
err_clk:
    if (priv->bus_clk)
        clk_disable_unprepare(priv->bus_clk);
    clk_disable_unprepare(priv->core_clk);
    clk_disable_unprepare(priv->ahb_clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int dwcmshc_remove(struct platform_device *pdev)
{
    struct dwcmshc_priv *priv = platform_get_drvdata(pdev);
​
    // 1. 移除 SDHCI 主机
    sdhci_remove_host(priv->host, 0);
​
    // 2. 释放 SDHCI 平台资源
    sdhci_pltfm_free(pdev);
​
    // 3. 禁用时钟
    if (priv->bus_clk)
        clk_disable_unprepare(priv->bus_clk);
    clk_disable_unprepare(priv->core_clk);
    clk_disable_unprepare(priv->ahb_clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id dwcmshc_of_match[] = {
    { .compatible = "rockchip,rk3399-dwcmshc" },
    { .compatible = "snps,dwcmshc" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dwcmshc_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver dwcmshc_driver = {
    .probe = dwcmshc_probe,
    .remove = dwcmshc_remove,
    .driver = {
        .name = "dwcmshc",
        .of_match_table = dwcmshc_of_match,
    },
};
module_platform_driver(dwcmshc_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("DesignWare DWC MSHC SDHCI Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 SDHCI 调试核心难点

3.1 卡检测失败

现象:插入 SD 卡后,系统无任何响应,lsblk 不显示设备。

原因

  • 卡检测 GPIO 未正确配置。

  • 卡供电未使能。

  • 卡检测中断未触发。

调试方法

  1. 检查卡检测 GPIO

    cat /sys/kernel/debug/gpio | grep cd
  2. 强制检测卡

    echo 1 > /sys/devices/platform/*dwcmshc/mmc_host/mmc0/scan
  3. 检查卡供电

    cat /sys/kernel/debug/regulator/regulator_summary | grep mmc

3.2 数据传输 CRC 错误

现象dmesg 频繁出现 "CRC error",读写速度慢。

原因

  • SD 总线信号质量差。

  • 时钟频率过高。

  • 布线问题。

调试方法

  1. 降低时钟频率

    # 在 DTS 中设置 max-frequency = <10000000>;
  2. 降低总线宽度

    echo 1 > /sys/devices/platform/*dwcmshc/mmc_host/mmc0/mmc0:0001/bus_width
  3. 检查信号完整性:用示波器查看 SD 时钟和数据线。

3.3 DMA 传输失败

现象:大量数据传输时出错,dmesg 显示 "DMA error"。

原因

  • ADMA 描述符表对齐错误。

  • DMA 地址超过 32 位范围。

  • DMA 中断处理超时。

调试方法

  1. 检查 ADMA 描述符

    devmem2 <base>+0x50  # ADMA_LOW
  2. 使用 PIO 模式测试:在驱动中强制禁用 DMA。

  3. 调试 DMA 中断

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

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

场景:使用 eMMC 读写文件时,速度只有 50MB/s,理论上应达 300MB/s。

分析流程

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

    • perf top 显示 sdhci_irq_handler 占用 CPU 约 30%。

    • iostat -x 1 显示 mmcblk0 的 r/sw/s 较高,但吞吐量低。

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

    cat /sys/kernel/debug/mmc0/ios

    发现当前频率为 50MHz,总线宽度为 4-bit,理论带宽约 200MB/s。

  3. 时钟配置(Clock 层):

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

    发现 core_clk 频率仅为 100MHz。

  4. 根本原因

    • eMMC 芯片支持 HS200 模式(200MHz),但实际时钟被限制在 50MHz。

    • SDHCI 驱动中未正确启用 HS200 模式。

    • 未执行调优 (Tuning) 流程。

  5. 解决方案

    • 在 DTS 中设置 cap-mmc-hw-resetmmc-hs200-1_8v

    • 手动触发调优:echo 1 > /sys/kernel/debug/mmc0/execute_tuning

    • 调整时钟频率到 200MHz。

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


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

控制器 协同方式 调试关键点
PINCTRL 配置 SD 总线引脚功能 引脚复用、驱动强度
Clock Controller 提供 SD 核心时钟 频率匹配、电压调整
DMA 数据传输搬运 描述符链、中断聚合
Regulator 提供卡电源 (vmmc/vqmmc) 电压稳定、上电时序

第三部分 GPU 控制器 (Mali)

第一章 GPU 在 Platform Bus 中的位置

GPU 是现代 SoC 中最重要的协处理器之一,负责图形渲染、计算加速和多媒体处理。在嵌入式设备中,Mali 系列 GPU 是最广泛使用的 IP 核之一,通常作为 Platform 设备挂载在内部总线上,通过专用 DMA 和中断机制与 CPU 协同工作。

在 Linux 5.10 中,Mali GPU 驱动位于 drivers/gpu/drm/panfrost/(Panfrost 是 Mali 的开源驱动),支持 Mali T860、T880、G52、G76 等型号。

1.1 硬件关键概念

  • GPU 核心寄存器:管理 GPU 电源、时钟、工作状态。

  • MMU (Memory Management Unit):GPU 专用的内存管理单元,支持虚拟地址。

  • Shader 引擎:执行图形和计算任务。

  • 调度器:管理任务队列、优先级、上下文切换。

  • 中断:处理作业完成、MMU 缺页、错误等事件。

  • DVFS (Dynamic Voltage and Frequency Scaling):动态电源管理。

1.2 核心代码

// 基于 Linux 5.10 drivers/gpu/drm/panfrost/panfrost_device.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_device.h>
#include <drm/drm_file.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_gem_shmem_helper.h>
​
/**
 * @brief Mali GPU 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 GPU 硬件实例。
 */
struct panfrost_device {
    void __iomem *base;              /**< 映射后的寄存器基址 */
    void __iomem *mmu_base;          /**< MMU 寄存器基址 */
    void __iomem *pm_base;           /**< 电源管理寄存器基址 */
    int irq;                         /**< GPU 中断号 */
    int irq_mmu;                     /**< MMU 中断号 */
    int irq_job;                     /**< 作业中断号 */
    struct clk *clk;                 /**< 核心时钟 */
    struct clk *bus_clk;             /**< 总线时钟 */
    struct device *dev;              /**< 设备指针 */
    struct drm_device *drm;          /**< DRM 设备指针 */
    struct panfrost_job *curr_job;   /**< 当前运行的作业 */
    spinlock_t lock;                 /**< 硬件保护锁 */
    u32 gpu_version;                 /**< GPU 版本号 */
    u32 num_cores;                   /**< Shader 核心数量 */
    u32 num_tiler;                   /**< Tiler 单元数量 */
    atomic_t job_count;              /**< 作业计数 */
};
​
/* 寄存器偏移量 (Mali GPU) */
#define GPU_CTRL                    0x00
#define GPU_STATUS                  0x04
#define GPU_INT_EN                  0x08
#define GPU_INT_STAT                0x0C
#define GPU_SHADER_CONFIG           0x10
#define GPU_TILER_CONFIG            0x14
#define GPU_MMU_CONFIG              0x18
#define GPU_PWR_CTRL                0x20
#define GPU_PWR_STATUS              0x24
#define GPU_PWR_CTRL2               0x28
#define GPU_MMU_PAGE_TABLE          0x30
#define GPU_MMU_STATUS              0x34
​
/**
 * @brief 初始化 GPU 电源管理。
 * 
 * @param pfdev 指向 panfrost_device 结构。
 * @return 0 成功。
 */
static int panfrost_gpu_power_init(struct panfrost_device *pfdev)
{
    u32 pwr_ctrl;
​
    // 1. 读取电源状态
    pwr_ctrl = readl(pfdev->pm_base + GPU_PWR_STATUS);
    if (!(pwr_ctrl & 0x1)) {
        dev_err(pfdev->dev, "GPU power domain not ready\n");
        return -EIO;
    }
​
    // 2. 启用所有 Shader 核心
    pwr_ctrl = readl(pfdev->pm_base + GPU_PWR_CTRL);
    pwr_ctrl |= 0xFFFF;
    writel(pwr_ctrl, pfdev->pm_base + GPU_PWR_CTRL);
​
    // 3. 等待电源稳定
    msleep(100);
​
    return 0;
}
​
/**
 * @brief 初始化 GPU MMU。
 * 
 * @param pfdev 指向 panfrost_device 结构。
 * @param pgd 页表基地址。
 */
static void panfrost_mmu_init(struct panfrost_device *pfdev, dma_addr_t pgd)
{
    // 1. 设置页表地址
    writel(lower_32_bits(pgd), pfdev->mmu_base + GPU_MMU_PAGE_TABLE);
    writel(upper_32_bits(pgd), pfdev->mmu_base + GPU_MMU_PAGE_TABLE + 4);
​
    // 2. 启用 MMU
    u32 mmu_cfg = readl(pfdev->mmu_base + GPU_MMU_CONFIG);
    mmu_cfg |= (1 << 0);
    writel(mmu_cfg, pfdev->mmu_base + GPU_MMU_CONFIG);
​
    // 3. 检查 MMU 状态
    u32 status = readl(pfdev->mmu_base + GPU_MMU_STATUS);
    if (!(status & (1 << 0))) {
        dev_err(pfdev->dev, "MMU not ready\n");
    }
}
​
/**
 * @brief 提交 GPU 作业。
 * 
 * @param pfdev 指向 panfrost_device 结构。
 * @param job 指向 panfrost_job 结构。
 * @return 0 成功。
 */
static int panfrost_gpu_submit_job(struct panfrost_device *pfdev,
                                   struct panfrost_job *job)
{
    // 1. 检查 GPU 是否空闲
    if (atomic_read(&pfdev->job_count) > 0) {
        // 需要排队
        return -EBUSY;
    }
​
    // 2. 设置作业上下文
    // 将作业参数写入 GPU 寄存器
    writel(job->fragment_shader, pfdev->base + 0x100);
    writel(job->vertex_shader, pfdev->base + 0x104);
    writel(job->fragment_begin, pfdev->base + 0x108);
    writel(job->vertex_begin, pfdev->base + 0x10C);
    writel(job->frame_buffer, pfdev->base + 0x110);
    writel(job->render_target, pfdev->base + 0x114);
​
    // 3. 启用 GPU 中断
    writel(0x1F, pfdev->base + GPU_INT_EN);
​
    // 4. 启动 GPU 作业
    u32 ctrl = readl(pfdev->base + GPU_CTRL);
    ctrl |= (1 << 0);  // 启用 GPU
    ctrl |= (1 << 1);  // 启用作业
    writel(ctrl, pfdev->base + GPU_CTRL);
​
    pfdev->curr_job = job;
    atomic_inc(&pfdev->job_count);
​
    return 0;
}
​
/**
 * @brief GPU 中断处理函数。
 * 
 * 处理作业完成、MMU 缺页、电源错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 panfrost_device 结构。
 */
static irqreturn_t panfrost_gpu_irq_handler(int irq, void *dev_id)
{
    struct panfrost_device *pfdev = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(pfdev->base + GPU_INT_STAT);
​
    // 2. 处理作业完成中断
    if (int_stat & (1 << 0)) {
        writel(1 << 0, pfdev->base + GPU_INT_STAT);
        // 通知上层作业完成
        if (pfdev->curr_job) {
            pfdev->curr_job->cb(pfdev->curr_job);
            pfdev->curr_job = NULL;
            atomic_dec(&pfdev->job_count);
        }
        // 检查队列中是否有待处理作业
    }
​
    // 3. 处理 MMU 缺页中断
    if (int_stat & (1 << 1)) {
        dev_err(pfdev->dev, "GPU MMU fault detected\n");
        writel(1 << 1, pfdev->base + GPU_INT_STAT);
        // 读取 MMU 状态寄存器定位错误地址
        u32 fault_addr = readl(pfdev->mmu_base + 0x38);
        dev_err(pfdev->dev, "Fault address: 0x%08x\n", fault_addr);
    }
​
    // 4. 处理电源错误
    if (int_stat & (1 << 2)) {
        dev_err(pfdev->dev, "GPU power error\n");
        writel(1 << 2, pfdev->base + GPU_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 GPU 控制器硬件,注册到 DRM。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int panfrost_probe(struct platform_device *pdev)
{
    struct panfrost_device *pfdev;
    struct resource *res;
    struct drm_device *drm;
    int ret, irq, irq_mmu, irq_job;
​
    // 1. 分配私有数据结构
    pfdev = devm_kzalloc(&pdev->dev, sizeof(*pfdev), GFP_KERNEL);
    if (!pfdev)
        return -ENOMEM;
    platform_set_drvdata(pdev, pfdev);
    pfdev->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;
    }
    pfdev->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(pfdev->base))
        return PTR_ERR(pfdev->base);
​
    // 3. 获取 MMU 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    if (res) {
        pfdev->mmu_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(pfdev->mmu_base))
            return PTR_ERR(pfdev->mmu_base);
    } else {
        pfdev->mmu_base = pfdev->base + 0x2000;
    }
​
    // 4. 获取电源管理资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
    if (res) {
        pfdev->pm_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(pfdev->pm_base))
            return PTR_ERR(pfdev->pm_base);
    } else {
        pfdev->pm_base = pfdev->base + 0x1000;
    }
​
    // 5. 获取时钟
    pfdev->clk = devm_clk_get(&pdev->dev, "core");
    if (IS_ERR(pfdev->clk))
        return PTR_ERR(pfdev->clk);
    pfdev->bus_clk = devm_clk_get(&pdev->dev, "bus");
    if (IS_ERR(pfdev->bus_clk))
        return PTR_ERR(pfdev->bus_clk);
​
    clk_prepare_enable(pfdev->clk);
    clk_prepare_enable(pfdev->bus_clk);
​
    // 6. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    pfdev->irq = irq;
​
    irq_mmu = platform_get_irq(pdev, 1);
    if (irq_mmu < 0) {
        ret = irq_mmu;
        goto err_clk;
    }
    pfdev->irq_mmu = irq_mmu;
​
    irq_job = platform_get_irq(pdev, 2);
    if (irq_job < 0) {
        ret = irq_job;
        goto err_clk;
    }
    pfdev->irq_job = irq_job;
​
    // 7. 初始化硬件
    ret = panfrost_gpu_power_init(pfdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to initialize GPU power\n");
        goto err_clk;
    }
​
    // 8. 读取 GPU 信息
    pfdev->gpu_version = readl(pfdev->base + 0x00);
    pfdev->num_cores = readl(pfdev->base + GPU_SHADER_CONFIG);
    pfdev->num_tiler = readl(pfdev->base + GPU_TILER_CONFIG);
​
    spin_lock_init(&pfdev->lock);
    atomic_set(&pfdev->job_count, 0);
​
    // 9. 注册中断
    ret = devm_request_irq(&pdev->dev, pfdev->irq, panfrost_gpu_irq_handler,
                           IRQF_SHARED, "panfrost-gpu", pfdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request GPU IRQ\n");
        goto err_clk;
    }
​
    ret = devm_request_irq(&pdev->dev, pfdev->irq_mmu, panfrost_mmu_irq_handler,
                           IRQF_SHARED, "panfrost-mmu", pfdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request MMU IRQ\n");
        goto err_clk;
    }
​
    ret = devm_request_irq(&pdev->dev, pfdev->irq_job, panfrost_job_irq_handler,
                           IRQF_SHARED, "panfrost-job", pfdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request job IRQ\n");
        goto err_clk;
    }
​
    // 10. 注册 DRM 设备
    drm = drm_device_alloc(&panfrost_driver, &pdev->dev);
    if (IS_ERR(drm)) {
        ret = PTR_ERR(drm);
        goto err_clk;
    }
    pfdev->drm = drm;
    drm->dev_private = pfdev;
​
    ret = drm_dev_register(drm, 0);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register DRM device\n");
        goto err_drm;
    }
​
    dev_info(&pdev->dev, "Mali GPU registered (version %d, cores %d, tiler %d)\n",
             pfdev->gpu_version, pfdev->num_cores, pfdev->num_tiler);
    return 0;
​
err_drm:
    drm_dev_put(drm);
err_clk:
    clk_disable_unprepare(pfdev->bus_clk);
    clk_disable_unprepare(pfdev->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int panfrost_remove(struct platform_device *pdev)
{
    struct panfrost_device *pfdev = platform_get_drvdata(pdev);
​
    // 1. 注销 DRM 设备
    drm_dev_unregister(pfdev->drm);
​
    // 2. 禁用 GPU 硬件
    writel(0, pfdev->base + GPU_CTRL);
​
    // 3. 禁用时钟
    clk_disable_unprepare(pfdev->bus_clk);
    clk_disable_unprepare(pfdev->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id panfrost_of_match[] = {
    { .compatible = "arm,mali-t860" },
    { .compatible = "arm,mali-t880" },
    { .compatible = "arm,mali-g52" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, panfrost_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver panfrost_driver = {
    .probe = panfrost_probe,
    .remove = panfrost_remove,
    .driver = {
        .name = "panfrost",
        .of_match_table = panfrost_of_match,
    },
};
module_platform_driver(panfrost_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Mali GPU Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 GPU 调试核心难点

3.1 GPU 挂死 (GPU Hang)

现象:图形界面响应缓慢或完全无响应,dmesg 显示 "GPU hang detected"。

原因

  • Shader 作业超时。

  • 内存访问越界。

  • 电源管理问题。

调试方法

  1. 检查 GPU 状态

    devmem2 <base>+0x04  # GPU_STATUS
  2. 强制复位 GPU

    # 通过 debugfs 复位
    echo 1 > /sys/kernel/debug/dri/0/panfrost/gpu_reset
  3. 使用 perf 监控作业

    perf record -e panfrost:panfrost_job_submit -a -- sleep 10
    perf script | grep panfrost

3.2 MMU 缺页错误

现象dmesg 出现 "GPU MMU fault detected",图形程序崩溃。

原因

  • 分配的内存被释放但 GPU 仍在访问。

  • 页表映射不正确。

  • 内存分配对齐问题。

调试方法

  1. 读取 MMU 状态

    devmem2 <mmu_base>+0x04  # MMU_STATUS
  2. 定位错误地址

    devmem2 <mmu_base>+0x38  # 错误地址寄存器
  3. 分析 GPU 作业

    # 跟踪最近提交的作业
    trace-cmd record -e panfrost:* -a -- timeout 5
    trace-cmd report | grep fault

3.3 GPU 性能异常 (Low FPS)

现象:OpenGL 游戏帧率远低于预期,GPU 利用率高但性能差。

原因

  • GPU 时钟频率过低。

  • 内存带宽不足。

  • 作业调度配置错误。

调试方法

  1. 检查 GPU 频率

    cat /sys/kernel/debug/clk/clk_summary | grep gpu
  2. 查看 GPU 频率上限

    # 检查频率上限
    cat /sys/devices/platform/*gpu*/devfreq/devfreq0/cur_freq
  3. 增加作业并发

    # 增加调度队列深度
    echo 8 > /sys/kernel/debug/dri/0/panfrost/job_queue_depth

3.4 电源管理异常 (GPU 不掉电)

现象:系统空闲时,GPU 仍然处于工作状态,功耗较高。

原因

  • 电源管理驱动未启用。

  • 有持续运行的作业。

  • GPU 无法进入休眠状态。

调试方法

  1. 检查 GPU 电源状态

    devmem2 <pm_base>+0x24  # PWR_STATUS
  2. 强制 GPU 进入空闲

    # 终止所有作业
    echo 1 > /sys/kernel/debug/dri/0/panfrost/gpu_force_idle
  3. 检查 PM 脚本

    cat /sys/power/pm_debug

第四部分:结合性能调试场景示例

场景:3D 游戏帧率在 20-25fps 之间波动,而 GPU 峰值性能应达到 60fps。

分析流程

  1. 宏观层面( CPU 和 Memory 层):

    • perf top 显示 panfrost_job_irq_handler 占用 CPU 约 20%。

    • iostat 显示内存带宽接近饱和。

  2. GPU 层(Device Drivers -> GPU 层):

    cat /sys/devices/platform/*gpu*/devfreq/devfreq0/cur_freq

    发现 GPU 频率稳定在 650MHz,而理论最高为 800MHz。

  3. 硬件监控( Thermal 层):

    cat /sys/class/thermal/thermal_zone*/temp

    发现 GPU 温度达到 85°C。

  4. 根本原因

    • GPU 温度达到温度阈值,导致热降频(Thermal Throttling)。

    • 散热设计不足,无法长期维持高性能。

    • 热管理策略过于保守。

  5. 解决方案

    • 改善散热(增加散热片或风扇)。

    • 在 DTS 中提高温度阈值 temperature-high = <90000>;

    • 降低渲染分辨率或画质。

    • 增加 GPU 主动冷却周期。

    • 优化后,帧率稳定在 50fps 以上。


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

控制器 协同方式 调试关键点
MMU 为 GPU 提供虚拟地址映射 页表错误、内存访问权限
DMA 通过 DMA 传输纹理数据 带宽分配、描述符链
Thermal 监测 GPU 温度并进行降频 温度阈值、降频策略
Power 提供电源动态管理 空闲降频、休眠唤醒
Interrupt Controller 管理 GPU 中断 中断优先级、延迟

第四部分 PMIC 控制器 (RK808)

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

PMIC (Power Management Integrated Circuit) 是现代嵌入式系统中最重要的组件之一,负责为 SoC 和所有外设提供稳定的电压和电流,并管理电池充电、热保护、电源按键等功能。PMIC 通常通过 I2CSPI 总线与主 SoC 通信,在 Linux 中作为 Platform 设备注册。

在 Linux 5.10 中,PMIC 驱动位于 drivers/mfd/(多功能设备驱动),核心层次分为三层:

  1. MFD 核心层:将 PMIC 注册为 MFD 设备,暴露子设备(Regulator、RTC、PWM、ADC 等)。

  2. PMIC 主驱动(本篇文章重点):负责 I2C 通信、中断处理、电源管理。

  3. 子设备驱动:如 rk808-regulator.c(电压调节器)、rk808-rtc.c(RTC)、rk808-clk.c(时钟输出)。


第二章 Linux 5.10 典型 PMIC 驱动 —— rk808.c

RK808 是 Rockchip 系列 SoC 中最常用的 PMIC 之一,支持 4 路 DCDC 转换器、8 路 LDO、RTC、电源按键、电池充电等功能。以下代码基于 Linux 5.10 drivers/mfd/rk808.c,展示核心逻辑。

2.1 硬件关键概念

  • I2C 寄存器:通过 I2C 访问内部寄存器,配置电压、使能、中断。

  • DCDC 转换器:提供大电流、高效率的电压输出(用于 CPU、GPU、DDR)。

  • LDO:提供低噪声、低电流的电压输出(用于模拟电路、音频)。

  • RTC:实时时钟,保持系统时间。

  • 中断控制器:处理电源按键、过压、过温、充电状态等事件。

  • 充电器:支持锂电池充电管理。

2.2 核心代码

// 基于 Linux 5.10 drivers/mfd/rk808.c (精简版)
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/mfd/core.h>
#include <linux/mfd/rk808.h>
​
/**
 * @brief RK808 PMIC 的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 PMIC 硬件实例。
 */
struct rk808 {
    struct i2c_client *i2c;          /**< I2C 客户端指针 */
    struct regmap *regmap;           /**< 寄存器映射 */
    int irq;                         /**< 中断号 */
    struct device *dev;              /**< 设备指针 */
    struct regmap_irq_chip_data *irq_data; /**< 中断控制器数据 */
    struct i2c_client *i2c_rtc;      /**< RTC I2C 客户端 */
    struct i2c_client *i2c_gpio;     /**< GPIO I2C 客户端 */
    bool battery_present;            /**< 电池是否在位 */
};
​
/* 寄存器偏移量 (RK808) */
#define RK808_REG_PWRON            0x00
#define RK808_REG_PWRON_EN         0x01
#define RK808_REG_INT_STS          0x02
#define RK808_REG_INT_MASK         0x03
#define RK808_REG_DCDC1_CTRL       0x10
#define RK808_REG_DCDC2_CTRL       0x11
#define RK808_REG_DCDC3_CTRL       0x12
#define RK808_REG_LDO1_CTRL        0x20
#define RK808_REG_LDO2_CTRL        0x21
#define RK808_REG_LDO3_CTRL        0x22
#define RK808_REG_BATTERY_CTRL     0x30
#define RK808_REG_CHARGE_CTRL      0x31
#define RK808_REG_RTC_CTRL         0x40
#define RK808_REG_RTC_SEC          0x41
​
/* 中断位定义 */
#define RK808_IRQ_PWRON            (1 << 0)  /**< 电源按键中断 */
#define RK808_IRQ_OVP              (1 << 1)  /**< 过压中断 */
#define RK808_IRQ_OTP              (1 << 2)  /**< 过温中断 */
#define RK808_IRQ_BAT_INSERT       (1 << 3)  /**< 电池插入中断 */
#define RK808_IRQ_BAT_REMOVE       (1 << 4)  /**< 电池移除中断 */
#define RK808_IRQ_BAT_OK           (1 << 5)  /**< 电池状态良好中断 */
#define RK808_IRQ_CHARGE           (1 << 6)  /**< 充电状态中断 */
​
/**
 * @brief 读取 PMIC 寄存器。
 * 
 * @param rk808 指向 rk808 结构。
 * @param reg 寄存器地址。
 * @param val 输出值。
 * @return 0 成功。
 */
static int rk808_read_reg(struct rk808 *rk808, u8 reg, u8 *val)
{
    return regmap_read(rk808->regmap, reg, (unsigned int *)val);
}
​
/**
 * @brief 写入 PMIC 寄存器。
 * 
 * @param rk808 指向 rk808 结构。
 * @param reg 寄存器地址。
 * @param val 要写入的值。
 * @return 0 成功。
 */
static int rk808_write_reg(struct rk808 *rk808, u8 reg, u8 val)
{
    return regmap_write(rk808->regmap, reg, val);
}
​
/**
 * @brief 初始化 PMIC 硬件配置。
 * 
 * @param rk808 指向 rk808 结构。
 * @return 0 成功。
 */
static int rk808_hw_init(struct rk808 *rk808)
{
    u8 val;
    int ret;
​
    // 1. 检查 PMIC 是否正常工作 (读取设备 ID)
    ret = rk808_read_reg(rk808, RK808_REG_PWRON, &val);
    if (ret) {
        dev_err(rk808->dev, "Failed to read PMIC ID\n");
        return ret;
    }
    dev_info(rk808->dev, "RK808 PMIC ID: 0x%02x\n", val);
​
    // 2. 使能电源按键中断
    ret = rk808_write_reg(rk808, RK808_REG_INT_MASK, ~RK808_IRQ_PWRON);
    if (ret) {
        dev_err(rk808->dev, "Failed to enable power key interrupt\n");
        return ret;
    }
​
    // 3. 设置 DCDC1 输出电压 (CPU 核心电压)
    // RK808 的 DCDC1 默认输出 1.0V,通过寄存器设置
    // 此处简化为配置使能
    ret = rk808_write_reg(rk808, RK808_REG_DCDC1_CTRL, 0x1F);
    if (ret) {
        dev_err(rk808->dev, "Failed to configure DCDC1\n");
        return ret;
    }
​
    // 4. 设置 LDO1 输出电压 (用于某些外设)
    ret = rk808_write_reg(rk808, RK808_REG_LDO1_CTRL, 0x0F);
    if (ret) {
        dev_err(rk808->dev, "Failed to configure LDO1\n");
        return ret;
    }
​
    // 5. 检查电池状态
    ret = rk808_read_reg(rk808, RK808_REG_BATTERY_CTRL, &val);
    if (ret == 0) {
        rk808->battery_present = !!(val & (1 << 0));
    }
​
    return 0;
}
​
/**
 * @brief 处理电源按键中断。
 * 
 * @param rk808 指向 rk808 结构。
 */
static void rk808_handle_power_key(struct rk808 *rk808)
{
    u8 val;
​
    // 1. 读取电源按键状态
    rk808_read_reg(rk808, RK808_REG_PWRON, &val);
​
    // 2. 判断按键按下还是释放
    if (val & (1 << 0)) {
        dev_info(rk808->dev, "Power key pressed\n");
        // 触发系统关机或休眠
        // 需要根据按键时长决定 (例如长按关机)
        // 此处简化为直接关机
        pm_power_off();
    } else {
        dev_info(rk808->dev, "Power key released\n");
    }
}
​
/**
 * @brief 处理电池插入/移除中断。
 * 
 * @param rk808 指向 rk808 结构。
 */
static void rk808_handle_battery(struct rk808 *rk808)
{
    u8 val;
​
    // 1. 读取电池状态
    rk808_read_reg(rk808, RK808_REG_BATTERY_CTRL, &val);
​
    // 2. 更新电池状态
    rk808->battery_present = !!(val & (1 << 0));
    if (rk808->battery_present) {
        dev_info(rk808->dev, "Battery inserted\n");
        // 通知电池子系统
        power_supply_changed(NULL);
    } else {
        dev_info(rk808->dev, "Battery removed\n");
    }
}
​
/**
 * @brief PMIC 中断处理函数。
 * 
 * 处理电源按键、电池插入/移除、过压/过温等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rk808 结构。
 */
static irqreturn_t rk808_irq_handler(int irq, void *dev_id)
{
    struct rk808 *rk808 = dev_id;
    u8 int_stat;
​
    // 1. 读取中断状态
    rk808_read_reg(rk808, RK808_REG_INT_STS, &int_stat);
​
    // 2. 处理电源按键中断
    if (int_stat & RK808_IRQ_PWRON) {
        rk808_handle_power_key(rk808);
        // 清除中断
        rk808_write_reg(rk808, RK808_REG_INT_STS, RK808_IRQ_PWRON);
    }
​
    // 3. 处理电池插入/移除中断
    if (int_stat & RK808_IRQ_BAT_INSERT) {
        rk808_handle_battery(rk808);
        rk808_write_reg(rk808, RK808_REG_INT_STS, RK808_IRQ_BAT_INSERT);
    }
    if (int_stat & RK808_IRQ_BAT_REMOVE) {
        rk808_handle_battery(rk808);
        rk808_write_reg(rk808, RK808_REG_INT_STS, RK808_IRQ_BAT_REMOVE);
    }
​
    // 4. 处理过压/过温中断
    if (int_stat & RK808_IRQ_OVP) {
        dev_err(rk808->dev, "Over-voltage detected!\n");
        rk808_write_reg(rk808, RK808_REG_INT_STS, RK808_IRQ_OVP);
        // 可能需要紧急关机
    }
    if (int_stat & RK808_IRQ_OTP) {
        dev_err(rk808->dev, "Over-temperature detected!\n");
        rk808_write_reg(rk808, RK808_REG_INT_STS, RK808_IRQ_OTP);
        // 可能需要启用热保护
    }
​
    // 5. 处理充电状态变化
    if (int_stat & RK808_IRQ_CHARGE) {
        dev_info(rk808->dev, "Charge status changed\n");
        rk808_write_reg(rk808, RK808_REG_INT_STS, RK808_IRQ_CHARGE);
        power_supply_changed(NULL);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 PMIC 硬件,注册子设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rk808_probe(struct platform_device *pdev)
{
    struct rk808 *rk808;
    struct i2c_client *i2c;
    struct regmap *regmap;
    int ret, irq;
​
    // 1. 分配私有数据结构
    rk808 = devm_kzalloc(&pdev->dev, sizeof(*rk808), GFP_KERNEL);
    if (!rk808)
        return -ENOMEM;
    platform_set_drvdata(pdev, rk808);
    rk808->dev = &pdev->dev;
​
    // 2. 从平台数据中获取 I2C 客户端
    i2c = dev_get_drvdata(pdev->dev.parent);
    if (!i2c) {
        dev_err(&pdev->dev, "No I2C client provided\n");
        return -EINVAL;
    }
    rk808->i2c = i2c;
​
    // 3. 初始化 regmap
    regmap = devm_regmap_init_i2c(i2c, &rk808_regmap_config);
    if (IS_ERR(regmap)) {
        dev_err(&pdev->dev, "Failed to init regmap\n");
        return PTR_ERR(regmap);
    }
    rk808->regmap = regmap;
​
    // 4. 获取中断
    irq = i2c->irq;
    if (irq < 0) {
        dev_err(&pdev->dev, "No IRQ provided\n");
        return irq;
    }
    rk808->irq = irq;
​
    // 5. 硬件初始化
    ret = rk808_hw_init(rk808);
    if (ret) {
        dev_err(&pdev->dev, "Failed to initialize hardware\n");
        return ret;
    }
​
    // 6. 注册中断
    ret = devm_request_irq(&pdev->dev, rk808->irq, rk808_irq_handler,
                           IRQF_SHARED, "rk808", rk808);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        return ret;
    }
​
    // 7. 注册 MFD 子设备 (Regulator, RTC, GPIO, ADC 等)
    ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
                                rk808_devs, ARRAY_SIZE(rk808_devs),
                                NULL, 0, NULL);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add MFD devices\n");
        return ret;
    }
​
    dev_info(&pdev->dev, "RK808 PMIC registered\n");
    return 0;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rk808_remove(struct platform_device *pdev)
{
    // 1. 移除 MFD 子设备
    // MFD 核心会自动清理
​
    // 2. 禁用中断
    // devm 会自动释放
​
    return 0;
}
​
/* MFD 子设备列表 */
static const struct mfd_cell rk808_devs[] = {
    {
        .name = "rk808-regulator",
        .of_compatible = "rockchip,rk808-regulator",
    },
    {
        .name = "rk808-rtc",
        .of_compatible = "rockchip,rk808-rtc",
    },
    {
        .name = "rk808-gpio",
        .of_compatible = "rockchip,rk808-gpio",
    },
    {
        .name = "rk808-adc",
        .of_compatible = "rockchip,rk808-adc",
    },
};
​
/* 设备树匹配表 */
static const struct of_device_id rk808_of_match[] = {
    { .compatible = "rockchip,rk808" },
    { .compatible = "rockchip,rk809" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rk808_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rk808_driver = {
    .probe = rk808_probe,
    .remove = rk808_remove,
    .driver = {
        .name = "rk808",
        .of_match_table = rk808_of_match,
    },
};
module_platform_driver(rk808_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip RK808 PMIC Driver");
MODULE_LICENSE("GPL v2");

第三章 PMIC 调试核心难点

3.1 PMIC 无法与 SoC 通信

现象dmesg 显示 "Failed to read PMIC ID",系统启动后 PMIC 未初始化。

原因

  • I2C 总线通信失败。

  • PMIC 地址错误。

  • PMIC 未正确上电。

调试方法

  1. 检查 I2C 总线

    i2cdetect -y <bus>
  2. 手动读取 PMIC 寄存器

    i2ctransfer -y <bus> w1@<addr> 0x00 r1
  3. 检查电源输入:用万用表测量 PMIC 的输入电压。

3.2 电源按键无反应

现象:按下板上的电源按键,系统没有任何响应。

原因

  • 电源按键中断未使能。

  • 电源按键 GPIO 配置错误。

  • PMIC 中断处理失败。

调试方法

  1. 查看中断统计

    cat /proc/interrupts | grep rk808
  2. 测试中断触发

    # 手动触发中断
    devmem2 <base>+0x02 0x01  # 写入中断状态寄存器
  3. 检查电源按键 GPIO

    cat /sys/kernel/debug/gpio | grep power

3.3 电压调节器无法控制

现象cat /sys/class/regulator/regulator0/status 显示 "disabled",无法启用。

原因

  • 电压调节器驱动未加载。

  • 电压上限/下限配置错误。

  • 硬件保护触发。

调试方法

  1. 查看调节器状态

    cat /sys/class/regulator/regulator*/status
  2. 尝试手动启用

    echo "enabled" > /sys/class/regulator/regulator0/enable
  3. 检查硬件保护

    devmem2 <base>+0x02  # 读取中断状态

3.4 电池充电不工作

现象:插入充电器后,系统不显示充电状态,电池电量不增加。

原因

  • 充电控制寄存器未配置。

  • 电池检测失效。

  • 充电器插入检测失败。

调试方法

  1. 检查充电状态

    cat /sys/class/power_supply/charger/status
  2. 读取充电寄存器

    devmem2 <base>+0x31  # CHARGE_CTRL
  3. 手动启动充电

    echo "1" > /sys/class/power_supply/charger/charge_control

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

场景:系统在插入充电器时,PMIC 频繁触发中断,CPU 占用率升高,导致系统响应缓慢。

分析流程

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

    • perf top 显示 rk808_irq_handler 占用 CPU 约 30%。

    • cat /proc/interrupts | grep rk808 显示中断触发频率极高(每秒 50 次)。

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

    devmem2 <base>+0x02  # INT_STS

    发现 RK808_IRQ_CHARGE 持续置位。

  3. 充电状态(电池充电层):

    cat /sys/class/power_supply/charger/status

    显示充电状态在 "charging" 和 "charged" 之间快速切换。

  4. 根本原因

    • 电池接近满电,充电芯片在 "恒压充电" 阶段。

    • 充电电流临界触发中断阈值。

    • PMIC 在充电完成和充电进行之间频繁切换。

  5. 解决方案

    • 增加充电中断的迟滞 (hysteresis) 值。

    • 在驱动中添加 "充电完成" 状态锁定机制。

    • 调整充电终止电流阈值。

    • 优化后,中断频率降低至正常水平。


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

控制器 协同方式 调试关键点
I2C PMIC 通过 I2C 与 SoC 通信 I2C 频率、地址冲突
Regulator 管理电压输出 电压稳定、上电时序
RTC 维护系统时间 电池备份、时间同步
Power Supply 管理电池充电 充电状态、电量计算
Thermal 监测 PMIC 温度 过热保护、降频策略

第五部分 SARADC 控制器

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

SARADC (逐次逼近模数转换器) 是 SoC 中用于将模拟信号转换为数字信号的硬件模块。它广泛应用于嵌入式系统,用于读取触摸屏坐标、电池电压、传感器数据、环境温度等。SARADC 控制器通常作为 Platform 设备挂载在 SoC 内部总线上。

在 Linux 5.10 中,SARADC 驱动通常位于 drivers/iio/adc/,通过 IIO (Industrial I/O) 子系统暴露控制接口。典型驱动包括 rockchip-saradc.csun4i-gpadc.c 等。

1.1 硬件关键概念

  • ADC 核心寄存器:控制寄存器、数据寄存器、状态寄存器、中断使能。

  • 时钟:需要 clk (ADC 核心时钟) 和 pclk (总线时钟)。

  • 中断:转换完成中断,用于通知 CPU 读取结果。

  • 采样率:可由软件配置(如 1kHz ~ 1MHz)。

  • 通道:支持多路模拟输入(通常 4~8 个通道)。

  • 参考电压:通常为 SoC 的 VCC 或内部 1.8V 基准。

1.2 核心代码

// 基于 Linux 5.10 drivers/iio/adc/rockchip-saradc.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/delay.h>
​
/**
 * @brief Rockchip SARADC 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 SARADC 硬件实例。
 */
struct rockchip_saradc {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 */
    struct clk *clk;                /**< ADC 核心时钟 */
    struct clk *pclk;               /**< 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct iio_dev *indio_dev;      /**< IIO 设备抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 version;                    /**< 控制器版本 */
    u32 sample_rate;                /**< 采样率 (Hz) */
    u32 channel_count;              /**< 通道数量 */
    struct completion completion;   /**< 转换完成同步 */
    u32 last_result;                /**< 上一次转换结果 */
};
​
/* 寄存器偏移量 (Rockchip SARADC) */
#define SARADC_CTRL                 0x00
#define SARADC_DATA                 0x04
#define SARADC_STATUS               0x08
#define SARADC_INT_EN               0x0C
#define SARADC_INT_STAT             0x10
#define SARADC_CLK_DIV              0x14
#define SARADC_PWR_CTRL             0x18
#define SARADC_CHANNEL_SEL          0x1C
​
/* 控制寄存器位定义 */
#define SARADC_CTRL_ENABLE          (1 << 0)  /**< ADC 使能 */
#define SARADC_CTRL_START           (1 << 1)  /**< 启动转换 */
#define SARADC_CTRL_MODE_CONTINUOUS (0 << 2)  /**< 连续模式 */
#define SARADC_CTRL_MODE_SINGLE     (1 << 2)  /**< 单次模式 */
​
/**
 * @brief 初始化 SARADC 硬件。
 * 
 * @param saradc 指向 rockchip_saradc 结构。
 * @return 0 成功。
 */
static int rockchip_saradc_hw_init(struct rockchip_saradc *saradc)
{
    u32 ctrl;
​
    // 1. 启用电源
    writel(0x1, saradc->base + SARADC_PWR_CTRL);
    msleep(1);
​
    // 2. 配置时钟分频 (根据采样率计算)
    u32 clk_rate = clk_get_rate(saradc->clk);
    u32 div = clk_rate / saradc->sample_rate;
    if (div < 2) div = 2;
    if (div > 0xFFFF) div = 0xFFFF;
    writel(div, saradc->base + SARADC_CLK_DIV);
​
    // 3. 配置为单次转换模式
    ctrl = SARADC_CTRL_ENABLE | SARADC_CTRL_MODE_SINGLE;
    writel(ctrl, saradc->base + SARADC_CTRL);
​
    // 4. 启用中断
    writel(0x1, saradc->base + SARADC_INT_EN);
​
    return 0;
}
​
/**
 * @brief 启动 SARADC 单次转换。
 * 
 * @param saradc 指向 rockchip_saradc 结构。
 * @param channel 通道号 (0 ~ channel_count-1)。
 * @return 0 成功。
 */
static int rockchip_saradc_start_single(struct rockchip_saradc *saradc,
                                        int channel)
{
    u32 ctrl;
    unsigned long flags;
​
    if (channel >= saradc->channel_count) {
        dev_err(saradc->dev, "Invalid channel %d\n", channel);
        return -EINVAL;
    }
​
    spin_lock_irqsave(&saradc->lock, flags);
​
    // 1. 选择通道
    writel(channel, saradc->base + SARADC_CHANNEL_SEL);
​
    // 2. 启动转换
    ctrl = readl(saradc->base + SARADC_CTRL);
    ctrl |= SARADC_CTRL_START;
    writel(ctrl, saradc->base + SARADC_CTRL);
​
    spin_unlock_irqrestore(&saradc->lock, flags);
​
    // 3. 等待转换完成
    if (!wait_for_completion_timeout(&saradc->completion, 100 * HZ)) {
        dev_err(saradc->dev, "ADC conversion timeout\n");
        return -ETIMEDOUT;
    }
​
    return 0;
}
​
/**
 * @brief 读取 SARADC 转换结果。
 * 
 * @param saradc 指向 rockchip_saradc 结构。
 * @return 转换结果 (0 ~ 1023 或 0 ~ 4095 取决于分辨率)。
 */
static u32 rockchip_saradc_read_result(struct rockchip_saradc *saradc)
{
    return readl(saradc->base + SARADC_DATA) & 0xFFF;
}
​
/**
 * @brief IIO 读取原始值回调。
 * 
 * @param indio_dev 指向 iio_dev。
 * @param chan 指向 iio_chan_spec。
 * @param val 输出原始值。
 * @return 0 成功。
 */
static int rockchip_saradc_read_raw(struct iio_dev *indio_dev,
                                    struct iio_chan_spec const *chan,
                                    int *val, int *val2, long mask)
{
    struct rockchip_saradc *saradc = iio_priv(indio_dev);
    int ret;
​
    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        // 启动单次转换
        ret = rockchip_saradc_start_single(saradc, chan->channel);
        if (ret < 0)
            return ret;
​
        // 读取结果
        *val = rockchip_saradc_read_result(saradc);
        return IIO_VAL_INT;
​
    case IIO_CHAN_INFO_SCALE:
        // 计算实际电压值 = raw * 参考电压 / 分辨率
        // 假设 1.8V 参考电压, 12 位分辨率
        *val = 1800;
        *val2 = 12;  // 12 位分辨率
        return IIO_VAL_FRACTIONAL_LOG2;
​
    default:
        return -EINVAL;
    }
}
​
/**
 * @brief SARADC 中断处理函数。
 * 
 * 处理转换完成中断,唤醒等待的线程。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_saradc 结构。
 */
static irqreturn_t rockchip_saradc_irq_handler(int irq, void *dev_id)
{
    struct rockchip_saradc *saradc = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(saradc->base + SARADC_INT_STAT);
​
    // 2. 处理转换完成中断
    if (int_stat & (1 << 0)) {
        // 清除中断
        writel(1 << 0, saradc->base + SARADC_INT_STAT);
        // 唤醒等待的线程
        complete(&saradc->completion);
    }
​
    // 3. 处理错误中断
    if (int_stat & (1 << 1)) {
        dev_err(saradc->dev, "ADC error\n");
        writel(1 << 1, saradc->base + SARADC_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 SARADC 硬件,注册 IIO 设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_saradc_probe(struct platform_device *pdev)
{
    struct rockchip_saradc *saradc;
    struct iio_dev *indio_dev;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配 IIO 设备
    indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*saradc));
    if (!indio_dev)
        return -ENOMEM;
    saradc = iio_priv(indio_dev);
    saradc->indio_dev = indio_dev;
    saradc->dev = &pdev->dev;
    platform_set_drvdata(pdev, saradc);
​
    // 2. 获取 I/O 资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "No memory resource\n");
        return -ENXIO;
    }
    saradc->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(saradc->base))
        return PTR_ERR(saradc->base);
​
    // 3. 获取时钟
    saradc->clk = devm_clk_get(&pdev->dev, "clk");
    if (IS_ERR(saradc->clk))
        return PTR_ERR(saradc->clk);
    saradc->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(saradc->pclk))
        return PTR_ERR(saradc->pclk);
​
    clk_prepare_enable(saradc->clk);
    clk_prepare_enable(saradc->pclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    saradc->irq = irq;
​
    // 5. 读取配置
    saradc->channel_count = 4;  // 假设 4 通道
    saradc->sample_rate = 1000; // 1kHz
    saradc->version = 0x100;
​
    spin_lock_init(&saradc->lock);
    init_completion(&saradc->completion);
​
    // 6. 硬件初始化
    ret = rockchip_saradc_hw_init(saradc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to init hardware\n");
        goto err_clk;
    }
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, saradc->irq, rockchip_saradc_irq_handler,
                           IRQF_SHARED, "rockchip-saradc", saradc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 8. 设置 IIO 设备
    indio_dev->name = "rockchip-saradc";
    indio_dev->info = &rockchip_saradc_info;
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->channels = rockchip_saradc_channels;
    indio_dev->num_channels = saradc->channel_count;
​
    // 9. 注册 IIO 设备
    ret = devm_iio_device_register(&pdev->dev, indio_dev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register IIO device\n");
        goto err_clk;
    }
​
    dev_info(&pdev->dev, "Rockchip SARADC registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, saradc->irq);
    return 0;
​
err_clk:
    clk_disable_unprepare(saradc->pclk);
    clk_disable_unprepare(saradc->clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_saradc_remove(struct platform_device *pdev)
{
    struct rockchip_saradc *saradc = platform_get_drvdata(pdev);
​
    // 1. 禁用 ADC
    writel(0, saradc->base + SARADC_CTRL);
​
    // 2. 禁用时钟
    clk_disable_unprepare(saradc->pclk);
    clk_disable_unprepare(saradc->clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_saradc_of_match[] = {
    { .compatible = "rockchip,rk3399-saradc" },
    { .compatible = "rockchip,rk3568-saradc" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_saradc_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_saradc_driver = {
    .probe = rockchip_saradc_probe,
    .remove = rockchip_saradc_remove,
    .driver = {
        .name = "rockchip-saradc",
        .of_match_table = rockchip_saradc_of_match,
    },
};
module_platform_driver(rockchip_saradc_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip SARADC Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 SARADC 调试核心难点

3.1 ADC 读数异常(噪声或跳变)

现象cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw 显示的数值在多个值之间频繁跳变。

原因

  • 模拟输入信号存在噪声。

  • 采样率过高导致转换不稳定。

  • 参考电压不稳定。

调试方法

  1. 降低采样率

    echo 100 > /sys/bus/iio/devices/iio:device0/sampling_frequency
  2. 添加软件滤波

    # 使用 iio 滤波工具
    iio-filter --average 5 /sys/bus/iio/devices/iio:device0/in_voltage0_raw
  3. 检查参考电压:用万用表测量 ADC 参考引脚。

3.2 ADC 读数始终为 0

现象:无论输入电压如何,ADC 读数始终为 0。

原因

  • 输入通道未正确选择。

  • ADC 电源未启用。

  • 参考电压为 0。

调试方法

  1. 检查 ADC 状态

    devmem2 <base>+0x08  # SARADC_STATUS
  2. 检查电源控制

    devmem2 <base>+0x18  # SARADC_PWR_CTRL
  3. 检查时钟

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

3.3 ADC 读数始终为最大值

现象:输入电压变化,ADC 读数始终为 1023(10 位)或 4095(12 位)。

原因

  • 输入电压超过参考电压。

  • 输入引脚被上拉。

  • 通道选择错误。

调试方法

  1. 测量输入电压:用万用表测量 ADC 输入引脚电压。

  2. 检查参考电压:确认参考电压值。

  3. 检查通道选择

    devmem2 <base>+0x1C  # SARADC_CHANNEL_SEL

3.4 ADC 转换超时

现象:读取 ADC 值时,程序卡住,dmesg 显示 "ADC conversion timeout"。

原因

  • 中断未触发。

  • 硬件时钟停止。

  • 中断被其他中断阻塞。

调试方法

  1. 检查中断统计

    cat /proc/interrupts | grep saradc
  2. 检查中断使能

    devmem2 <base>+0x0C  # SARADC_INT_EN
  3. 手动强制转换

    devmem2 <base>+0x00 0x03  # 强制启动

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

场景:系统使用 ADC 读取电池电压,当 CPU 负载高时,ADC 读取结果出现波动。

分析流程

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

    • perf top 显示 rockchip_saradc_irq_handler 占用 CPU 约 10%。

    • 中断触发频率较高。

  2. ADC 层(图谱的 Device Drivers -> SARADC 层):

    cat /sys/kernel/debug/iio/device0/registers

    发现采样率为 1000Hz。

  3. 中断延迟(Interrupt Controller 层):

    perf record -e irq:irq_handler_entry -a -- timeout 5
    perf script | grep saradc

    发现中断处理延迟在 CPU 负载高时达到 500us。

  4. 根本原因

    • ADC 中断优先级较低,被其他高优先级中断(如网络、USB)抢占。

    • 采样率 1000Hz 需要每 1ms 处理一次中断。

    • 高 CPU 负载导致中断处理延迟超过 1ms,转换结果被丢弃或读取时已过期。

  5. 解决方案

    • 降低采样率到 100Hz。

    • 提高 ADC 中断优先级。

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

    • 在 ADC 驱动中添加硬件 FIFO 缓冲。


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

控制器 协同方式 调试关键点
Clock Controller 提供 ADC 核心时钟 频率稳定、分频计算
GPIO 配置 ADC 输入引脚 引脚模式(模拟输入)
Interrupt Controller 触发转换完成中断 优先级、延迟
Thermal 读取内部温度传感器 温度校准、偏移补偿
Power Supply 读取电池电压 电压分压比、充电状态

第六部分 NPU 控制器 (Rockchip RKNPU2)

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

NPU (Neural Processing Unit) 是专门为神经网络推理设计的协处理器。在嵌入式 AI 设备中,NPU 负责加速卷积神经网络 (CNN)、循环神经网络 (RNN) 等深度学习模型的推理计算。它通常作为 Platform 设备挂载在 SoC 内部总线上,通过专用 DMA 和中断机制与 CPU 协同工作。

在 Linux 5.10 中,Rockchip 的 NPU 驱动位于 drivers/soc/rockchip/rknpu/ (RKNPU2),通过 IOCTL 接口暴露给用户空间,支持 TensorFlow Lite、ONNX、RKNN 等模型格式。

1.1 硬件关键概念

  • NPU 核心寄存器:管理 NPU 电源、时钟、工作状态。

  • DMA 引擎:从内存直接搬运模型权重和输入数据。

  • 指令队列:接收 CPU 提交的推理指令(卷积、池化、全连接等)。

  • 中断:处理推理完成、指令执行错误、电源异常等。

  • DVFS:动态调频调压,平衡性能与功耗。

1.2 核心代码与 Doxygen 注解

// 基于 Linux 5.10 drivers/soc/rockchip/rknpu/rknpu2.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/sched.h>
​
/**
 * @brief Rockchip NPU 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 NPU 硬件实例。
 */
struct rknpu_device {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< NPU 中断号 */
    struct clk *aclk;               /**< 总线时钟 */
    struct clk *hclk;               /**< 核心时钟 */
    struct clk *pclk;               /**< APB 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct miscdevice misc;         /**< 字符设备抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    struct work_struct power_work;  /**< 电源管理工作队列 */
    u32 version;                    /**< NPU 版本号 (2.0/2.5) */
    u32 core_count;                 /**< 核心数量 (1/2/3) */
    u32 max_freq;                   /**< 最大频率 */
    struct rknpu_regs *regs;        /**< 寄存器缓存 */
};
​
/* 寄存器偏移量 (Rockchip NPU2) */
#define NPU_REG_CTRL                0x000
#define NPU_REG_STATUS              0x004
#define NPU_REG_INT_EN              0x008
#define NPU_REG_INT_STAT            0x00C
#define NPU_REG_CORE_CTRL           0x100
#define NPU_REG_CORE_STATUS         0x104
#define NPU_REG_INSTR_QUEUE         0x200
#define NPU_REG_INSTR_STAT          0x204
#define NPU_REG_WEIGHT_BASE         0x300
#define NPU_REG_DATA_BASE           0x304
#define NPU_REG_RESULT_BASE         0x308
#define NPU_REG_DMA_CTRL            0x400
#define NPU_REG_DMA_STATUS          0x404
#define NPU_REG_DVFS_CTRL           0x500
#define NPU_REG_DVFS_STATUS         0x504
​
/**
 * @brief 初始化 NPU 电源管理。
 * 
 * @param npu 指向 rknpu_device 结构。
 * @return 0 成功。
 */
static int rknpu_power_init(struct rknpu_device *npu)
{
    u32 status;
​
    // 1. 检查电源域状态
    status = readl(npu->base + NPU_REG_STATUS);
    if (!(status & 0x1)) {
        dev_err(npu->dev, "NPU power domain not ready\n");
        return -EIO;
    }
​
    // 2. 启用核心
    writel(0x1, npu->base + NPU_REG_CORE_CTRL);
    msleep(10);
​
    // 3. 检查核心状态
    status = readl(npu->base + NPU_REG_CORE_STATUS);
    if (!(status & 0x1)) {
        dev_err(npu->dev, "NPU core not ready\n");
        return -EIO;
    }
​
    return 0;
}
​
/**
 * @brief 初始化 NPU DMA。
 * 
 * @param npu 指向 rknpu_device 结构。
 * @param weight_dma 权重数据 DMA 地址。
 * @param input_dma  输入数据 DMA 地址。
 * @param output_dma 输出数据 DMA 地址。
 */
static void rknpu_dma_init(struct rknpu_device *npu,
                           dma_addr_t weight_dma,
                           dma_addr_t input_dma,
                           dma_addr_t output_dma)
{
    // 1. 设置权重地址
    writel(lower_32_bits(weight_dma), npu->base + NPU_REG_WEIGHT_BASE);
    writel(upper_32_bits(weight_dma), npu->base + NPU_REG_WEIGHT_BASE + 4);
​
    // 2. 设置输入数据地址
    writel(lower_32_bits(input_dma), npu->base + NPU_REG_DATA_BASE);
    writel(upper_32_bits(input_dma), npu->base + NPU_REG_DATA_BASE + 4);
​
    // 3. 设置输出数据地址
    writel(lower_32_bits(output_dma), npu->base + NPU_REG_RESULT_BASE);
    writel(upper_32_bits(output_dma), npu->base + NPU_REG_RESULT_BASE + 4);
​
    // 4. 启用 DMA
    writel(0x1, npu->base + NPU_REG_DMA_CTRL);
}
​
/**
 * @brief 提交 NPU 推理任务。
 * 
 * @param npu 指向 rknpu_device 结构。
 * @param instr_ptr 指令序列指针。
 * @param instr_len 指令序列长度。
 * @return 0 成功。
 */
static int rknpu_submit_task(struct rknpu_device *npu,
                             u32 *instr_ptr, u32 instr_len)
{
    int i;
​
    // 1. 检查 NPU 是否空闲
    u32 status = readl(npu->base + NPU_REG_STATUS);
    if (status & 0x2) {
        dev_err(npu->dev, "NPU busy\n");
        return -EBUSY;
    }
​
    // 2. 将指令写入 NPU 指令队列
    for (i = 0; i < instr_len; i++) {
        writel(instr_ptr[i], npu->base + NPU_REG_INSTR_QUEUE + i * 4);
    }
​
    // 3. 启动推理
    writel(0x1, npu->base + NPU_REG_CTRL);
​
    // 4. 启用中断
    writel(0x1, npu->base + NPU_REG_INT_EN);
​
    return 0;
}
​
/**
 * @brief 读取 NPU 推理结果。
 * 
 * @param npu 指向 rknpu_device 结构。
 * @param output_dma 输出数据地址。
 * @param output_len 输出数据长度。
 */
static void rknpu_get_result(struct rknpu_device *npu,
                             dma_addr_t output_dma,
                             u32 output_len)
{
    // 结果已通过 DMA 直接写入内存
    // 只需要通知用户空间读取即可
}
​
/**
 * @brief 配置 NPU 动态电压频率调整。
 * 
 * @param npu 指向 rknpu_device 结构。
 * @param freq 目标频率 (MHz)。
 */
static void rknpu_dvfs_config(struct rknpu_device *npu, u32 freq)
{
    u32 div = clk_get_rate(npu->aclk) / (freq * 1000000);
    if (div < 1) div = 1;
    if (div > 0xFF) div = 0xFF;
​
    writel(div, npu->base + NPU_REG_DVFS_CTRL);
}
​
/**
 * @brief NPU 中断处理函数。
 * 
 * 处理推理完成、指令错误、DMA 错误等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rknpu_device 结构。
 */
static irqreturn_t rknpu_irq_handler(int irq, void *dev_id)
{
    struct rknpu_device *npu = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(npu->base + NPU_REG_INT_STAT);
​
    // 2. 处理推理完成中断
    if (int_stat & (1 << 0)) {
        writel(1 << 0, npu->base + NPU_REG_INT_STAT);
        // 通知用户空间推理完成
        wake_up_interruptible(&npu->wait_queue);
    }
​
    // 3. 处理指令错误中断
    if (int_stat & (1 << 1)) {
        dev_err(npu->dev, "NPU instruction error\n");
        // 读取错误指令地址
        u32 err_addr = readl(npu->base + NPU_REG_INSTR_STAT);
        dev_err(npu->dev, "Error instruction address: 0x%08x\n", err_addr);
        writel(1 << 1, npu->base + NPU_REG_INT_STAT);
    }
​
    // 4. 处理 DMA 错误中断
    if (int_stat & (1 << 2)) {
        dev_err(npu->dev, "NPU DMA error\n");
        writel(1 << 2, npu->base + NPU_REG_INT_STAT);
        // 可能需要重置 DMA
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief IOCTL 接口:提交推理任务。
 * 
 * @param npu 指向 rknpu_device 结构。
 * @param arg 用户空间参数。
 * @return 0 成功。
 */
static long rknpu_ioctl_submit(struct rknpu_device *npu, void __user *arg)
{
    struct rknpu_task task;
    dma_addr_t weight_dma, input_dma, output_dma;
    int ret;
​
    // 1. 从用户空间复制参数
    if (copy_from_user(&task, arg, sizeof(task))) {
        dev_err(npu->dev, "Failed to copy task from user\n");
        return -EFAULT;
    }
​
    // 2. 映射用户空间 DMA 缓冲区
    weight_dma = dma_map_single(npu->dev, task.weight, task.weight_len,
                                DMA_TO_DEVICE);
    if (dma_mapping_error(npu->dev, weight_dma)) {
        dev_err(npu->dev, "Failed to map weight DMA\n");
        return -ENOMEM;
    }
​
    input_dma = dma_map_single(npu->dev, task.input, task.input_len,
                               DMA_TO_DEVICE);
    if (dma_mapping_error(npu->dev, input_dma)) {
        dev_err(npu->dev, "Failed to map input DMA\n");
        dma_unmap_single(npu->dev, weight_dma, task.weight_len, DMA_TO_DEVICE);
        return -ENOMEM;
    }
​
    output_dma = dma_map_single(npu->dev, task.output, task.output_len,
                                DMA_FROM_DEVICE);
    if (dma_mapping_error(npu->dev, output_dma)) {
        dev_err(npu->dev, "Failed to map output DMA\n");
        dma_unmap_single(npu->dev, input_dma, task.input_len, DMA_TO_DEVICE);
        dma_unmap_single(npu->dev, weight_dma, task.weight_len, DMA_TO_DEVICE);
        return -ENOMEM;
    }
​
    // 3. 初始化 DMA
    rknpu_dma_init(npu, weight_dma, input_dma, output_dma);
​
    // 4. 提交推理任务
    ret = rknpu_submit_task(npu, task.instr_ptr, task.instr_len);
    if (ret < 0) {
        dev_err(npu->dev, "Failed to submit task\n");
        dma_unmap_single(npu->dev, output_dma, task.output_len, DMA_FROM_DEVICE);
        dma_unmap_single(npu->dev, input_dma, task.input_len, DMA_TO_DEVICE);
        dma_unmap_single(npu->dev, weight_dma, task.weight_len, DMA_TO_DEVICE);
        return ret;
    }
​
    // 5. 等待推理完成
    wait_event_interruptible(npu->wait_queue,
                             !(readl(npu->base + NPU_REG_STATUS) & 0x2));
​
    // 6. 取消 DMA 映射
    dma_unmap_single(npu->dev, output_dma, task.output_len, DMA_FROM_DEVICE);
    dma_unmap_single(npu->dev, input_dma, task.input_len, DMA_TO_DEVICE);
    dma_unmap_single(npu->dev, weight_dma, task.weight_len, DMA_TO_DEVICE);
​
    return 0;
}
​
/**
 * @brief 字符设备 IOCTL 接口。
 * 
 * @param file 指向 file 结构。
 * @param cmd IOCTL 命令。
 * @param arg 参数。
 * @return 0 成功。
 */
static long rknpu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct rknpu_device *npu = container_of(file->private_data,
                                            struct rknpu_device, misc);
    long ret = -ENOTTY;
​
    switch (cmd) {
    case RKNPU_IOCTL_SUBMIT:
        ret = rknpu_ioctl_submit(npu, (void __user *)arg);
        break;
    case RKNPU_IOCTL_GET_VERSION:
        ret = put_user(npu->version, (u32 __user *)arg);
        break;
    case RKNPU_IOCTL_GET_CORE_COUNT:
        ret = put_user(npu->core_count, (u32 __user *)arg);
        break;
    default:
        break;
    }
​
    return ret;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 NPU 控制器硬件,注册字符设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rknpu_probe(struct platform_device *pdev)
{
    struct rknpu_device *npu;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    npu = devm_kzalloc(&pdev->dev, sizeof(*npu), GFP_KERNEL);
    if (!npu)
        return -ENOMEM;
    platform_set_drvdata(pdev, npu);
    npu->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;
    }
    npu->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(npu->base))
        return PTR_ERR(npu->base);
​
    // 3. 获取时钟
    npu->aclk = devm_clk_get(&pdev->dev, "aclk");
    if (IS_ERR(npu->aclk))
        return PTR_ERR(npu->aclk);
    npu->hclk = devm_clk_get(&pdev->dev, "hclk");
    if (IS_ERR(npu->hclk))
        return PTR_ERR(npu->hclk);
    npu->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(npu->pclk))
        return PTR_ERR(npu->pclk);
​
    clk_prepare_enable(npu->aclk);
    clk_prepare_enable(npu->hclk);
    clk_prepare_enable(npu->pclk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    npu->irq = irq;
​
    // 5. 读取 NPU 配置
    npu->version = readl(npu->base + 0x00);
    npu->core_count = readl(npu->base + 0x04);
    npu->max_freq = readl(npu->base + 0x08);
​
    spin_lock_init(&npu->lock);
    init_waitqueue_head(&npu->wait_queue);
​
    // 6. 硬件初始化
    ret = rknpu_power_init(npu);
    if (ret) {
        dev_err(&pdev->dev, "Failed to init NPU power\n");
        goto err_clk;
    }
​
    // 7. 注册中断
    ret = devm_request_irq(&pdev->dev, npu->irq, rknpu_irq_handler,
                           IRQF_SHARED, "rknpu", npu);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 8. 注册字符设备
    npu->misc.minor = MISC_DYNAMIC_MINOR;
    npu->misc.name = "rknpu";
    npu->misc.fops = &rknpu_fops;
    ret = misc_register(&npu->misc);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register misc device\n");
        goto err_clk;
    }
​
    // 9. 注册到设备树
    dev_info(&pdev->dev, "Rockchip NPU registered at 0x%llx, IRQ %d, version %d, cores %d\n",
             (unsigned long long)res->start, npu->irq, npu->version, npu->core_count);
    return 0;
​
err_clk:
    clk_disable_unprepare(npu->pclk);
    clk_disable_unprepare(npu->hclk);
    clk_disable_unprepare(npu->aclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rknpu_remove(struct platform_device *pdev)
{
    struct rknpu_device *npu = platform_get_drvdata(pdev);
​
    // 1. 注销字符设备
    misc_deregister(&npu->misc);
​
    // 2. 关闭 NPU
    writel(0, npu->base + NPU_REG_CTRL);
​
    // 3. 禁用时钟
    clk_disable_unprepare(npu->pclk);
    clk_disable_unprepare(npu->hclk);
    clk_disable_unprepare(npu->aclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rknpu_of_match[] = {
    { .compatible = "rockchip,rk3568-npu" },
    { .compatible = "rockchip,rk3588-npu" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rknpu_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rknpu_driver = {
    .probe = rknpu_probe,
    .remove = rknpu_remove,
    .driver = {
        .name = "rknpu",
        .of_match_table = rknpu_of_match,
    },
};
module_platform_driver(rknpu_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip NPU Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 NPU 调试核心难点

3.1 模型推理失败

现象:用户空间调用 IOCTL 提交推理任务后,系统卡住或返回错误码。

原因

  • 模型权重数据格式错误。

  • DMA 地址对齐错误。

  • 指令序列不完整。

调试方法

  1. 检查 NPU 状态

    devmem2 <base>+0x004  # NPU_REG_STATUS
  2. 检查中断统计

    cat /proc/interrupts | grep rknpu
  3. 使用调试工具

    # 使用 rknn 测试工具
    rknn-test -m model.rknn -i input.bin

3.2 内存分配失败

现象:DMA 映射时失败,dmesg 显示 "Failed to map weight DMA"。

原因

  • 内存申请超过连续内存大小。

  • 用户空间缓冲区未对齐到 64 字节。

  • DMA 地址超过 32 位范围。

调试方法

  1. 检查 CMA 大小

    cat /sys/kernel/debug/dma/cma/cma-0/used
  2. 增加 CMA 大小

    # 在 kernel cmdline 中设置
    cma=256M
  3. 检查 DMA 对齐

    # 检查用户空间分配
    cat /proc/sys/kernel/dma_alignment

3.3 推理速度慢

现象:模型推理速度远低于理论值。

原因

  • NPU 频率过低。

  • 数据未在 DDR 中连续存放。

  • 模型量化精度低。

调试方法

  1. 检查 NPU 频率

    cat /sys/kernel/debug/clk/clk_summary | grep npu
  2. 增加频率

    # 设置最大频率
    echo 800000000 > /sys/devices/platform/*npu/devfreq/devfreq0/max_freq
  3. 使用 DMA 缓冲池:预分配 DMA 缓冲区,减少映射开销。

3.4 多任务并发问题

现象:多个应用程序同时使用 NPU 时,任务冲突或死锁。

原因

  • 无并发控制机制。

  • 中断冲突。

  • 资源未同步。

调试方法

  1. 检查锁状态

    cat /proc/lockdep
  2. 优化并发控制

    • 增加硬件队列深度。

    • 使用任务优先级管理。


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

场景:4K 图像进行目标检测时,推理时间 500ms,远高于要求的 50ms,无法满足实时需求。

分析流程

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

    • perf top 显示 rknpu_ioctl_submit 占用 CPU 约 15%。

    • iostat 显示内存带宽高,但主要不是 NPU 消耗。

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

    devmem2 <base>+0x004  # NPU_REG_STATUS

    显示 NPU 频率为 200MHz。

  3. DVFS 层(Power 层):

    cat /sys/devices/platform/*npu/devfreq/devfreq0/cur_freq

    发现 NPU 频率被限制在 200MHz,低于最高 800MHz。

  4. 根本原因

    • 热降频 (Thermal Throttling) 导致 NPU 频率无法提升。

    • 温度达到 85°C 阈值。

    • 散热设计不足。

  5. 解决方案

    • 提高温度阈值 temperature-high = <90000>;

    • 增加散热片或主动散热。

    • 降低输入图像分辨率 (从 4K 到 1080p)。

    • 优化模型,使用更低精度的量化。


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

控制器 协同方式 调试关键点
DMA 模型权重和输入数据传输 地址对齐、带宽分配
Thermal 监测 NPU 温度 降频阈值、散热设计
Power 动态电压频率调整 频率上限、功耗优化
Clock Controller 提供 NPU 时钟 时钟源稳定
Interrupt Controller 推理完成中断 优先级、延迟

第七部分 Ethernet MAC 控制器 (STMMAC/Rockchip)

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

Ethernet MAC 控制器 是挂载在 Platform Bus 上的一个独立外设。它负责数据包处理(封装成帧、地址过滤、CRC 校验)和DMA 传输(将数据从内存搬运到硬件 FIFO,反之亦然)。它通过 RGMII/RMII/MII 接口连接到 PHY 芯片(负责物理层信号转换),通过 MDIO 总线管理 PHY。

Linux 5.10 中,STMMAC 驱动的核心层次:

  1. STMMAC 核心层 (drivers/net/ethernet/stmicro/stmmac/):管理 MAC 硬件、DMA 通道、中断、NAPI 机制。

  2. SoC 特定逻辑层(如 dwmac-rockchip.c):负责获取特定的时钟、PHY 接口配置、RGMII 延迟调整。

  3. 网络核心层 (net/core/):net_devicesk_buff、NAPI、中断。

  4. PHY 层 (drivers/net/phy/):通过 MDIO 管理外部 PHY 状态。


第二部分:核心数据结构与关键操作

2.1 核心数据结构

// 基于 Linux 5.10 drivers/net/ethernet/stmicro/stmmac/stmmac.h (简化)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/ethtool.h>
#include <linux/skbuff.h>
#include <linux/interrupt.h>
​
/**
 * @brief STMMAC 核心的私有数据结构。
 * 
 * 包含 DMA 配置、NAPI 状态、中断配置、硬件寄存器映射等核心内容。
 */
struct stmmac_priv {
    struct net_device *dev;            /**< 网络设备指针 */
    void __iomem *ioaddr;              /**< 寄存器基址 */
    struct stmmac_dma_cfg dma_cfg;     /**< DMA 配置 (突发长度、阈值等) */
    struct stmmac_chain *tx_chain;     /**< TX 描述符链 */
    struct stmmac_chain *rx_chain;     /**< RX 描述符链 */
    struct napi_struct napi;           /**< NAPI 结构 (用于轮询) */
    spinlock_t lock;                   /**< 自旋锁 */
    u32 dma_rx_size;                   /**< RX 描述符数量 */
    u32 dma_tx_size;                   /**< TX 描述符数量 */
    bool rx_coalesce;                  /**< 是否启用 RX 中断聚合 */
    u32 rx_coalesce_usecs;             /**< RX 聚合超时 (微秒) */
    u32 rx_max_coalesced_frames;       /**< RX 最大聚合帧数 */
    struct stmmac_packet_desc *rx_pool; /**< RX 缓冲区池 */
    struct stmmac_packet_desc *tx_pool; /**< TX 缓冲区池 */
    struct device *device;             /**< 设备指针 */
};
​
/**
 * @brief Rockchip 特定扩展数据。
 */
struct rockchip_dwmac {
    struct stmmac_priv *stmmac;        /**< 指向核心私有数据 */
    struct clk *stmmac_clk;            /**< MAC 核心时钟 */
    struct clk *pclk;                  /**< 总线时钟 */
    struct clk *ptp_ref_clk;           /**< PTP 参考时钟 */
    struct phy_device *phydev;         /**< 关联的 PHY 设备 */
    u32 tx_delay;                      /**< RGMII 发送延迟 (相位调整) */
    u32 rx_delay;                      /**< RGMII 接收延迟 */
    int phy_interface;                 /**< PHY 接口模式 (RGMII/RMII) */
};

2.2 核心操作:NAPI 轮询 (数据包接收)

这是以太网驱动最关键的路径。它利用 NAPI(New API)避免在高速流量下发生中断风暴。

/**
 * @brief NAPI 轮询函数 (替代中断处理进行接收)。
 * 
 * 当数据包到达时,NAPI 会禁用 RX 中断并轮询 RX 描述符。这极大地减少了中断开销。
 *
 * @param napi 指向 napi_struct。
 * @param budget 本次轮询允许处理的最大帧数。
 * @return 实际处理的帧数。
 */
static int stmmac_napi_poll(struct napi_struct *napi, int budget)
{
    struct stmmac_priv *priv = container_of(napi, struct stmmac_priv, napi);
    struct net_device *dev = priv->dev;
    struct stmmac_packet_desc *rx_desc;
    struct sk_buff *skb;
    int work_done = 0;
​
    // 1. 检查 RX 描述符环是否已处理完所有帧
    while (work_done < budget) {
        // 获取当前 RX 描述符
        rx_desc = &priv->rx_chain[priv->rx_cur_desc];
        if (!(rx_desc->status & RX_DESC_STATUS_OWN)) {
            // 硬件已标记完成(DMA 将数据写入内存)
            dma_unmap_single(priv->device, rx_desc->addr,
                             rx_desc->len, DMA_FROM_DEVICE);
            
            // 2. 创建 SKB 并复制数据
            skb = napi_alloc_skb(napi, rx_desc->len);
            if (likely(skb)) {
                memcpy(skb_put(skb, rx_desc->len),
                       rx_desc->virt_addr, rx_desc->len);
                skb->protocol = eth_type_trans(skb, dev);
                netif_receive_skb(skb); // 送入网络协议栈
            }
​
            // 3. 清理描述符,重新填充 DMA 缓冲区
            priv->rx_cur_desc = (priv->rx_cur_desc + 1) % priv->dma_rx_size;
            work_done++;
        } else {
            break; // 无更多数据
        }
    }
​
    // 4. 如果未达到预算,重新启用中断
    if (work_done < budget) {
        napi_complete(napi);
        stmmac_enable_rx_interrupt(priv); // 重新开启中断
    }
​
    return work_done;
}

2.3 核心操作:TX 传输

/**
 * @brief 网络设备 TX 回调函数。
 * 
 * @param skb 指向 sk_buff。
 * @param dev 指向 net_device。
 * @return NETDEV_TX_OK 成功。
 */
static netdev_tx_t stmmac_tx(struct sk_buff *skb, struct net_device *dev)
{
    struct stmmac_priv *priv = netdev_priv(dev);
    struct stmmac_packet_desc *tx_desc;
    dma_addr_t dma_addr;
​
    // 1. 获取当前 TX 描述符
    tx_desc = &priv->tx_chain[priv->tx_cur_desc];
​
    // 2. 映射 SKB 数据到 DMA 地址
    dma_addr = dma_map_single(priv->device, skb->data, skb->len, DMA_TO_DEVICE);
    if (dma_mapping_error(priv->device, dma_addr)) {
        netdev_err(dev, "Failed to map TX DMA\n");
        dev_kfree_skb(skb);
        return NETDEV_TX_OK;
    }
​
    tx_desc->addr = dma_addr;
    tx_desc->len = skb->len;
    tx_desc->status = TX_DESC_STATUS_OWN; // 将所有权交给 DMA
​
    // 3. 触发 DMA 发送
    stmmac_tx_dma_start(priv);
​
    priv->tx_cur_desc = (priv->tx_cur_desc + 1) % priv->dma_tx_size;
​
    // 4. 如果 TX 环已满,停止队列
    if (priv->tx_cur_desc == priv->tx_next_desc) {
        netif_stop_queue(dev);
    }
​
    dev_kfree_skb(skb); // SKB 数据已被 DMA 映射,交给硬件后释放
    return NETDEV_TX_OK;
}

第三部分:资深视角 —— STMMAC 调试与性能优化

3.1 丢包调试 (Dropped Packets)

现象ifconfig eth0 显示 rx_droppedtx_dropped 不断上升。

原因

  • RX 掉包:RX 描述符不足,DMA 缓冲区无法及时返回给硬件。

  • TX 掉包:TX 队列满,上层无法入队。

  • 中断风暴:中断处理未及时完成,导致新帧被丢弃。

调试方法

  1. 查看系统统计

    ethtool -S eth0      # 查看硬件统计(丢包、CRC 错误)
    netstat -i           # 查看接口统计
  2. 查看 RX 环

    # 查看 DMA RX 环大小(默认 256)
    cat /sys/class/net/eth0/rings/rx_pending
  3. 使用 trace-cmd 跟踪 NAPI

    # 跟踪 NAPI 轮询过程
    trace-cmd record -e net:netif_receive_skb -e net:netif_napi_add -e net:napi_poll
    trace-cmd report | grep -E "NAPI|drop"

    如果在 napi_poll 中看到大量 WORK_DONE 等于 0,表示 NAPI 未及时处理数据。

  4. 增加 NAPI budget 或环大小

    # 增加 RX 描述符数量
    ethtool -G eth0 rx 1024 tx 512
    # 增加 NAPI 预算
    echo 300 > /sys/module/stmmac/parameters/napi_budget

3.2 吞吐量低 (Low Throughput)

现象iperf3 -c host 只能跑到 200Mbps,而硬件支持 1Gbps。

原因

  • PHY 协商到低速(如 100Mbps)。

  • 中断聚合设置不当。

  • DMA 突发长度过小。

调试方法

  1. 检查链路速度

    ethtool eth0
  2. 检查中断聚合

    ethtool -c eth0          # 查看聚合参数
    ethtool -C eth0 rx-usecs 20 rx-frames 32  # 增加聚合
  3. 检查 DMA 突发长度

    # 查看驱动中的 DMA 配置
    devmem2 <base_addr>+0x200  # 读取 DMA 控制寄存器

    如果突发长度是 64 字节,尝试增加到 256 或 512。

  4. 使用 perf 监控中断处理时间

    perf record -e irq:irq_handler_entry -e irq:irq_handler_exit -a -- sleep 10
    perf script | grep -E "stmmac|napi"

    如果处理时间超过 100us,可能 NAPI 未及时完成。

3.3 中断风暴 (Interrupt Storm)

现象cat /proc/interrupts | grep eth0 显示每秒数千次中断,CPU 占用高。

原因

  • 中断聚合未启用或设置过小。

  • RX 描述符环过小,导致硬件频繁触发 RX 中断。

  • 驱动未正确清除中断标志。

调试方法

  1. 启用中断聚合

    ethtool -C eth0 rx-usecs 100 tx-usecs 100
  2. 使用 bpftrace 动态插桩 ISR

    bpftrace -e 'kprobe:stmmac_interrupt /arg1 == <irq_num>/ { @count[tid] = count(); @time[tid] = nsecs; } kretprobe:stmmac_interrupt { @us[tid] = (nsecs - @time[tid]) / 1000; }'
  3. 强制进入 NAPI 轮询模式

    # 关闭硬件中断,仅通过 NAPI 轮询
    echo 1 > /sys/class/net/eth0/napi_mode

3.4 RGMII 时序问题 (Phantom Issues)

现象:链路正常,但丢包率非常高,CRC 错误多。RGMII 接口对相位非常敏感。

根本原因:TX/RX 时钟与数据线的相位偏移。

调试方法

  1. 检查当前延迟

    # 读取硬件延迟配置
    devmem2 <base_addr>+0x34  # RGMII_CTRL 寄存器
  2. 调整延迟:在 DTS 中修改 tx_delayrx_delay(从 0 到 31)。

  3. 测试不同相位

    # 逐步调整,每次 1 步,测试吞吐量
    for i in {0..31}; do echo $i > /sys/class/net/eth0/rx_delay; done

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

场景:服务器通过千兆网卡发送大文件时,偶尔出现持续 100ms 的卡顿,iperf3 测试间歇性降速到 100Mbps。

分析流程

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

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

    • cat /proc/interrupts 显示网卡中断触发频繁。

  2. 网络层(图谱的 Device Drivers -> Ethernet MAC 层):

    ethtool -S eth0

    发现 rx_errorsrx_missed 在卡顿期间急剧增加。

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

    trace-cmd record -e dma:* -e net:netdev_xmit -a -- sleep 5
    trace-cmd report | grep -E "dma|stmmac"

    发现 dma_sync_single_for_cpu 被频繁调用,且耗时较长。

  4. 根本原因

    • TX DMA 描述符环太小(仅 64 个)。

    • 当突发流量到来时,描述符环迅速被填满,驱动进入 netif_stop_queue()

    • 内核 dma_sync_single_for_cpu 对每个 TX 描述符进行缓存刷新,造成延迟。

  5. 解决方案

    • 增加 DMA 描述符环大小:

      ethtool -G eth0 tx 1024
    • 启用 tx_napi 模式,减少中断频率。

    • 使用 dma_alloc_coherent 分配 TX 缓冲区,避免 sync 开销。

    • 增加 tx_max_coalesced_frames 到 64。


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

控制器 协同方式 调试关键点
PHY 通过 MDIO 总线管理物理层芯片 链路协商、信号质量
DMA 数据包搬运 描述符环大小、对齐、带宽
Interrupt Controller (GIC) 将中断路由到特定 CPU 中断亲和性、优先级
Power Management WOL (Wake-on-LAN)、链路休眠 WOL 配置、PME 信号
PTP Hardware Clock (PHC) 高精度时间同步 PTP 事件中断

第八部分 MIPI D-PHY 控制器

第一章 MIPI D-PHY 控制器在 Platform Bus 中的位置

MIPI D-PHY 是 MIPI 联盟定义的物理层接口标准,用于连接 SoC 与 MIPI DSI(显示接口)或 MIPI CSI(摄像头接口)设备。它通过高速差分信号传输数据,通常作为 Platform 设备挂载在 SoC 内部总线上,是 CSI 和 DSI 控制器的物理层基础。

在 Linux 5.10 中,MIPI D-PHY 驱动通常位于 drivers/phy/drivers/phy/mipi/,通过 PHY 框架 暴露控制接口。典型驱动包括 phy-rockchip-mipi-dphy.cphy-mipi-dphy-standalone.c 等。

1.1 硬件关键概念

  • D-PHY 核心寄存器:控制寄存器、状态寄存器、中断使能、链路配置。

  • 高速模式 (HS Mode):用于传输视频/图像数据,速率可达 1Gbps 以上。

  • 低功耗模式 (LP Mode):用于控制信号和低功耗状态。

  • 时钟通道 (Clock Lane):提供 DDR 时钟信号。

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

  • ESD 保护:内置静电防护电路。

  • 自动校准:支持自动补偿信号质量。

1.2 核心代码

// 基于 Linux 5.10 drivers/phy/rockchip/phy-rockchip-mipi-dphy.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/delay.h>
​
/**
 * @brief Rockchip MIPI D-PHY 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 MIPI D-PHY 硬件实例。
 */
struct rockchip_mipi_dphy {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 */
    struct clk *pclk;               /**< 总线时钟 */
    struct clk *ref_clk;            /**< 参考时钟 */
    struct phy *phy;                /**< PHY 抽象 */
    struct device *dev;             /**< 设备指针 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 version;                    /**< PHY 版本 */
    u32 lanes;                      /**< 数据通道数 */
    u32 max_speed;                  /**< 最大速度 (Mbps) */
    u32 current_speed;              /**< 当前速度 */
    struct completion completion;   /**< 完成同步 */
};
​
/* 寄存器偏移量 (Rockchip MIPI D-PHY) */
#define DPHY_CTRL                   0x00
#define DPHY_STATUS                 0x04
#define DPHY_INT_EN                 0x08
#define DPHY_INT_STAT               0x0C
#define DPHY_CLK_CTRL               0x10
#define DPHY_CLK_STATUS             0x14
#define DPHY_LANE_CTRL              0x20
#define DPHY_LANE_STATUS            0x24
#define DPHY_CALIBRATION            0x30
#define DPHY_CALIBRATION_STATUS     0x34
#define DPHY_POWER_CTRL             0x40
#define DPHY_POWER_STATUS           0x44
​
/**
 * @brief 初始化 MIPI D-PHY 硬件。
 * 
 * @param dphy 指向 rockchip_mipi_dphy 结构。
 * @return 0 成功。
 */
static int rockchip_mipi_dphy_hw_init(struct rockchip_mipi_dphy *dphy)
{
    u32 ctrl;
​
    // 1. 上电 D-PHY
    writel(0x1, dphy->base + DPHY_POWER_CTRL);
    msleep(10);
​
    // 2. 检查电源状态
    if (!(readl(dphy->base + DPHY_POWER_STATUS) & 0x1)) {
        dev_err(dphy->dev, "D-PHY power on failed\n");
        return -EIO;
    }
​
    // 3. 配置时钟通道
    ctrl = readl(dphy->base + DPHY_CLK_CTRL);
    ctrl |= (1 << 0);  // 启用时钟通道
    writel(ctrl, dphy->base + DPHY_CLK_CTRL);
​
    // 4. 配置数据通道
    ctrl = readl(dphy->base + DPHY_LANE_CTRL);
    ctrl &= ~0x0F;
    ctrl |= dphy->lanes;  // 设置通道数
    writel(ctrl, dphy->base + DPHY_LANE_CTRL);
​
    // 5. 执行自动校准
    writel(0x1, dphy->base + DPHY_CALIBRATION);
    msleep(50);
​
    // 6. 检查校准状态
    if (!(readl(dphy->base + DPHY_CALIBRATION_STATUS) & 0x1)) {
        dev_err(dphy->dev, "D-PHY calibration failed\n");
        return -EIO;
    }
​
    return 0;
}
​
/**
 * @brief 配置 D-PHY 速度。
 * 
 * @param dphy 指向 rockchip_mipi_dphy 结构。
 * @param speed 目标速度 (Mbps)。
 */
static int rockchip_mipi_dphy_set_speed(struct rockchip_mipi_dphy *dphy,
                                        u32 speed)
{
    u32 ctrl, div;
​
    if (speed > dphy->max_speed) {
        dev_err(dphy->dev, "Speed %d exceeds max %d\n", speed, dphy->max_speed);
        return -EINVAL;
    }
​
    // 1. 计算分频系数 (基于参考时钟)
    u32 ref_clk_rate = clk_get_rate(dphy->ref_clk);
    div = ref_clk_rate / (speed * 1000000);
    if (div < 1) div = 1;
    if (div > 0xFF) div = 0xFF;
​
    // 2. 配置时钟分频
    ctrl = readl(dphy->base + DPHY_CLK_CTRL);
    ctrl &= ~0xFF00;
    ctrl |= (div << 8);
    writel(ctrl, dphy->base + DPHY_CLK_CTRL);
​
    dphy->current_speed = speed;
​
    return 0;
}
​
/**
 * @brief 启动 MIPI D-PHY 传输。
 * 
 * @param dphy 指向 rockchip_mipi_dphy 结构。
 * @return 0 成功。
 */
static int rockchip_mipi_dphy_start(struct rockchip_mipi_dphy *dphy)
{
    u32 ctrl;
​
    // 1. 启用高速模式
    ctrl = readl(dphy->base + DPHY_CTRL);
    ctrl |= (1 << 0);  // 启用高速模式
    ctrl |= (1 << 1);  // 启用数据通道
    writel(ctrl, dphy->base + DPHY_CTRL);
​
    // 2. 启用中断
    writel(0x0F, dphy->base + DPHY_INT_EN);
​
    return 0;
}
​
/**
 * @brief 停止 MIPI D-PHY 传输。
 * 
 * @param dphy 指向 rockchip_mipi_dphy 结构。
 */
static void rockchip_mipi_dphy_stop(struct rockchip_mipi_dphy *dphy)
{
    // 1. 禁用中断
    writel(0x00, dphy->base + DPHY_INT_EN);
​
    // 2. 禁用高速模式
    u32 ctrl = readl(dphy->base + DPHY_CTRL);
    ctrl &= ~(1 << 0);
    ctrl &= ~(1 << 1);
    writel(ctrl, dphy->base + DPHY_CTRL);
}
​
/**
 * @brief MIPI D-PHY 中断处理函数。
 * 
 * 处理链路错误、自动校准完成等事件。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_mipi_dphy 结构。
 */
static irqreturn_t rockchip_mipi_dphy_irq_handler(int irq, void *dev_id)
{
    struct rockchip_mipi_dphy *dphy = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(dphy->base + DPHY_INT_STAT);
​
    // 2. 处理校准完成中断
    if (int_stat & (1 << 0)) {
        writel(1 << 0, dphy->base + DPHY_INT_STAT);
        complete(&dphy->completion);
    }
​
    // 3. 处理时钟锁定丢失
    if (int_stat & (1 << 1)) {
        dev_err(dphy->dev, "D-PHY clock lock lost\n");
        writel(1 << 1, dphy->base + DPHY_INT_STAT);
        // 可能需要重新校准
    }
​
    // 4. 处理数据通道错误
    if (int_stat & (1 << 2)) {
        dev_err(dphy->dev, "D-PHY data lane error\n");
        writel(1 << 2, dphy->base + DPHY_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief PHY 框架操作:初始化。
 * 
 * @param phy 指向 phy 结构。
 * @return 0 成功。
 */
static int rockchip_mipi_dphy_init(struct phy *phy)
{
    struct rockchip_mipi_dphy *dphy = phy_get_drvdata(phy);
​
    return rockchip_mipi_dphy_hw_init(dphy);
}
​
/**
 * @brief PHY 框架操作:上电。
 * 
 * @param phy 指向 phy 结构。
 * @return 0 成功。
 */
static int rockchip_mipi_dphy_power_on(struct phy *phy)
{
    struct rockchip_mipi_dphy *dphy = phy_get_drvdata(phy);
​
    return rockchip_mipi_dphy_start(dphy);
}
​
/**
 * @brief PHY 框架操作:断电。
 * 
 * @param phy 指向 phy 结构。
 * @return 0 成功。
 */
static int rockchip_mipi_dphy_power_off(struct phy *phy)
{
    struct rockchip_mipi_dphy *dphy = phy_get_drvdata(phy);
​
    rockchip_mipi_dphy_stop(dphy);
    return 0;
}
​
/**
 * @brief PHY 框架操作:配置。
 * 
 * @param phy 指向 phy 结构。
 * @param params 指向 phy_ops_args。
 * @return 0 成功。
 */
static int rockchip_mipi_dphy_configure(struct phy *phy,
                                        struct phy_ops_args *params)
{
    struct rockchip_mipi_dphy *dphy = phy_get_drvdata(phy);
​
    if (params->speed) {
        return rockchip_mipi_dphy_set_speed(dphy, params->speed);
    }
    return 0;
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 MIPI D-PHY 硬件,注册 PHY 设备。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_mipi_dphy_probe(struct platform_device *pdev)
{
    struct rockchip_mipi_dphy *dphy;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
    if (!dphy)
        return -ENOMEM;
    platform_set_drvdata(pdev, dphy);
    dphy->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;
    }
    dphy->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(dphy->base))
        return PTR_ERR(dphy->base);
​
    // 3. 获取时钟
    dphy->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(dphy->pclk))
        return PTR_ERR(dphy->pclk);
    dphy->ref_clk = devm_clk_get(&pdev->dev, "ref_clk");
    if (IS_ERR(dphy->ref_clk))
        return PTR_ERR(dphy->ref_clk);
​
    clk_prepare_enable(dphy->pclk);
    clk_prepare_enable(dphy->ref_clk);
​
    // 4. 获取中断
    irq = platform_get_irq(pdev, 0);
    if (irq < 0) {
        ret = irq;
        goto err_clk;
    }
    dphy->irq = irq;
​
    // 5. 读取 D-PHY 配置
    dphy->lanes = 4;  // 假设 4 通道
    dphy->max_speed = 1500;  // 1.5 Gbps
    dphy->current_speed = 1000;  // 1 Gbps
​
    spin_lock_init(&dphy->lock);
    init_completion(&dphy->completion);
​
    // 6. 注册中断
    ret = devm_request_irq(&pdev->dev, dphy->irq, rockchip_mipi_dphy_irq_handler,
                           IRQF_SHARED, "rockchip-dphy", dphy);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request IRQ\n");
        goto err_clk;
    }
​
    // 7. 创建 PHY 设备
    dphy->phy = devm_phy_create(&pdev->dev, NULL, &rockchip_mipi_dphy_ops);
    if (IS_ERR(dphy->phy)) {
        dev_err(&pdev->dev, "Failed to create PHY\n");
        ret = PTR_ERR(dphy->phy);
        goto err_clk;
    }
    phy_set_drvdata(dphy->phy, dphy);
​
    // 8. 注册 PHY 设备
    ret = devm_phy_register(&pdev->dev, dphy->phy);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register PHY\n");
        goto err_clk;
    }
​
    dev_info(&pdev->dev, "Rockchip MIPI D-PHY registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, dphy->irq);
    return 0;
​
err_clk:
    clk_disable_unprepare(dphy->ref_clk);
    clk_disable_unprepare(dphy->pclk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_mipi_dphy_remove(struct platform_device *pdev)
{
    struct rockchip_mipi_dphy *dphy = platform_get_drvdata(pdev);
​
    // 1. 停止 D-PHY
    rockchip_mipi_dphy_stop(dphy);
​
    // 2. 禁用时钟
    clk_disable_unprepare(dphy->ref_clk);
    clk_disable_unprepare(dphy->pclk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_mipi_dphy_of_match[] = {
    { .compatible = "rockchip,rk3399-mipi-dphy" },
    { .compatible = "rockchip,rk3568-mipi-dphy" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_mipi_dphy_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_mipi_dphy_driver = {
    .probe = rockchip_mipi_dphy_probe,
    .remove = rockchip_mipi_dphy_remove,
    .driver = {
        .name = "rockchip-mipi-dphy",
        .of_match_table = rockchip_mipi_dphy_of_match,
    },
};
module_platform_driver(rockchip_mipi_dphy_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip MIPI D-PHY Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 MIPI D-PHY 调试核心难点

3.1 信号质量问题

现象:DSI/CSI 输出图像出现花屏、条纹、闪烁,降低速度后正常。

原因

  • 信号完整性差(走线过长、阻抗不匹配)。

  • 时钟通道抖动过大。

  • D-PHY 驱动强度不足。

调试方法

  1. 查看 D-PHY 状态

    devmem2 <base>+0x04  # DPHY_STATUS
  2. 检查时钟锁定状态

    devmem2 <base>+0x14  # DPHY_CLK_STATUS
  3. 调整驱动强度

    # 调整驱动强度寄存器 (假设存在)
    devmem2 <base>+0x40 0x03

3.2 链路建立失败

现象:CSI 或 DSI 设备无法启动,dmesg 显示 "D-PHY calibration failed"。

原因

  • 参考时钟频率不正确。

  • 电源电压不足。

  • ESD 保护电路触发。

调试方法

  1. 检查参考时钟

    cat /sys/kernel/debug/clk/clk_summary | grep ref_clk
  2. 增加校准重试次数

    # 在驱动中增加重试逻辑
    devmem2 <base>+0x30 0x01  # 重启校准
  3. 检查电源电压:用万用表测量 MIPI 电源引脚。

3.3 高速模式无法进入

现象:D-PHY 只能工作在低功耗模式,无法切换到高速模式。

原因

  • PLL 未能锁定。

  • 通道数配置错误。

  • 速度设置过高。

调试方法

  1. 检查 PLL 状态

    devmem2 <base>+0x04  # DPHY_STATUS
  2. 降低速度

    # 设置速度为 500 Mbps
    devmem2 <base>+0x10 0x12345678

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

控制器 协同方式 调试关键点
CSI Controller 通过 D-PHY 接收图像数据 信号质量、校准
DSI Controller 通过 D-PHY 发送显示数据 链路稳定、速度匹配
PLL 提供高速时钟 锁定状态、频率稳定
Power Management 省电模式切换 低功耗唤醒
Clock Controller 提供参考时钟 频率精度、抖动

第九部分 MDIO 总线控制器 (Rockchip MDIO)

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

MDIO (Management Data Input/Output) 总线是 IEEE 802.3 定义的用于管理 PHY 芯片的串行接口,包含时钟线(MDC)和数据线(MDIO)。在 SoC 中,MDIO 控制器通常作为 Platform 设备挂载在内部总线上,为以太网 MAC 提供访问 PHY 寄存器的通道。

在 Linux 5.10 中,MDIO 控制器驱动位于 drivers/net/ethernet/drivers/net/mdio/,通过 MDIO 总线框架 暴露管理接口。典型驱动包括 mdio-rockchip.cmdio-mux.c 等。

1.1 硬件关键概念

  • MDIO 控制器寄存器:控制寄存器、状态寄存器、数据寄存器、时钟分频。

  • 管理帧格式:包含 PHY 地址、寄存器地址、读/写位、数据。

  • 时钟分频:根据总线速度计算 MDC 时钟分频系数(通常 MDC 为 2.5MHz)。

  • 中断:有些控制器支持 MDIO 传输完成中断。

  • PHY 扫描:自动检测总线上的 PHY 地址。

  • 共享总线:支持多个 PHY 挂载在同一 MDIO 总线。

1.2 核心代码

// 基于 Linux 5.10 drivers/net/ethernet/stmicro/stmmac/dwmac-mdio.c (精简版)
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/mdio.h>
#include <linux/phy.h>
​
/**
 * @brief Rockchip MDIO 控制器的私有数据结构。
 * 
 * 对应于图中 Platform Bus 上的 MDIO 控制器硬件实例。
 */
struct rockchip_mdio_priv {
    void __iomem *base;             /**< 映射后的寄存器基址 */
    int irq;                        /**< 中断号 (某些版本支持) */
    struct clk *mdio_clk;           /**< MDIO 时钟 */
    struct clk *pclk;               /**< 总线时钟 */
    struct device *dev;             /**< 设备指针 */
    struct mii_bus *bus;            /**< MII 总线抽象 */
    spinlock_t lock;                /**< 硬件保护锁 */
    u32 bus_freq;                   /**< MDC 时钟频率 (Hz) */
    u32 phy_mask;                   /**< PHY 扫描掩码 */
    struct completion complete;     /**< 传输完成同步 */
    u32 last_data;                  /**< 上一次读写数据 */
};
​
/* 寄存器偏移量 (Rockchip MDIO) */
#define MDIO_CTRL                   0x00
#define MDIO_STATUS                 0x04
#define MDIO_DATA                   0x08
#define MDIO_INT_EN                 0x0C
#define MDIO_INT_STAT               0x10
#define MDIO_CLK_DIV                0x14
#define MDIO_DEV_ADDR               0x18
#define MDIO_OP_CODE                0x1C
​
/* 控制寄存器位定义 */
#define MDIO_CTRL_ENABLE            (1 << 0)  /**< MDIO 使能 */
#define MDIO_CTRL_READ              (1 << 1)  /**< 读操作 */
#define MDIO_CTRL_WRITE             (0 << 1)  /**< 写操作 */
#define MDIO_CTRL_START             (1 << 2)  /**< 启动传输 */
#define MDIO_CTRL_BUSY              (1 << 3)  /**< MDIO 忙 */
​
/**
 * @brief 初始化 MDIO 硬件。
 * 
 * @param mdio 指向 rockchip_mdio_priv 结构。
 * @return 0 成功。
 */
static int rockchip_mdio_hw_init(struct rockchip_mdio_priv *mdio)
{
    u32 ctrl;
​
    // 1. 启用 MDIO 控制器
    ctrl = MDIO_CTRL_ENABLE;
    writel(ctrl, mdio->base + MDIO_CTRL);
​
    // 2. 设置时钟分频 (使 MDC 达到 2.5MHz)
    // 假设输入时钟为 50MHz,则需要分频 20
    u32 div = clk_get_rate(mdio->mdio_clk) / (mdio->bus_freq * 2);
    if (div < 1) div = 1;
    if (div > 0xFF) div = 0xFF;
    writel(div, mdio->base + MDIO_CLK_DIV);
​
    // 3. 检查状态
    if (!(readl(mdio->base + MDIO_STATUS) & 0x1)) {
        dev_err(mdio->dev, "MDIO controller not ready\n");
        return -EIO;
    }
​
    return 0;
}
​
/**
 * @brief 执行 MDIO 读操作。
 * 
 * @param mdio 指向 rockchip_mdio_priv 结构。
 * @param phy_id PHY 地址 (0-31)。
 * @param reg 寄存器地址 (0-31)。
 * @return 16 位数据,-1 错误。
 */
static int rockchip_mdio_read(struct rockchip_mdio_priv *mdio,
                              int phy_id, int reg)
{
    u32 ctrl;
    int ret;
​
    // 1. 等待 MDIO 空闲
    while (readl(mdio->base + MDIO_STATUS) & MDIO_CTRL_BUSY)
        ;
​
    // 2. 设置 PHY 地址和寄存器地址
    writel((phy_id << 5) | reg, mdio->base + MDIO_DEV_ADDR);
​
    // 3. 启动读操作
    ctrl = MDIO_CTRL_ENABLE | MDIO_CTRL_READ | MDIO_CTRL_START;
    writel(ctrl, mdio->base + MDIO_CTRL);
​
    // 4. 等待传输完成
    if (!wait_for_completion_timeout(&mdio->complete, 100 * HZ)) {
        dev_err(mdio->dev, "MDIO read timeout\n");
        return -ETIMEDOUT;
    }
​
    // 5. 读取数据
    ret = readl(mdio->base + MDIO_DATA) & 0xFFFF;
​
    return ret;
}
​
/**
 * @brief 执行 MDIO 写操作。
 * 
 * @param mdio 指向 rockchip_mdio_priv 结构。
 * @param phy_id PHY 地址。
 * @param reg 寄存器地址。
 * @param data 16 位数据。
 * @return 0 成功。
 */
static int rockchip_mdio_write(struct rockchip_mdio_priv *mdio,
                               int phy_id, int reg, u16 data)
{
    u32 ctrl;
​
    // 1. 等待 MDIO 空闲
    while (readl(mdio->base + MDIO_STATUS) & MDIO_CTRL_BUSY)
        ;
​
    // 2. 设置 PHY 地址和寄存器地址
    writel((phy_id << 5) | reg, mdio->base + MDIO_DEV_ADDR);
​
    // 3. 写入数据
    writel(data, mdio->base + MDIO_DATA);
​
    // 4. 启动写操作
    ctrl = MDIO_CTRL_ENABLE | MDIO_CTRL_WRITE | MDIO_CTRL_START;
    writel(ctrl, mdio->base + MDIO_CTRL);
​
    // 5. 等待传输完成
    if (!wait_for_completion_timeout(&mdio->complete, 100 * HZ)) {
        dev_err(mdio->dev, "MDIO write timeout\n");
        return -ETIMEDOUT;
    }
​
    return 0;
}
​
/**
 * @brief MII 总线读取回调。
 * 
 * @param bus 指向 mii_bus。
 * @param phy_id PHY 地址。
 * @param reg 寄存器地址。
 * @return 16 位数据。
 */
static int rockchip_mdio_bus_read(struct mii_bus *bus, int phy_id, int reg)
{
    struct rockchip_mdio_priv *mdio = bus->priv;
​
    return rockchip_mdio_read(mdio, phy_id, reg);
}
​
/**
 * @brief MII 总线写入回调。
 * 
 * @param bus 指向 mii_bus。
 * @param phy_id PHY 地址。
 * @param reg 寄存器地址。
 * @param data 16 位数据。
 * @return 0 成功。
 */
static int rockchip_mdio_bus_write(struct mii_bus *bus, int phy_id,
                                   int reg, u16 data)
{
    struct rockchip_mdio_priv *mdio = bus->priv;
​
    return rockchip_mdio_write(mdio, phy_id, reg, data);
}
​
/**
 * @brief MDIO 中断处理函数。
 * 
 * 处理传输完成中断。
 *
 * @param irq 中断号。
 * @param dev_id 指向 rockchip_mdio_priv 结构。
 */
static irqreturn_t rockchip_mdio_irq_handler(int irq, void *dev_id)
{
    struct rockchip_mdio_priv *mdio = dev_id;
    u32 int_stat;
​
    // 1. 读取中断状态
    int_stat = readl(mdio->base + MDIO_INT_STAT);
​
    // 2. 处理传输完成中断
    if (int_stat & (1 << 0)) {
        writel(1 << 0, mdio->base + MDIO_INT_STAT);
        complete(&mdio->complete);
    }
​
    // 3. 处理错误中断
    if (int_stat & (1 << 1)) {
        dev_err(mdio->dev, "MDIO error\n");
        writel(1 << 1, mdio->base + MDIO_INT_STAT);
    }
​
    return IRQ_HANDLED;
}
​
/**
 * @brief 扫描并注册所有 PHY 设备。
 * 
 * @param mdio 指向 rockchip_mdio_priv 结构。
 * @param bus 指向 mii_bus。
 */
static void rockchip_mdio_scan_phys(struct rockchip_mdio_priv *mdio,
                                    struct mii_bus *bus)
{
    int i;
​
    for (i = 0; i < PHY_MAX_ADDR; i++) {
        // 跳过掩码指定的地址
        if (mdio->phy_mask & (1 << i))
            continue;
​
        // 尝试读取 PHY ID
        if (mdiobus_get_phy(bus, i)) {
            dev_info(mdio->dev, "PHY found at address %d\n", i);
        }
    }
}
​
/**
 * @brief Platform 探测函数。
 * 
 * 初始化 MDIO 控制器硬件,注册 MII 总线。
 *
 * @param pdev Platform 设备指针。
 * @return 0 成功,负数错误。
 */
static int rockchip_mdio_probe(struct platform_device *pdev)
{
    struct rockchip_mdio_priv *mdio;
    struct mii_bus *bus;
    struct resource *res;
    int ret, irq;
​
    // 1. 分配私有数据结构
    mdio = devm_kzalloc(&pdev->dev, sizeof(*mdio), GFP_KERNEL);
    if (!mdio)
        return -ENOMEM;
    platform_set_drvdata(pdev, mdio);
    mdio->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;
    }
    mdio->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(mdio->base))
        return PTR_ERR(mdio->base);
​
    // 3. 获取时钟
    mdio->mdio_clk = devm_clk_get(&pdev->dev, "mdio_clk");
    if (IS_ERR(mdio->mdio_clk))
        return PTR_ERR(mdio->mdio_clk);
    mdio->pclk = devm_clk_get(&pdev->dev, "pclk");
    if (IS_ERR(mdio->pclk))
        return PTR_ERR(mdio->pclk);
​
    clk_prepare_enable(mdio->mdio_clk);
    clk_prepare_enable(mdio->pclk);
​
    // 4. 获取中断 (可选)
    irq = platform_get_irq(pdev, 0);
    if (irq > 0) {
        mdio->irq = irq;
        ret = devm_request_irq(&pdev->dev, mdio->irq,
                               rockchip_mdio_irq_handler,
                               IRQF_SHARED, "rockchip-mdio", mdio);
        if (ret) {
            dev_err(&pdev->dev, "Failed to request IRQ\n");
            goto err_clk;
        }
    } else {
        // 没有中断,使用轮询模式
        mdio->irq = -1;
    }
​
    // 5. 硬件初始化
    mdio->bus_freq = 2500000;  // 2.5 MHz
    mdio->phy_mask = 0;        // 扫描所有地址
    spin_lock_init(&mdio->lock);
    init_completion(&mdio->complete);
​
    ret = rockchip_mdio_hw_init(mdio);
    if (ret) {
        dev_err(&pdev->dev, "Failed to init hardware\n");
        goto err_clk;
    }
​
    // 6. 分配并注册 MII 总线
    bus = devm_mdiobus_alloc(&pdev->dev);
    if (!bus) {
        ret = -ENOMEM;
        goto err_clk;
    }
    mdio->bus = bus;
​
    bus->name = "rockchip-mdio";
    bus->read = rockchip_mdio_bus_read;
    bus->write = rockchip_mdio_bus_write;
    bus->priv = mdio;
    snprintf(bus->id, MII_BUS_ID_SIZE, "rockchip-mdio-%d", pdev->id);
​
    ret = devm_mdiobus_register(&pdev->dev, bus);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register MDIO bus\n");
        goto err_clk;
    }
​
    // 7. 扫描 PHY
    rockchip_mdio_scan_phys(mdio, bus);
​
    dev_info(&pdev->dev, "Rockchip MDIO controller registered at 0x%llx, IRQ %d\n",
             (unsigned long long)res->start, mdio->irq);
    return 0;
​
err_clk:
    clk_disable_unprepare(mdio->pclk);
    clk_disable_unprepare(mdio->mdio_clk);
    return ret;
}
​
/**
 * @brief 移除函数。
 * 
 * @param pdev Platform 设备指针。
 */
static int rockchip_mdio_remove(struct platform_device *pdev)
{
    struct rockchip_mdio_priv *mdio = platform_get_drvdata(pdev);
​
    // 1. 禁用 MDIO 控制器
    writel(0, mdio->base + MDIO_CTRL);
​
    // 2. 禁用时钟
    clk_disable_unprepare(mdio->pclk);
    clk_disable_unprepare(mdio->mdio_clk);
​
    return 0;
}
​
/* 设备树匹配表 */
static const struct of_device_id rockchip_mdio_of_match[] = {
    { .compatible = "rockchip,rk3399-mdio" },
    { .compatible = "rockchip,rk3568-mdio" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_mdio_of_match);
​
/* Platform 驱动结构 */
static struct platform_driver rockchip_mdio_driver = {
    .probe = rockchip_mdio_probe,
    .remove = rockchip_mdio_remove,
    .driver = {
        .name = "rockchip-mdio",
        .of_match_table = rockchip_mdio_of_match,
    },
};
module_platform_driver(rockchip_mdio_driver);
​
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Rockchip MDIO Controller Driver");
MODULE_LICENSE("GPL v2");

第三章 MDIO 调试核心难点

3.1 MDIO 总线无法访问 PHY

现象mii-tool eth0 显示 No PHY foundPHY ID reads failed

原因

  • MDIO 时钟频率过高(应 ≤ 2.5MHz)。

  • PHY 地址错误(DTS 中的 phy-address 与实际不符)。

  • MDIO 引脚复用配置错误。

调试方法

  1. 检查 MDIO 时钟

    devmem2 <base>+0x14  # MDIO_CLK_DIV
  2. 手动读取 PHY ID

    # 使用 mdio-tool 或 devmem2 直接操作
    devmem2 <base>+0x08 0x00  # 尝试读 PHY 地址 0
  3. 检查 PHY 地址

    ethtool -s eth0 phyaddr <addr>

3.2 MDIO 传输超时

现象dmesg 出现 "MDIO write timeout" 或 "MDIO read timeout"。

原因

  • 中断未配置(使用轮询模式时超时值过小)。

  • MDIO 总线被拉死(PHY 芯片故障或线路短路)。

  • 时钟频率过低。

调试方法

  1. 增加超时值

    # 在驱动中增加 timeout = 500 * HZ
  2. 检查 MDIO 状态

    devmem2 <base>+0x04  # MDIO_STATUS
  3. 禁用中断使用轮询

    # 在 driver 中设置 polling = true

3.3 PHY 扫描失败

现象ethtool -s eth0 phyaddr 0 后 PHY 仍然无法工作。

原因

  • PHY 扫描掩码 (phy_mask) 配置错误,导致有效 PHY 被跳过。

  • PHY 芯片要求特定上电序列。

调试方法

  1. 检查 phy_mask

    devmem2 <base>+0x20  # 读取掩码寄存器
  2. 全扫描

    # 在驱动中设置 phy_mask = 0

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

场景:系统启动后,PHY 无法链接,ethtool -s eth0 设置后仍无法工作,MAC 与 PHY 通信正常,但 PHY 状态寄存器不变。

分析流程

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

    • perf top 显示 stmmac_mdio_read 频繁调用且返回错误。

    • dmesg 显示 "MDIO read failed"。

  2. MDIO 层(Device Drivers -> MDIO 层):

    devmem2 <base>+0x04  # MDIO_STATUS

    发现状态寄存器 BUSY 位一直为 1。

  3. 硬件检查(PHY 层):

    • 用示波器测量 MDC 和 MDIO 信号,发现 MDC 频率为 50MHz(过高)。

    • MDIO 数据线受干扰。

  4. 根本原因

    • MDIO 时钟分频计算错误,导致 MDC 频率高达 50MHz(规范要求 ≤ 2.5MHz)。

    • PHY 芯片无法识别过高的时钟频率。

  5. 解决方案

    • 调整 clk_div 使 MDC = 2.5MHz。

    • 增加滤波电容。

    • 优化后,MDIO 通信正常,PHY 链接建立。


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

控制器 协同方式 调试关键点
Ethernet MAC 通过 MDIO 管理 PHY MDC 频率、PHY 地址
GPIO 控制 PHY 复位引脚 复位时序、电平配置
Clock Controller 提供 MDIO 时钟 频率计算、分频设置
PHY 被管理的物理层芯片 寄存器读写、链路状态
Logo

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

更多推荐