3. 配置GPU时钟树

配置GPU时钟树 学习卡片

任务说明

本案例目标是在SA8155平台上配置GPU时钟树,使GPU核心时钟(GPU_CC_CORE_CLK)运行在指定频率,总线时钟(GPU_CC_CX_GMU_AXI_CLK)按需使能,并通过读取硬件寄存器验证时钟输出与配置一致。实现范围限定为:通过内核时钟框架接口完成时钟获取、频率设置与使能,不涉及DVFS调频策略或底层PLL参数重写。

硬件时钟树结构

SA8155 GPU时钟树由GPU_CC(Graphics Clock Controller)管理,关键节点如下:

  • GPU_CC_CORE_CLK:GPU核心逻辑时钟,源为GPLL0,经DIV分频后输出,最高支持800MHz。
  • GPU_CC_CX_GMU_AXI_CLK:GPU与GMU(Graphics Management Unit)之间的总线接口时钟,源为GPLL0,用于AXI总线通信。
  • GPU_CC_CX_GMU_CLK:GMU自身工作时钟,需在访问GMU寄存器前使能。

时钟树拓扑关系:GPLL0 → 各自分频器 → 时钟门控 → 输出至GPU子系统。配置时需先确保父时钟GPLL0已开启,再设置分频系数,最后打开时钟门控。

代码实现

以下驱动代码在GPU平台驱动初始化阶段调用,完成时钟获取、频率设定与使能操作:

// gpu_clk_config.c
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

struct gpu_clk_handle {
    struct clk *core_clk;
    struct clk *axi_clk;
    struct clk *gmu_clk;
};

static int gpu_clk_configure(struct device *dev, struct gpu_clk_handle *handle)
{
    long core_rate;
    int ret;

    /* 1. 获取三个时钟节点 */
    handle->core_clk = devm_clk_get(dev, "core_clk");
    if (IS_ERR(handle->core_clk)) {
        dev_err(dev, "failed to get core_clk: %ld\n", PTR_ERR(handle->core_clk));
        return PTR_ERR(handle->core_clk);
    }

    handle->axi_clk = devm_clk_get(dev, "axi_clk");
    if (IS_ERR(handle->axi_clk)) {
        dev_err(dev, "failed to get axi_clk: %ld\n", PTR_ERR(handle->axi_clk));
        return PTR_ERR(handle->axi_clk);
    }

    handle->gmu_clk = devm_clk_get(dev, "gmu_clk");
    if (IS_ERR(handle->gmu_clk)) {
        dev_err(dev, "failed to get gmu_clk: %ld\n", PTR_ERR(handle->gmu_clk));
        return PTR_ERR(handle->gmu_clk);
    }

    /* 2. 设置GPU核心时钟频率为553MHz(GPU_CC_CORE_CLK) */
    ret = clk_set_rate(handle->core_clk, 553000000);
    if (ret) {
        dev_err(dev, "failed to set core_clk rate: %d\n", ret);
        return ret;
    }

    /* 3. 使能GMU时钟(必须先于AXI时钟) */
    ret = clk_prepare_enable(handle->gmu_clk);
    if (ret) {
        dev_err(dev, "failed to enable gmu_clk: %d\n", ret);
        return ret;
    }

    /* 4. 使能AXI总线时钟 */
    ret = clk_prepare_enable(handle->axi_clk);
    if (ret) {
        dev_err(dev, "failed to enable axi_clk: %d\n", ret);
        clk_disable_unprepare(handle->gmu_clk);
        return ret;
    }

    /* 5. 使能GPU核心时钟 */
    ret = clk_prepare_enable(handle->core_clk);
    if (ret) {
        dev_err(dev, "failed to enable core_clk: %d\n", ret);
        clk_disable_unprepare(handle->axi_clk);
        clk_disable_unprepare(handle->gmu_clk);
        return ret;
    }

    /* 6. 读取实际配置频率 */
    core_rate = clk_get_rate(handle->core_clk);
    dev_info(dev, "GPU core clock configured at %ld Hz\n", core_rate);

    return 0;
}

static int gpu_probe(struct platform_device *pdev)
{
    struct gpu_clk_handle *handle;
    int ret;

    handle = devm_kzalloc(&pdev->dev, sizeof(*handle), GFP_KERNEL);
    if (!handle)
        return -ENOMEM;

    ret = gpu_clk_configure(&pdev->dev, handle);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, handle);
    return 0;
}

static const struct of_device_id gpu_clk_match[] = {
    { .compatible = "qcom,sa8155-gpu-clk" },
    { }
};

static struct platform_driver gpu_clk_driver = {
    .probe = gpu_probe,
    .driver = {
        .name = "sa8155_gpu_clk",
        .of_match_table = gpu_clk_match,
    },
};
module_platform_driver(gpu_clk_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SA8155 GPU clock tree configuration driver");

设备树配置

在设备树中为GPU节点添加时钟属性,与驱动中的时钟名称对应:

/* sa8155-gpu.dtsi 片段 */
&soc {
    gpu_clk: gpu-clk@3d90000 {
        compatible = "qcom,sa8155-gpu-clk";
        reg = <0x03d90000 0x9000>;
        clocks = <&gpucc GPU_CC_CORE_CLK>,
                 <&gpucc GPU_CC_CX_GMU_AXI_CLK>,
                 <&gpucc GPU_CC_CX_GMU_CLK>;
        clock-names = "core_clk", "axi_clk", "gmu_clk";
    };
};

时钟树配置流程

GPU时钟树配置与验证流程 GPLL0 ~800MHz源 DIV 分频器 GATE 时钟门控 GPU_CC_CORE_CLK 目标553MHz 验证路径:读取RPMH寄存器 地址 0x3D9100(CORE_CLK_CFG) 读取 寄存器值 = 分频系数 预期:0x1(对应553MHz)

编译与加载

将驱动编译为内核模块,或直接编入内核。若为模块方式:

# 交叉编译(以ARM64为例)
aarch64-linux-gnu-gcc -D__KERNEL__ -DMODULE -I<kernel-source>/include \
    -I<kernel-source>/arch/arm64/include -c gpu_clk_config.c -o gpu_clk_config.o

# 或使用内核Kbuild系统
# 将gpu_clk_config.c放入drivers/clk/qcom/目录,并在Kconfig中添加:
# config SA8155_GPU_CLK
#     tristate "SA8155 GPU clock configuration"
#     depends on QCOM_GPUCC_SA8155

验证方法

完成加载后,通过以下步骤验证时钟配置正确:

  1. 检查内核日志:执行dmesg | grep "GPU core clock",应看到类似输出:GPU core clock configured at 553000000 Hz。此输出由驱动中clk_get_rate()返回,反映时钟框架记录的实际频率。
  2. 读取硬件寄存器:使用devmem工具读取时钟控制寄存器,确认分频配置已写入:
# 读取GPU_CC_CORE_CLK分频寄存器(地址因版本而异,此处为示例)
devmem 0x3D9100 32
# 预期输出:0x00000001(表示分频系数为1,即553MHz输出)
# 注意:实际地址需参考SA8155 TRM手册中GPU_CC寄存器映射表
  1. 验证时钟门控状态:读取GPU_CC_CORE_CLK_CMD_RCGR寄存器(偏移0x3D9104),bit 0应为1,表示时钟已使能。

判定标准

  • 通过:内核日志显示553000000 Hz;寄存器读取值与配置一致;GPU模块可正常访问,无时钟超时错误。
  • 失败排查:若clk_set_rate返回错误,检查GPLL0是否已使能、分频系数是否在支持范围内;若使能失败,检查电源域(CX/MX)是否已上电。