2. 实现GPU电源管理驱动
任务说明
本案例实现SA8155平台上GPU的电源管理驱动,完成以下三个核心职责:时钟使能(为GPU提供运行时钟)、电压调节(通过调节供电电压满足不同频率需求)、电源域控制(控制GPU所在电源域的开关)。驱动通过内核runtime PM框架管理GPU的运行时电源状态,确保GPU在正常工作时处于上电状态,空闲时自动下电以节省功耗。
驱动框架与设备树绑定
驱动基于Linux内核platform_driver模型,通过设备树节点获取时钟、稳压器和电源域资源。设备树中定义GPU节点及其关联的时钟、供电和电源域信息,驱动在probe阶段解析这些资源并完成初始化。
驱动代码实现
以下代码实现GPU电源管理驱动的完整功能。驱动通过dev_pm_ops注册运行时PM回调,在runtime_resume中依次完成电源域开启、电压调节和时钟使能;在runtime_suspend中按相反顺序关闭资源。
// gpu_pwr_drv.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_domain.h>
#include <linux/err.h>
struct gpu_pwr_dev {
struct device *dev;
struct clk *core_clk;
struct clk *iface_clk;
struct regulator *vdd;
struct device *pd_dev;
};
static int gpu_pwr_runtime_resume(struct device *dev)
{
struct gpu_pwr_dev *gpu = dev_get_drvdata(dev);
int ret;
/* 1. 开启电源域 */
if (gpu->pd_dev) {
ret = pm_runtime_resume_and_get(gpu->pd_dev);
if (ret < 0) {
dev_err(dev, "failed to resume power domain: %d\n", ret);
return ret;
}
}
/* 2. 调节电压至GPU工作电压 0.8V */
ret = regulator_set_voltage(gpu->vdd, 800000, 800000);
if (ret < 0) {
dev_err(dev, "failed to set voltage: %d\n", ret);
goto err_pd;
}
ret = regulator_enable(gpu->vdd);
if (ret < 0) {
dev_err(dev, "failed to enable regulator: %d\n", ret);
goto err_pd;
}
/* 3. 使能时钟 */
ret = clk_prepare_enable(gpu->core_clk);
if (ret < 0) {
dev_err(dev, "failed to enable core clk: %d\n", ret);
goto err_reg;
}
ret = clk_prepare_enable(gpu->iface_clk);
if (ret < 0) {
dev_err(dev, "failed to enable iface clk: %d\n", ret);
clk_disable_unprepare(gpu->core_clk);
goto err_reg;
}
dev_info(dev, "GPU power on: clocks enabled, voltage set\n");
return 0;
err_reg:
regulator_disable(gpu->vdd);
err_pd:
if (gpu->pd_dev)
pm_runtime_put_sync(gpu->pd_dev);
return ret;
}
static int gpu_pwr_runtime_suspend(struct device *dev)
{
struct gpu_pwr_dev *gpu = dev_get_drvdata(dev);
/* 按相反顺序关闭资源 */
clk_disable_unprepare(gpu->iface_clk);
clk_disable_unprepare(gpu->core_clk);
regulator_disable(gpu->vdd);
if (gpu->pd_dev)
pm_runtime_put_sync(gpu->pd_dev);
dev_info(dev, "GPU power off\n");
return 0;
}
static const struct dev_pm_ops gpu_pwr_pm_ops = {
RUNTIME_PM_OPS(gpu_pwr_runtime_suspend,
gpu_pwr_runtime_resume, NULL)
};
static int gpu_pwr_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpu_pwr_dev *gpu;
int ret;
gpu = devm_kzalloc(dev, sizeof(*gpu), GFP_KERNEL);
if (!gpu)
return -ENOMEM;
gpu->dev = dev;
dev_set_drvdata(dev, gpu);
/* 获取时钟资源 */
gpu->core_clk = devm_clk_get(dev, "core");
if (IS_ERR(gpu->core_clk)) {
ret = PTR_ERR(gpu->core_clk);
dev_err(dev, "failed to get core clk: %d\n", ret);
return ret;
}
gpu->iface_clk = devm_clk_get(dev, "iface");
if (IS_ERR(gpu->iface_clk)) {
ret = PTR_ERR(gpu->iface_clk);
dev_err(dev, "failed to get iface clk: %d\n", ret);
return ret;
}
/* 获取稳压器 */
gpu->vdd = devm_regulator_get(dev, "vdd");
if (IS_ERR(gpu->vdd)) {
ret = PTR_ERR(gpu->vdd);
dev_err(dev, "failed to get regulator: %d\n", ret);
return ret;
}
/* 获取电源域(可选) */
gpu->pd_dev = dev_pm_domain_attach(dev, true);
if (IS_ERR_OR_NULL(gpu->pd_dev)) {
gpu->pd_dev = NULL;
dev_info(dev, "no power domain attached\n");
}
/* 启用运行时PM */
pm_runtime_enable(dev);
/* 手动触发一次resume,确保GPU上电 */
ret = pm_runtime_resume_and_get(dev);
if (ret < 0) {
dev_err(dev, "failed to resume GPU: %d\n", ret);
pm_runtime_disable(dev);
return ret;
}
dev_info(dev, "GPU power driver probed successfully\n");
return 0;
}
static void gpu_pwr_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
dev_pm_domain_detach(dev, true);
}
static const struct of_device_id gpu_pwr_match[] = {
{ .compatible = "qcom,gpu-pwr" },
{ }
};
MODULE_DEVICE_TABLE(of, gpu_pwr_match);
static struct platform_driver gpu_pwr_driver = {
.probe = gpu_pwr_probe,
.remove = gpu_pwr_remove,
.driver = {
.name = "gpu_pwr",
.of_match_table = gpu_pwr_match,
.pm = &gpu_pwr_pm_ops,
},
};
module_platform_driver(gpu_pwr_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SA8155 GPU Power Management Driver");
设备树配置示例
/* gpu-pwr.dtsi 片段 */
&soc {
gpu_pwr: gpu-pwr@3d00000 {
compatible = "qcom,gpu-pwr";
reg = <0x03d00000 0x1000>;
clocks = <&gpucc GPU_CC_CORE_CLK>,
<&gpucc GPU_CC_IFACE_CLK>;
clock-names = "core", "iface";
vdd-supply = <&gpu_supply>;
power-domains = <&gpu_pd>;
};
};
编译与验证
编译方法:将驱动源码放入内核drivers/gpu/pwr/目录,在Kconfig中添加config GPU_PWR_DRIVER选项,Makefile中添加obj-$(CONFIG_GPU_PWR_DRIVER) += gpu_pwr_drv.o。在内核根目录执行:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig # 启用GPU_PWR_DRIVER
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image
验证步骤与判定标准:
- 将编译好的内核烧录至SA8155目标板,启动后执行
dmesg | grep gpu_pwr,应看到GPU power driver probed successfully和GPU power on日志,说明probe阶段电源管理初始化成功。 - 执行
cat /sys/devices/platform/gpu-pwr@3d00000/power/runtime_status,输出应为active,表示GPU处于上电状态。 - 执行
echo auto > /sys/devices/platform/gpu-pwr@3d00000/power/control启用自动运行时PM,再执行echo 0 > /sys/devices/platform/gpu-pwr@3d00000/power/autosuspend_delay_ms,等待数秒后再次查看runtime_status,应变为suspended,同时dmesg出现GPU power off,说明suspend路径正常。 - 触发GPU使用(如运行图形负载),
runtime_status应自动回到active,dmesg出现GPU power on,证明resume路径工作正常。
以上步骤全部通过,即可判定GPU电源管理驱动实现正确,时钟、电压和电源域控制均按预期工作。