3. 配置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";
};
};
时钟树配置流程
编译与加载
将驱动编译为内核模块,或直接编入内核。若为模块方式:
# 交叉编译(以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
验证方法
完成加载后,通过以下步骤验证时钟配置正确:
- 检查内核日志:执行
dmesg | grep "GPU core clock",应看到类似输出:GPU core clock configured at 553000000 Hz。此输出由驱动中clk_get_rate()返回,反映时钟框架记录的实际频率。 - 读取硬件寄存器:使用devmem工具读取时钟控制寄存器,确认分频配置已写入:
# 读取GPU_CC_CORE_CLK分频寄存器(地址因版本而异,此处为示例)
devmem 0x3D9100 32
# 预期输出:0x00000001(表示分频系数为1,即553MHz输出)
# 注意:实际地址需参考SA8155 TRM手册中GPU_CC寄存器映射表
- 验证时钟门控状态:读取GPU_CC_CORE_CLK_CMD_RCGR寄存器(偏移0x3D9104),bit 0应为1,表示时钟已使能。
判定标准
- 通过:内核日志显示553000000 Hz;寄存器读取值与配置一致;GPU模块可正常访问,无时钟超时错误。
- 失败排查:若
clk_set_rate返回错误,检查GPLL0是否已使能、分频系数是否在支持范围内;若使能失败,检查电源域(CX/MX)是否已上电。