Re: [PATCH 2/3] pmdomain: spacemit: Add power domain driver
From: Icenowy Zheng
Date: Fri Sep 18 2026 - 05:24:23 EST
在 2026-09-18五的 02:40 +0000,Yixun Lan写道:
> SpacemiT's PMU (Power Management Unit) consist of several power
> domains
> which can be managed independently, depending on different
> application
> scenario, each domain can be powered on/off for saving power.
>
> The driver is implemented based on Linux Generic PM Domain
> framework.
> From a hardware perspective, either of two distinct power-on
> sequences
> are supported, in software mode (SW mode), the driver is responsible
> for
> controlling the states of bits such as sleep1, sleep2, isolation and
> pwr_state, while in hardware mode (HW mode), the PMU hardware will
> complete
> the sequence automatically without requiring software intervention.
I think there also exists solutions for K3 that uses RPMI power
domains, and let their ESOS firmware to access the hardware.
What's the relationship of this implementation with the ESOS
implementation? Should the ESOS part be disabled to use this
implementation?
Thanks,
Icenowy
>
> Signed-off-by: Yixun Lan <dlan@xxxxxxxxxx>
> ---
> drivers/pmdomain/Kconfig | 1 +
> drivers/pmdomain/Makefile | 1 +
> drivers/pmdomain/spacemit/Kconfig | 15 ++
> drivers/pmdomain/spacemit/Makefile | 1 +
> drivers/pmdomain/spacemit/pm_domains.c | 448
> +++++++++++++++++++++++++++++++++
> 5 files changed, 466 insertions(+)
>
> diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig
> index 23076ae90e66..0610acd030f6 100644
> --- a/drivers/pmdomain/Kconfig
> +++ b/drivers/pmdomain/Kconfig
> @@ -13,6 +13,7 @@ source "drivers/pmdomain/qcom/Kconfig"
> source "drivers/pmdomain/renesas/Kconfig"
> source "drivers/pmdomain/rockchip/Kconfig"
> source "drivers/pmdomain/samsung/Kconfig"
> +source "drivers/pmdomain/spacemit/Kconfig"
> source "drivers/pmdomain/st/Kconfig"
> source "drivers/pmdomain/starfive/Kconfig"
> source "drivers/pmdomain/sunxi/Kconfig"
> diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile
> index ebc802f13eb9..ba25b444e263 100644
> --- a/drivers/pmdomain/Makefile
> +++ b/drivers/pmdomain/Makefile
> @@ -11,6 +11,7 @@ obj-y += qcom/
> obj-y += renesas/
> obj-y += rockchip/
> obj-y += samsung/
> +obj-y += spacemit/
> obj-y += st/
> obj-y += starfive/
> obj-y += sunxi/
> diff --git a/drivers/pmdomain/spacemit/Kconfig
> b/drivers/pmdomain/spacemit/Kconfig
> new file mode 100644
> index 000000000000..4b927beedc7c
> --- /dev/null
> +++ b/drivers/pmdomain/spacemit/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +menu "SpacemiT PM Domains"
> +
> +config SPACEMIT_PM_DOMAINS
> + bool "PM Domain driver for SpacemiT SoC"
> + depends on PM
> + depends on OF
> + depends on MFD_SYSCON
> + select PM_GENERIC_DOMAINS
> + help
> + PM Domain driver for SpacemiT SoC. Say Y if you want to
> support
> + SpacemiT SoCs.
> +
> +endmenu
> diff --git a/drivers/pmdomain/spacemit/Makefile
> b/drivers/pmdomain/spacemit/Makefile
> new file mode 100644
> index 000000000000..5e8a740ba494
> --- /dev/null
> +++ b/drivers/pmdomain/spacemit/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_SPACEMIT_PM_DOMAINS) += pm_domains.o
> diff --git a/drivers/pmdomain/spacemit/pm_domains.c
> b/drivers/pmdomain/spacemit/pm_domains.c
> new file mode 100644
> index 000000000000..d563e4e4e232
> --- /dev/null
> +++ b/drivers/pmdomain/spacemit/pm_domains.c
> @@ -0,0 +1,448 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit Generic power domain support.
> + *
> + * Copyright (c) 2026 SpacemiT Technology Co. Ltd
> + */
> +
> +#include <linux/io.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +#include <linux/mfd/syscon.h>
> +#include <dt-bindings/power/spacemit,k1-power.h>
> +#include <dt-bindings/power/spacemit,k3-power.h>
> +
> +#define APMU_POWER_STATUS_REG 0xf0
> +#define APMU_POWER_TIMEOUT_US 10000
> +
> +struct spacemit_pm_domain_param {
> + int reg_pwr_ctrl;
> + int bit_hw_mode;
> + int bit_sleep2;
> + int bit_sleep1;
> + int bit_isolation;
> + int bit_auto_pwr_on;
> + int bit_hw_pwr_stat;
> + int bit_pwr_stat;
> + int use_hw;
> + const char *name;
> +};
> +
> +struct spacemit_pm_domain {
> + struct generic_pm_domain genpd;
> + int pm_index;
> + const struct spacemit_pm_domain_param *param;
> +};
> +
> +struct spacemit_pmu {
> + struct device *dev;
> + struct genpd_onecell_data genpd_data;
> + struct regmap *regmap;
> + struct spacemit_pm_domain **domains;
> + int num_domains;
> +};
> +
> +struct spacemit_pm_of_data {
> + int num_domains;
> + const struct spacemit_pm_domain_param *param;
> +};
> +
> +static struct spacemit_pmu *gpmu;
> +
> +static int spacemit_pd_power_off(struct generic_pm_domain *domain)
> +{
> + struct spacemit_pm_domain *spd = container_of(domain, struct
> spacemit_pm_domain, genpd);
> + const struct spacemit_pm_domain_param *p = spd->param;
> + unsigned int val;
> + int ret;
> +
> + if (!spd->param->use_hw) {
> + regmap_clear_bits(gpmu->regmap, p->reg_pwr_ctrl,
> BIT(p->bit_isolation));
> + fsleep(15);
> +
> + val = BIT(p->bit_sleep1) | BIT(p->bit_sleep2);
> + regmap_clear_bits(gpmu->regmap, p->reg_pwr_ctrl,
> val);
> + fsleep(15);
> +
> + ret = regmap_read_poll_timeout(gpmu->regmap,
> +
> APMU_POWER_STATUS_REG,
> + val,
> + !(val & BIT(p-
> >bit_pwr_stat)),
> + 5,
> +
> APMU_POWER_TIMEOUT_US);
> + } else {
> + val = BIT(p->bit_auto_pwr_on) | BIT(p->bit_hw_mode);
> + regmap_clear_bits(gpmu->regmap, p->reg_pwr_ctrl,
> val);
> + fsleep(15);
> +
> + ret = regmap_read_poll_timeout(gpmu->regmap,
> +
> APMU_POWER_STATUS_REG,
> + val,
> + !(val & BIT(p-
> >bit_hw_pwr_stat)),
> + 5,
> +
> APMU_POWER_TIMEOUT_US);
> + }
> +
> + if (ret) {
> + dev_err(&domain->dev, "Fail to power-off domain:
> %d\n",
> + spd->pm_index);
> + return -EBUSY;
> + }
> +
> + return 0;
> +}
> +
> +static int spacemit_pd_power_on(struct generic_pm_domain *domain)
> +{
> + struct spacemit_pm_domain *spd = container_of(domain, struct
> spacemit_pm_domain, genpd);
> + const struct spacemit_pm_domain_param *p = spd->param;
> + unsigned int val;
> + int ret = 0;
> +
> + regmap_read(gpmu->regmap, APMU_POWER_STATUS_REG, &val);
> +
> + if (!p->use_hw && (val & BIT(p->bit_pwr_stat))) {
> + val = BIT(p->bit_isolation);
> + regmap_clear_bits(gpmu->regmap, p->reg_pwr_ctrl,
> val);
> + fsleep(15);
> +
> + val = BIT(p->bit_sleep1) | BIT(p->bit_sleep2);
> + regmap_clear_bits(gpmu->regmap, p->reg_pwr_ctrl,
> val);
> + fsleep(15);
> +
> + ret = regmap_read_poll_timeout(gpmu->regmap,
> +
> APMU_POWER_STATUS_REG,
> + val,
> + !(val & BIT(p-
> >bit_pwr_stat)),
> + 5,
> +
> APMU_POWER_TIMEOUT_US);
> + }
> +
> + if (p->use_hw && (val & BIT(p->bit_hw_pwr_stat))) {
> + val = BIT(p->bit_auto_pwr_on) | BIT(p->bit_hw_mode);
> + regmap_clear_bits(gpmu->regmap, p->reg_pwr_ctrl,
> val);
> + fsleep(30);
> +
> + ret = regmap_read_poll_timeout(gpmu->regmap,
> +
> APMU_POWER_STATUS_REG,
> + val,
> + !(val & BIT(p-
> >bit_hw_pwr_stat)),
> + 5,
> +
> APMU_POWER_TIMEOUT_US);
> + }
> +
> + if (ret < 0) {
> + dev_err(&domain->dev, "power-off domain: %d,
> error\n", spd->pm_index);
> + return -EBUSY;
> + }
> +
> + if (!p->use_hw) {
> + regmap_set_bits(gpmu->regmap, p->reg_pwr_ctrl,
> BIT(p->bit_sleep1));
> + fsleep(20);
> +
> + val = BIT(p->bit_sleep2) | BIT(p->bit_sleep1);
> + regmap_set_bits(gpmu->regmap, p->reg_pwr_ctrl, val);
> + fsleep(20);
> +
> + regmap_set_bits(gpmu->regmap, p->reg_pwr_ctrl,
> BIT(p->bit_isolation));
> + fsleep(15);
> +
> + ret = regmap_read_poll_timeout(gpmu->regmap,
> +
> APMU_POWER_STATUS_REG,
> + val,
> + (val & BIT(p-
> >bit_pwr_stat)),
> + 5,
> +
> APMU_POWER_TIMEOUT_US);
> + } else {
> + val = BIT(p->bit_auto_pwr_on) | BIT(p->bit_hw_mode);
> + regmap_set_bits(gpmu->regmap, p->reg_pwr_ctrl, val);
> + fsleep(300);
> +
> + ret = regmap_read_poll_timeout(gpmu->regmap,
> +
> APMU_POWER_STATUS_REG,
> + val,
> + (val & BIT(p-
> >bit_hw_pwr_stat)),
> + 5,
> +
> APMU_POWER_TIMEOUT_US);
> + }
> +
> + if (ret < 0) {
> + dev_err(&domain->dev, "power-on domain: %d,
> error\n", spd->pm_index);
> + return -EBUSY;
> + }
> +
> + return 0;
> +}
> +
> +static bool spacemit_pm_get_state(struct spacemit_pmu *pmu,
> + struct spacemit_pm_domain *pd)
> +{
> + const struct spacemit_pm_domain_param *p = pd->param;
> + u32 reg, bit;
> +
> + regmap_read(pmu->regmap, APMU_POWER_STATUS_REG, ®);
> +
> + bit = p->use_hw ? BIT(pd->param->bit_hw_pwr_stat) :
> + BIT(pd->param->bit_pwr_stat);
> +
> + return !!(reg & bit);
> +}
> +
> +static int spacemit_pm_add_one_domain(struct spacemit_pmu *pmu, int
> id,
> + const struct
> spacemit_pm_domain_param *param)
> +{
> + struct spacemit_pm_domain *pd;
> +
> + pd = devm_kzalloc(pmu->dev, sizeof(*pd), GFP_KERNEL);
> + if (!pd)
> + return -ENOMEM;
> +
> + pd->pm_index = id;
> + pd->param = param;
> + pd->genpd.name = param->name;
> + pd->genpd.power_off = spacemit_pd_power_off;
> + pd->genpd.power_on = spacemit_pd_power_on;
> +
> + pm_genpd_init(&pd->genpd, NULL, !spacemit_pm_get_state(pmu,
> pd));
> +
> + pmu->domains[id] = pd;
> +
> + return 0;
> +}
> +
> +static void spacemit_pm_domain_cleanup(struct spacemit_pmu *pmu)
> +{
> + int i;
> +
> + for (i = 0; i < pmu->num_domains; i++) {
> + if (pmu->domains[i])
> + pm_genpd_remove(&pmu->domains[i]->genpd);
> + }
> +}
> +
> +static int spacemit_pm_domain_probe(struct platform_device *pdev)
> +{
> + const struct spacemit_pm_of_data *data;
> + struct device *dev = &pdev->dev;
> + struct spacemit_pmu *pmu;
> + int err, i;
> +
> + data = device_get_match_data(dev);
> +
> + pmu = devm_kzalloc(dev, sizeof(*pmu), GFP_KERNEL);
> + if (!pmu)
> + return -ENOMEM;
> +
> + pmu->dev = dev;
> + pmu->num_domains = data->num_domains;
> +
> + pmu->regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
> "spacemit,apmu");
> + if (IS_ERR(pmu->regmap))
> + return dev_err_probe(dev, PTR_ERR(pmu->regmap),
> "failed to get apmu regmap\n");
> +
> + pmu->domains = devm_kcalloc(dev, data->num_domains,
> + sizeof(*pmu->domains),
> GFP_KERNEL);
> + if (!pmu->domains)
> + return -ENOMEM;
> +
> + for (i = 0; i < data->num_domains; i++) {
> + err = spacemit_pm_add_one_domain(pmu, i, &data-
> >param[i]);
> + if (err) {
> + dev_err(dev, "failed to add domain %d:
> %d\n", i, err);
> + goto err_out;
> + }
> + }
> +
> + pmu->genpd_data.domains = (struct generic_pm_domain
> **)pmu->domains;
> + pmu->genpd_data.num_domains = data->num_domains;
> +
> + err = of_genpd_add_provider_onecell(dev->of_node, &pmu-
> >genpd_data);
> + if (err) {
> + dev_err(dev, "failed to add provider: %d\n", err);
> + goto err_out;
> + }
> +
> + gpmu = pmu;
> +
> + return 0;
> +
> +err_out:
> + spacemit_pm_domain_cleanup(pmu);
> + return err;
> +}
> +
> +static const struct spacemit_pm_domain_param k1_domain_params[] = {
> + [K1_PMDOMAIN_VPU] = {
> + .reg_pwr_ctrl = 0xa8,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_pwr_stat = 1,
> + .bit_hw_pwr_stat = 9,
> + .name = "vpu",
> + },
> + [K1_PMDOMAIN_GPU] = {
> + .reg_pwr_ctrl = 0xd0,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_pwr_stat = 0,
> + .name = "gpu",
> + },
> + [K1_PMDOMAIN_LCD] = {
> + .reg_pwr_ctrl = 0x380,
> + .bit_hw_mode = 4,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_auto_pwr_on = 0,
> + .bit_pwr_stat = 4,
> + .bit_hw_pwr_stat = 12,
> + .use_hw = 1,
> + .name = "lcd",
> + },
> + [K1_PMDOMAIN_ISP] = {
> + .reg_pwr_ctrl = 0x37c,
> + .bit_hw_mode = 4,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_auto_pwr_on = 0,
> + .bit_pwr_stat = 2,
> + .bit_hw_pwr_stat = 10,
> + .name = "isp",
> + },
> + [K1_PMDOMAIN_AUDIO] = {
> + .reg_pwr_ctrl = 0x378,
> + .bit_hw_mode = 4,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_auto_pwr_on = 0,
> + .bit_pwr_stat = 3,
> + .bit_hw_pwr_stat = 11,
> + .use_hw = 1,
> + .name = "audio",
> + },
> + [K1_PMDOMAIN_GNSS] = {
> + .reg_pwr_ctrl = 0x13c,
> + .bit_hw_mode = 4,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_auto_pwr_on = 0,
> + .bit_pwr_stat = 6,
> + .bit_hw_pwr_stat = 14,
> + .name = "gnss",
> + },
> + [K1_PMDOMAIN_HDMI] = {
> + .reg_pwr_ctrl = 0x3f4,
> + .bit_hw_mode = 4,
> + .bit_sleep2 = 3,
> + .bit_sleep1 = 2,
> + .bit_isolation = 1,
> + .bit_auto_pwr_on = 0,
> + .bit_pwr_stat = 7,
> + .bit_hw_pwr_stat = 15,
> + .use_hw = 1,
> + .name = "hdmi",
> + },
> +};
> +
> +static const struct spacemit_pm_domain_param k3_domain_params[] = {
> + [K3_PMDOMAIN_VPU] = {
> + .bit_auto_pwr_on = 0,
> + .bit_isolation = 1,
> + .bit_sleep1 = 2,
> + .bit_sleep2 = 3,
> + .bit_hw_mode = 4,
> + .bit_pwr_stat = 2,
> + .bit_hw_pwr_stat = 9,
> + .use_hw = 1,
> + .reg_pwr_ctrl = 0xa8,
> + .name = "vpu",
> + },
> + [K3_PMDOMAIN_GPU] = {
> + .bit_auto_pwr_on = 0,
> + .bit_isolation = 1,
> + .bit_sleep1 = 2,
> + .bit_sleep2 = 3,
> + .bit_hw_mode = 4,
> + .bit_pwr_stat = 0,
> + .bit_hw_pwr_stat = 8,
> + .use_hw = 1,
> + .reg_pwr_ctrl = 0xd0,
> + .name = "gpu",
> + },
> + [K3_PMDOMAIN_AUDIO] = {
> + .bit_auto_pwr_on = 0,
> + .bit_isolation = 1,
> + .bit_sleep1 = 2,
> + .bit_sleep2 = 3,
> + .bit_hw_mode = 4,
> + .bit_pwr_stat = 3,
> + .reg_pwr_ctrl = 0x378,
> + .name = "audio",
> + },
> + [K3_PMDOMAIN_LCD0] = {
> + .bit_auto_pwr_on = 0,
> + .bit_isolation = 1,
> + .bit_sleep1 = 2,
> + .bit_sleep2 = 3,
> + .bit_hw_mode = 4,
> + .bit_pwr_stat = 4,
> + .bit_hw_pwr_stat = 12,
> + .use_hw = 1,
> + .reg_pwr_ctrl = 0x380,
> + .name = "lcd0",
> + },
> + [K3_PMDOMAIN_LCD1] = {
> + .bit_auto_pwr_on = 0,
> + .bit_isolation = 1,
> + .bit_sleep1 = 2,
> + .bit_sleep2 = 3,
> + .bit_hw_mode = 4,
> + .bit_pwr_stat = 5,
> + .bit_hw_pwr_stat = 15,
> + .use_hw = 1,
> + .reg_pwr_ctrl = 0x3f4,
> + .name = "lcd1",
> + },
> +};
> +
> +static const struct spacemit_pm_of_data k1_of_data = {
> + .num_domains = ARRAY_SIZE(k1_domain_params),
> + .param = k1_domain_params,
> +};
> +
> +static const struct spacemit_pm_of_data k3_of_data = {
> + .num_domains = ARRAY_SIZE(k3_domain_params),
> + .param = k3_domain_params,
> +};
> +
> +static const struct of_device_id spacemit_pm_domain_dt_match[] = {
> + {
> + .compatible = "spacemit,k1-power-controller",
> + .data = &k1_of_data,
> + },
> + {
> + .compatible = "spacemit,k3-power-controller",
> + .data = &k3_of_data,
> + },
> + { /* sentinel */ }
> +};
> +
> +static struct platform_driver spacemit_pm_domain_driver = {
> + .probe = spacemit_pm_domain_probe,
> + .driver = {
> + .name = "spacemit-pm-domain",
> + .of_match_table = spacemit_pm_domain_dt_match,
> + },
> +};
> +
> +builtin_platform_driver(spacemit_pm_domain_driver);
> +MODULE_DESCRIPTION("SpacemiT Generic Power Domains driver");
> +MODULE_LICENSE("GPL");