2. 实现GPU电源管理驱动

任务说明

本案例实现SA8155平台上GPU的电源管理驱动,完成以下三个核心职责:时钟使能(为GPU提供运行时钟)、电压调节(通过调节供电电压满足不同频率需求)、电源域控制(控制GPU所在电源域的开关)。驱动通过内核runtime PM框架管理GPU的运行时电源状态,确保GPU在正常工作时处于上电状态,空闲时自动下电以节省功耗。

驱动框架与设备树绑定

驱动基于Linux内核platform_driver模型,通过设备树节点获取时钟、稳压器和电源域资源。设备树中定义GPU节点及其关联的时钟、供电和电源域信息,驱动在probe阶段解析这些资源并完成初始化。

设备树 GPU 节点 compatible = "qcom,gpu-pwr" clocks = &gpu_cc, &gpu_cc clock-names = "core", "iface" regulators = &gpu_supply power-domains = <&gpu_pd> GPU 电源驱动 probe() runtime_suspend() runtime_resume() clk_prepare_enable() 内核框架 clock framework regulator core genpd 硬件层 GPU 核心 时钟控制器 PMIC 稳压器 电源管理操作

驱动代码实现

以下代码实现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

验证步骤与判定标准:

  1. 将编译好的内核烧录至SA8155目标板,启动后执行dmesg | grep gpu_pwr,应看到GPU power driver probed successfully和GPU power on日志,说明probe阶段电源管理初始化成功。
  2. 执行cat /sys/devices/platform/gpu-pwr@3d00000/power/runtime_status,输出应为active,表示GPU处于上电状态。
  3. 执行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路径正常。
  4. 触发GPU使用(如运行图形负载),runtime_status应自动回到active,dmesg出现GPU power on,证明resume路径工作正常。

以上步骤全部通过,即可判定GPU电源管理驱动实现正确,时钟、电压和电源域控制均按预期工作。