* [PATCH v4 0/2] PM: spacemit: Add power domain support
@ 2026-11-12 8:00 ` Yixun Lan
0 siblings, 0 replies; 7+ messages in thread
From: Yixun Lan @ 2026-11-12 8:00 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti
Cc: Icenowy Zheng, Iker Pedrosa, devicetree, linux-riscv, spacemit,
linux-pm, linux-kernel, Yixun Lan, Conor Dooley
In this series, we try to add a Power Domain driver support for SpacemiT
K1/K3 SoCs, the driver implement the power-switch functionaly which support
power on/off, and it's based on Linux Generic PM Domain framework and
similiar to vendor K1's implementation, but unlike the K3's vendor
version which is more complicated.
The DT documentation for Power Domain already exist in below file,
and APMU already has #power-domain-cells property.
Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml
The previous DTS patch is obsolete, so I dropped it. For devices required
PM feature, it need to add a "power-domains" property, and should send in
a separate patch.
In order to test this series, a prerequisite CCU patches are needed,
which can be found here [1].
Link: https://lore.kernel.org/spacemit/20261008-04-k3-clk-pm-aux-v1-0-8372d7307694@kernel.org/ [1]
Signed-off-by: Yixun Lan <dlan@kernel.org>
---
Changes in v4:
- adjust Kconfig help message
- drop unused include header
- propagate regmap_read_x() error code
- simplify spacemit_pm_is_off_state()
- Link to v3: https://patch.msgid.link/20261108-04-k3-pm-support-v3-0-b9dc0857039b@kernel.org
Changes in v3:
- collect tags
- rename spacemit_pm_is_off_state()
- implement a remove() function
- use proper type in struct spacemit_pm_domain_param
- refactor struct spacemit_pmu
- Link to v2: https://patch.msgid.link/20261008-04-k3-pm-support-v2-0-6f778a53dd2d@kernel.org
Changes in v2:
- convert to auxilary driver
- no need dt-binding doc, drop it
- drop dts patch
- support to build as module
- set always on for K3 audio pm
- no global variable for struct spacemit_pmu
- no force to do power-off in spacemit_pd_power_on()
- check return value of pm_genpd_init()
- adjust license header format
- Link to v1: https://patch.msgid.link/20260918-04-k3-pm-support-v1-0-0acd2b36b96f@kernel.org
---
Yixun Lan (2):
dt-bindings: power: spacemit: Add power domain controller IDs
pmdomain: spacemit: Add power domain driver
drivers/pmdomain/Kconfig | 1 +
drivers/pmdomain/Makefile | 1 +
drivers/pmdomain/spacemit/Kconfig | 21 ++
drivers/pmdomain/spacemit/Makefile | 1 +
drivers/pmdomain/spacemit/spacemit-pm-domains.c | 424 ++++++++++++++++++++++++
include/dt-bindings/power/spacemit,k1-power.h | 14 +
include/dt-bindings/power/spacemit,k3-power.h | 12 +
7 files changed, 474 insertions(+)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20260909-04-k3-pm-support-0bef40e87fd7
Best regards,
--
Yixun Lan <dlan@kernel.org>
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v4 0/2] PM: spacemit: Add power domain support @ 2026-11-12 8:00 ` Yixun Lan 0 siblings, 0 replies; 7+ messages in thread From: Yixun Lan @ 2026-11-12 8:00 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti Cc: devicetree, linux-pm, linux-kernel, Conor Dooley, Yixun Lan, linux-riscv, spacemit, Iker Pedrosa In this series, we try to add a Power Domain driver support for SpacemiT K1/K3 SoCs, the driver implement the power-switch functionaly which support power on/off, and it's based on Linux Generic PM Domain framework and similiar to vendor K1's implementation, but unlike the K3's vendor version which is more complicated. The DT documentation for Power Domain already exist in below file, and APMU already has #power-domain-cells property. Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml The previous DTS patch is obsolete, so I dropped it. For devices required PM feature, it need to add a "power-domains" property, and should send in a separate patch. In order to test this series, a prerequisite CCU patches are needed, which can be found here [1]. Link: https://lore.kernel.org/spacemit/20261008-04-k3-clk-pm-aux-v1-0-8372d7307694@kernel.org/ [1] Signed-off-by: Yixun Lan <dlan@kernel.org> --- Changes in v4: - adjust Kconfig help message - drop unused include header - propagate regmap_read_x() error code - simplify spacemit_pm_is_off_state() - Link to v3: https://patch.msgid.link/20261108-04-k3-pm-support-v3-0-b9dc0857039b@kernel.org Changes in v3: - collect tags - rename spacemit_pm_is_off_state() - implement a remove() function - use proper type in struct spacemit_pm_domain_param - refactor struct spacemit_pmu - Link to v2: https://patch.msgid.link/20261008-04-k3-pm-support-v2-0-6f778a53dd2d@kernel.org Changes in v2: - convert to auxilary driver - no need dt-binding doc, drop it - drop dts patch - support to build as module - set always on for K3 audio pm - no global variable for struct spacemit_pmu - no force to do power-off in spacemit_pd_power_on() - check return value of pm_genpd_init() - adjust license header format - Link to v1: https://patch.msgid.link/20260918-04-k3-pm-support-v1-0-0acd2b36b96f@kernel.org --- Yixun Lan (2): dt-bindings: power: spacemit: Add power domain controller IDs pmdomain: spacemit: Add power domain driver drivers/pmdomain/Kconfig | 1 + drivers/pmdomain/Makefile | 1 + drivers/pmdomain/spacemit/Kconfig | 21 ++ drivers/pmdomain/spacemit/Makefile | 1 + drivers/pmdomain/spacemit/spacemit-pm-domains.c | 424 ++++++++++++++++++++++++ include/dt-bindings/power/spacemit,k1-power.h | 14 + include/dt-bindings/power/spacemit,k3-power.h | 12 + 7 files changed, 474 insertions(+) --- base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4 change-id: 20260909-04-k3-pm-support-0bef40e87fd7 Best regards, -- Yixun Lan <dlan@kernel.org> _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v4 1/2] dt-bindings: power: spacemit: Add power domain controller IDs 2026-11-12 8:00 ` Yixun Lan @ 2026-11-12 8:00 ` Yixun Lan -1 siblings, 0 replies; 7+ messages in thread From: Yixun Lan @ 2026-11-12 8:00 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti Cc: Icenowy Zheng, Iker Pedrosa, devicetree, linux-riscv, spacemit, linux-pm, linux-kernel, Yixun Lan, Conor Dooley Add initial power domain driver support for SpacemiT K1/K3 SoC, The Power Domain Controller is SoC internal module which is part of APMU (Advanced Power Management Unit), it is responsible for managing power across multi power domains, which includes power on/off, isolation, power-on sequence. The DT documentation for the power domains already exists in file of spacemit,k1-syscon.yaml. Acked-by: Conor Dooley <conor.dooley@microchip.com> Tested-by: Iker Pedrosa <ikerpedrosam@gmail.com> Reviewed-by: Iker Pedrosa <ikerpedrosam@gmail.com> Signed-off-by: Yixun Lan <dlan@kernel.org> --- include/dt-bindings/power/spacemit,k1-power.h | 14 ++++++++++++++ include/dt-bindings/power/spacemit,k3-power.h | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/include/dt-bindings/power/spacemit,k1-power.h b/include/dt-bindings/power/spacemit,k1-power.h new file mode 100644 index 000000000000..41ef540b5c44 --- /dev/null +++ b/include/dt-bindings/power/spacemit,k1-power.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ + +#ifndef _DT_BINDINGS_SPACEMIT_K1_POWER_H_ +#define _DT_BINDINGS_SPACEMIT_K1_POWER_H_ + +#define K1_PMDOMAIN_VPU 0 +#define K1_PMDOMAIN_GPU 1 +#define K1_PMDOMAIN_LCD 2 +#define K1_PMDOMAIN_ISP 3 +#define K1_PMDOMAIN_AUDIO 4 +#define K1_PMDOMAIN_GNSS 5 +#define K1_PMDOMAIN_HDMI 6 + +#endif diff --git a/include/dt-bindings/power/spacemit,k3-power.h b/include/dt-bindings/power/spacemit,k3-power.h new file mode 100644 index 000000000000..519fbf946312 --- /dev/null +++ b/include/dt-bindings/power/spacemit,k3-power.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ + +#ifndef _DT_BINDINGS_SPACEMIT_K3_POWER_H_ +#define _DT_BINDINGS_SPACEMIT_K3_POWER_H_ + +#define K3_PMDOMAIN_VPU 0 +#define K3_PMDOMAIN_GPU 1 +#define K3_PMDOMAIN_AUDIO 2 +#define K3_PMDOMAIN_LCD0 3 +#define K3_PMDOMAIN_LCD1 4 + +#endif -- 2.56.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 1/2] dt-bindings: power: spacemit: Add power domain controller IDs @ 2026-11-12 8:00 ` Yixun Lan 0 siblings, 0 replies; 7+ messages in thread From: Yixun Lan @ 2026-11-12 8:00 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti Cc: devicetree, linux-pm, linux-kernel, Conor Dooley, Yixun Lan, linux-riscv, spacemit, Iker Pedrosa Add initial power domain driver support for SpacemiT K1/K3 SoC, The Power Domain Controller is SoC internal module which is part of APMU (Advanced Power Management Unit), it is responsible for managing power across multi power domains, which includes power on/off, isolation, power-on sequence. The DT documentation for the power domains already exists in file of spacemit,k1-syscon.yaml. Acked-by: Conor Dooley <conor.dooley@microchip.com> Tested-by: Iker Pedrosa <ikerpedrosam@gmail.com> Reviewed-by: Iker Pedrosa <ikerpedrosam@gmail.com> Signed-off-by: Yixun Lan <dlan@kernel.org> --- include/dt-bindings/power/spacemit,k1-power.h | 14 ++++++++++++++ include/dt-bindings/power/spacemit,k3-power.h | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/include/dt-bindings/power/spacemit,k1-power.h b/include/dt-bindings/power/spacemit,k1-power.h new file mode 100644 index 000000000000..41ef540b5c44 --- /dev/null +++ b/include/dt-bindings/power/spacemit,k1-power.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ + +#ifndef _DT_BINDINGS_SPACEMIT_K1_POWER_H_ +#define _DT_BINDINGS_SPACEMIT_K1_POWER_H_ + +#define K1_PMDOMAIN_VPU 0 +#define K1_PMDOMAIN_GPU 1 +#define K1_PMDOMAIN_LCD 2 +#define K1_PMDOMAIN_ISP 3 +#define K1_PMDOMAIN_AUDIO 4 +#define K1_PMDOMAIN_GNSS 5 +#define K1_PMDOMAIN_HDMI 6 + +#endif diff --git a/include/dt-bindings/power/spacemit,k3-power.h b/include/dt-bindings/power/spacemit,k3-power.h new file mode 100644 index 000000000000..519fbf946312 --- /dev/null +++ b/include/dt-bindings/power/spacemit,k3-power.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */ + +#ifndef _DT_BINDINGS_SPACEMIT_K3_POWER_H_ +#define _DT_BINDINGS_SPACEMIT_K3_POWER_H_ + +#define K3_PMDOMAIN_VPU 0 +#define K3_PMDOMAIN_GPU 1 +#define K3_PMDOMAIN_AUDIO 2 +#define K3_PMDOMAIN_LCD0 3 +#define K3_PMDOMAIN_LCD1 4 + +#endif -- 2.56.0 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 2/2] pmdomain: spacemit: Add power domain driver 2026-11-12 8:00 ` Yixun Lan @ 2026-11-12 8:00 ` Yixun Lan -1 siblings, 0 replies; 7+ messages in thread From: Yixun Lan @ 2026-11-12 8:00 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti Cc: Icenowy Zheng, Iker Pedrosa, devicetree, linux-riscv, spacemit, linux-pm, linux-kernel, 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. Tested-by: Iker Pedrosa <ikerpedrosam@gmail.com> Reviewed-by: Iker Pedrosa <ikerpedrosam@gmail.com> Signed-off-by: Yixun Lan <dlan@kernel.org> --- drivers/pmdomain/Kconfig | 1 + drivers/pmdomain/Makefile | 1 + drivers/pmdomain/spacemit/Kconfig | 21 ++ drivers/pmdomain/spacemit/Makefile | 1 + drivers/pmdomain/spacemit/spacemit-pm-domains.c | 424 ++++++++++++++++++++++++ 5 files changed, 448 insertions(+) diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig index 5341dff669be..20aea5fe94b2 100644 --- a/drivers/pmdomain/Kconfig +++ b/drivers/pmdomain/Kconfig @@ -14,6 +14,7 @@ source "drivers/pmdomain/renesas/Kconfig" source "drivers/pmdomain/riscv/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 d7aee13ae571..25238576f8a3 100644 --- a/drivers/pmdomain/Makefile +++ b/drivers/pmdomain/Makefile @@ -12,6 +12,7 @@ obj-y += renesas/ obj-y += riscv/ 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..8e2808e92be3 --- /dev/null +++ b/drivers/pmdomain/spacemit/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "SpacemiT PM Domains" + +config SPACEMIT_PM_DOMAINS + tristate "PM Domain driver for SpacemiT SoC" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on PM && OF + select PM_GENERIC_DOMAINS + select PM_GENERIC_DOMAINS_OF + select AUXILIARY_BUS + help + This driver enables power domain management for the SpacemiT + SoC. On the SoC there are number of power domains available, + which can be managed independently. For example VPU, GPU, LCD, + and Audio reside in their own power domains which can be + turned on/off. + + Say Y or M to enable device power management through PM domains. + +endmenu diff --git a/drivers/pmdomain/spacemit/Makefile b/drivers/pmdomain/spacemit/Makefile new file mode 100644 index 000000000000..d0bd77a520c0 --- /dev/null +++ b/drivers/pmdomain/spacemit/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SPACEMIT_PM_DOMAINS) += spacemit-pm-domains.o diff --git a/drivers/pmdomain/spacemit/spacemit-pm-domains.c b/drivers/pmdomain/spacemit/spacemit-pm-domains.c new file mode 100644 index 000000000000..dd0613c1912a --- /dev/null +++ b/drivers/pmdomain/spacemit/spacemit-pm-domains.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Spacemit Generic power domain support. + * + * Copyright (c) 2026 SpacemiT Technology Co. Ltd + */ + +#include <linux/pm_domain.h> +#include <linux/regmap.h> +#include <linux/auxiliary_bus.h> +#include <dt-bindings/power/spacemit,k1-power.h> +#include <dt-bindings/power/spacemit,k3-power.h> +#include <soc/spacemit/ccu.h> + +#define APMU_POWER_STATUS_REG 0xf0 +#define APMU_POWER_TIMEOUT_US 10000 + +struct spacemit_pm_domain_param { + unsigned int reg_pwr_ctrl; + u8 bit_hw_mode; + u8 bit_sleep2; + u8 bit_sleep1; + u8 bit_isolation; + u8 bit_auto_pwr_on; + u8 bit_hw_pwr_stat; + u8 bit_pwr_stat; + bool use_hw; + unsigned int flags; + const char *name; +}; + +struct spacemit_pm_domain { + struct generic_pm_domain genpd; + struct spacemit_pmu *pmu; + int pm_index; + const struct spacemit_pm_domain_param *param; +}; + +struct spacemit_pmu { + struct device *dev; + struct regmap *regmap; + struct genpd_onecell_data genpd_data; + struct generic_pm_domain *domains[]; +}; + +struct spacemit_pm_aux_data { + int num_domains; + const struct spacemit_pm_domain_param *param; +}; + +#define to_spacemit_pd(gpd) container_of(gpd, struct spacemit_pm_domain, genpd) + +static int spacemit_pd_power_off(struct generic_pm_domain *domain) +{ + struct spacemit_pm_domain *pd = to_spacemit_pd(domain); + struct spacemit_pmu *pmu = pd->pmu; + const struct spacemit_pm_domain_param *p = pd->param; + unsigned int val; + int ret; + + if (!pd->param->use_hw) { + regmap_clear_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_isolation)); + fsleep(15); + + val = BIT(p->bit_sleep1) | BIT(p->bit_sleep2); + regmap_clear_bits(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(15); + + ret = regmap_read_poll_timeout(pmu->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(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(15); + + ret = regmap_read_poll_timeout(pmu->regmap, + APMU_POWER_STATUS_REG, + val, + !(val & BIT(p->bit_hw_pwr_stat)), + 5, + APMU_POWER_TIMEOUT_US); + } + + if (ret) + dev_err(&domain->dev, "failed to power off domain %s: %d\n", + p->name, pd->pm_index); + + return ret; +} + +static int spacemit_pd_power_on(struct generic_pm_domain *domain) +{ + struct spacemit_pm_domain *pd = to_spacemit_pd(domain); + struct spacemit_pmu *pmu = pd->pmu; + const struct spacemit_pm_domain_param *p = pd->param; + unsigned int val; + int ret = 0; + + if (!p->use_hw) { + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_sleep1)); + fsleep(20); + + val = BIT(p->bit_sleep2) | BIT(p->bit_sleep1); + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(20); + + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_isolation)); + fsleep(15); + + ret = regmap_read_poll_timeout(pmu->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(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(300); + + ret = regmap_read_poll_timeout(pmu->regmap, + APMU_POWER_STATUS_REG, + val, + (val & BIT(p->bit_hw_pwr_stat)), + 5, + APMU_POWER_TIMEOUT_US); + } + + if (ret) + dev_err(&domain->dev, "failed to power on domain %s: %d\n", + p->name, pd->pm_index); + + return ret; +} + +static bool spacemit_pm_is_off_state(struct spacemit_pmu *pmu, + struct spacemit_pm_domain *pd) +{ + const struct spacemit_pm_domain_param *p = pd->param; + u32 reg = 0, bit; + + regmap_read(pmu->regmap, APMU_POWER_STATUS_REG, ®); + + bit = p->use_hw ? BIT(p->bit_hw_pwr_stat) : BIT(p->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; + int ret; + + pd = devm_kzalloc(pmu->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + pd->pm_index = id; + pd->param = param; + pd->pmu = pmu; + pd->genpd.name = param->name; + pd->genpd.flags = param->flags; + pd->genpd.power_off = spacemit_pd_power_off; + pd->genpd.power_on = spacemit_pd_power_on; + + ret = pm_genpd_init(&pd->genpd, NULL, spacemit_pm_is_off_state(pmu, pd)); + if (ret) + return ret; + + pmu->domains[id] = &pd->genpd; + + return 0; +} + +static void spacemit_pm_domain_cleanup(struct spacemit_pmu *pmu) +{ + struct spacemit_pm_domain *pd; + int i, ret; + + for (i = 0; i < pmu->genpd_data.num_domains; i++) { + if (!pmu->domains[i]) + continue; + + pd = to_spacemit_pd(pmu->domains[i]); + ret = pm_genpd_remove(pmu->domains[i]); + if (ret) + dev_err(pmu->dev, "failed to remove domain %s: %d - state may be inconsistent\n", + pd->param->name, ret); + } +} + +static int spacemit_pm_domain_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct spacemit_ccu_adev *spdev = to_spacemit_ccu_adev(adev); + const struct spacemit_pm_domain_param *p; + const struct spacemit_pm_aux_data *data; + struct device *dev = &adev->dev; + struct spacemit_pmu *pmu; + int err, i; + + data = (const struct spacemit_pm_aux_data *)id->driver_data; + + pmu = devm_kzalloc(dev, + struct_size(pmu, domains, data->num_domains), + GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + auxiliary_set_drvdata(adev, pmu); + + pmu->dev = dev; + pmu->regmap = spdev->regmap; + pmu->genpd_data.num_domains = data->num_domains; + pmu->genpd_data.domains = pmu->domains; + + for (i = 0; i < data->num_domains; i++) { + p = &data->param[i]; + err = spacemit_pm_add_one_domain(pmu, i, p); + if (err) { + dev_err(dev, "failed to add domain %s: %d\n", p->name, err); + goto err_out; + } + } + + 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; + } + + return 0; + +err_out: + spacemit_pm_domain_cleanup(pmu); + return err; +} + +static void spacemit_pm_domain_remove(struct auxiliary_device *adev) +{ + struct spacemit_pmu *pmu = auxiliary_get_drvdata(adev); + struct device *dev = &adev->dev; + + of_genpd_del_provider(dev->of_node); + spacemit_pm_domain_cleanup(pmu); +} + +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 = true, + .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 = true, + .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 = true, + .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 = true, + .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 = true, + .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, + .flags = GENPD_FLAG_ALWAYS_ON, + .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 = true, + .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 = true, + .reg_pwr_ctrl = 0x3f4, + .name = "lcd1", + }, +}; + +static const struct spacemit_pm_aux_data k1_of_data = { + .num_domains = ARRAY_SIZE(k1_domain_params), + .param = k1_domain_params, +}; + +static const struct spacemit_pm_aux_data k3_of_data = { + .num_domains = ARRAY_SIZE(k3_domain_params), + .param = k3_domain_params, +}; + +static const struct auxiliary_device_id spacemit_pm_domain_ids[] = { + { + .name = "spacemit_ccu.k1-apmu-pmdomain", + .driver_data = (kernel_ulong_t)&k1_of_data, + }, + { + .name = "spacemit_ccu.k3-apmu-pmdomain", + .driver_data = (kernel_ulong_t)&k3_of_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(auxiliary, spacemit_pm_domain_ids); + +static struct auxiliary_driver spacemit_pm_domain_driver = { + .probe = spacemit_pm_domain_probe, + .remove = spacemit_pm_domain_remove, + .id_table = spacemit_pm_domain_ids, +}; +module_auxiliary_driver(spacemit_pm_domain_driver); + +MODULE_DESCRIPTION("SpacemiT Generic Power Domains driver"); +MODULE_LICENSE("GPL"); -- 2.56.0 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v4 2/2] pmdomain: spacemit: Add power domain driver @ 2026-11-12 8:00 ` Yixun Lan 0 siblings, 0 replies; 7+ messages in thread From: Yixun Lan @ 2026-11-12 8:00 UTC (permalink / raw) To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Ulf Hansson, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti Cc: devicetree, linux-pm, linux-kernel, Yixun Lan, linux-riscv, spacemit, Iker Pedrosa 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. Tested-by: Iker Pedrosa <ikerpedrosam@gmail.com> Reviewed-by: Iker Pedrosa <ikerpedrosam@gmail.com> Signed-off-by: Yixun Lan <dlan@kernel.org> --- drivers/pmdomain/Kconfig | 1 + drivers/pmdomain/Makefile | 1 + drivers/pmdomain/spacemit/Kconfig | 21 ++ drivers/pmdomain/spacemit/Makefile | 1 + drivers/pmdomain/spacemit/spacemit-pm-domains.c | 424 ++++++++++++++++++++++++ 5 files changed, 448 insertions(+) diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig index 5341dff669be..20aea5fe94b2 100644 --- a/drivers/pmdomain/Kconfig +++ b/drivers/pmdomain/Kconfig @@ -14,6 +14,7 @@ source "drivers/pmdomain/renesas/Kconfig" source "drivers/pmdomain/riscv/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 d7aee13ae571..25238576f8a3 100644 --- a/drivers/pmdomain/Makefile +++ b/drivers/pmdomain/Makefile @@ -12,6 +12,7 @@ obj-y += renesas/ obj-y += riscv/ 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..8e2808e92be3 --- /dev/null +++ b/drivers/pmdomain/spacemit/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "SpacemiT PM Domains" + +config SPACEMIT_PM_DOMAINS + tristate "PM Domain driver for SpacemiT SoC" + depends on ARCH_SPACEMIT || COMPILE_TEST + depends on PM && OF + select PM_GENERIC_DOMAINS + select PM_GENERIC_DOMAINS_OF + select AUXILIARY_BUS + help + This driver enables power domain management for the SpacemiT + SoC. On the SoC there are number of power domains available, + which can be managed independently. For example VPU, GPU, LCD, + and Audio reside in their own power domains which can be + turned on/off. + + Say Y or M to enable device power management through PM domains. + +endmenu diff --git a/drivers/pmdomain/spacemit/Makefile b/drivers/pmdomain/spacemit/Makefile new file mode 100644 index 000000000000..d0bd77a520c0 --- /dev/null +++ b/drivers/pmdomain/spacemit/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SPACEMIT_PM_DOMAINS) += spacemit-pm-domains.o diff --git a/drivers/pmdomain/spacemit/spacemit-pm-domains.c b/drivers/pmdomain/spacemit/spacemit-pm-domains.c new file mode 100644 index 000000000000..dd0613c1912a --- /dev/null +++ b/drivers/pmdomain/spacemit/spacemit-pm-domains.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Spacemit Generic power domain support. + * + * Copyright (c) 2026 SpacemiT Technology Co. Ltd + */ + +#include <linux/pm_domain.h> +#include <linux/regmap.h> +#include <linux/auxiliary_bus.h> +#include <dt-bindings/power/spacemit,k1-power.h> +#include <dt-bindings/power/spacemit,k3-power.h> +#include <soc/spacemit/ccu.h> + +#define APMU_POWER_STATUS_REG 0xf0 +#define APMU_POWER_TIMEOUT_US 10000 + +struct spacemit_pm_domain_param { + unsigned int reg_pwr_ctrl; + u8 bit_hw_mode; + u8 bit_sleep2; + u8 bit_sleep1; + u8 bit_isolation; + u8 bit_auto_pwr_on; + u8 bit_hw_pwr_stat; + u8 bit_pwr_stat; + bool use_hw; + unsigned int flags; + const char *name; +}; + +struct spacemit_pm_domain { + struct generic_pm_domain genpd; + struct spacemit_pmu *pmu; + int pm_index; + const struct spacemit_pm_domain_param *param; +}; + +struct spacemit_pmu { + struct device *dev; + struct regmap *regmap; + struct genpd_onecell_data genpd_data; + struct generic_pm_domain *domains[]; +}; + +struct spacemit_pm_aux_data { + int num_domains; + const struct spacemit_pm_domain_param *param; +}; + +#define to_spacemit_pd(gpd) container_of(gpd, struct spacemit_pm_domain, genpd) + +static int spacemit_pd_power_off(struct generic_pm_domain *domain) +{ + struct spacemit_pm_domain *pd = to_spacemit_pd(domain); + struct spacemit_pmu *pmu = pd->pmu; + const struct spacemit_pm_domain_param *p = pd->param; + unsigned int val; + int ret; + + if (!pd->param->use_hw) { + regmap_clear_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_isolation)); + fsleep(15); + + val = BIT(p->bit_sleep1) | BIT(p->bit_sleep2); + regmap_clear_bits(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(15); + + ret = regmap_read_poll_timeout(pmu->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(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(15); + + ret = regmap_read_poll_timeout(pmu->regmap, + APMU_POWER_STATUS_REG, + val, + !(val & BIT(p->bit_hw_pwr_stat)), + 5, + APMU_POWER_TIMEOUT_US); + } + + if (ret) + dev_err(&domain->dev, "failed to power off domain %s: %d\n", + p->name, pd->pm_index); + + return ret; +} + +static int spacemit_pd_power_on(struct generic_pm_domain *domain) +{ + struct spacemit_pm_domain *pd = to_spacemit_pd(domain); + struct spacemit_pmu *pmu = pd->pmu; + const struct spacemit_pm_domain_param *p = pd->param; + unsigned int val; + int ret = 0; + + if (!p->use_hw) { + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_sleep1)); + fsleep(20); + + val = BIT(p->bit_sleep2) | BIT(p->bit_sleep1); + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(20); + + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_isolation)); + fsleep(15); + + ret = regmap_read_poll_timeout(pmu->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(pmu->regmap, p->reg_pwr_ctrl, val); + fsleep(300); + + ret = regmap_read_poll_timeout(pmu->regmap, + APMU_POWER_STATUS_REG, + val, + (val & BIT(p->bit_hw_pwr_stat)), + 5, + APMU_POWER_TIMEOUT_US); + } + + if (ret) + dev_err(&domain->dev, "failed to power on domain %s: %d\n", + p->name, pd->pm_index); + + return ret; +} + +static bool spacemit_pm_is_off_state(struct spacemit_pmu *pmu, + struct spacemit_pm_domain *pd) +{ + const struct spacemit_pm_domain_param *p = pd->param; + u32 reg = 0, bit; + + regmap_read(pmu->regmap, APMU_POWER_STATUS_REG, ®); + + bit = p->use_hw ? BIT(p->bit_hw_pwr_stat) : BIT(p->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; + int ret; + + pd = devm_kzalloc(pmu->dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + pd->pm_index = id; + pd->param = param; + pd->pmu = pmu; + pd->genpd.name = param->name; + pd->genpd.flags = param->flags; + pd->genpd.power_off = spacemit_pd_power_off; + pd->genpd.power_on = spacemit_pd_power_on; + + ret = pm_genpd_init(&pd->genpd, NULL, spacemit_pm_is_off_state(pmu, pd)); + if (ret) + return ret; + + pmu->domains[id] = &pd->genpd; + + return 0; +} + +static void spacemit_pm_domain_cleanup(struct spacemit_pmu *pmu) +{ + struct spacemit_pm_domain *pd; + int i, ret; + + for (i = 0; i < pmu->genpd_data.num_domains; i++) { + if (!pmu->domains[i]) + continue; + + pd = to_spacemit_pd(pmu->domains[i]); + ret = pm_genpd_remove(pmu->domains[i]); + if (ret) + dev_err(pmu->dev, "failed to remove domain %s: %d - state may be inconsistent\n", + pd->param->name, ret); + } +} + +static int spacemit_pm_domain_probe(struct auxiliary_device *adev, + const struct auxiliary_device_id *id) +{ + struct spacemit_ccu_adev *spdev = to_spacemit_ccu_adev(adev); + const struct spacemit_pm_domain_param *p; + const struct spacemit_pm_aux_data *data; + struct device *dev = &adev->dev; + struct spacemit_pmu *pmu; + int err, i; + + data = (const struct spacemit_pm_aux_data *)id->driver_data; + + pmu = devm_kzalloc(dev, + struct_size(pmu, domains, data->num_domains), + GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + auxiliary_set_drvdata(adev, pmu); + + pmu->dev = dev; + pmu->regmap = spdev->regmap; + pmu->genpd_data.num_domains = data->num_domains; + pmu->genpd_data.domains = pmu->domains; + + for (i = 0; i < data->num_domains; i++) { + p = &data->param[i]; + err = spacemit_pm_add_one_domain(pmu, i, p); + if (err) { + dev_err(dev, "failed to add domain %s: %d\n", p->name, err); + goto err_out; + } + } + + 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; + } + + return 0; + +err_out: + spacemit_pm_domain_cleanup(pmu); + return err; +} + +static void spacemit_pm_domain_remove(struct auxiliary_device *adev) +{ + struct spacemit_pmu *pmu = auxiliary_get_drvdata(adev); + struct device *dev = &adev->dev; + + of_genpd_del_provider(dev->of_node); + spacemit_pm_domain_cleanup(pmu); +} + +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 = true, + .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 = true, + .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 = true, + .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 = true, + .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 = true, + .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, + .flags = GENPD_FLAG_ALWAYS_ON, + .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 = true, + .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 = true, + .reg_pwr_ctrl = 0x3f4, + .name = "lcd1", + }, +}; + +static const struct spacemit_pm_aux_data k1_of_data = { + .num_domains = ARRAY_SIZE(k1_domain_params), + .param = k1_domain_params, +}; + +static const struct spacemit_pm_aux_data k3_of_data = { + .num_domains = ARRAY_SIZE(k3_domain_params), + .param = k3_domain_params, +}; + +static const struct auxiliary_device_id spacemit_pm_domain_ids[] = { + { + .name = "spacemit_ccu.k1-apmu-pmdomain", + .driver_data = (kernel_ulong_t)&k1_of_data, + }, + { + .name = "spacemit_ccu.k3-apmu-pmdomain", + .driver_data = (kernel_ulong_t)&k3_of_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(auxiliary, spacemit_pm_domain_ids); + +static struct auxiliary_driver spacemit_pm_domain_driver = { + .probe = spacemit_pm_domain_probe, + .remove = spacemit_pm_domain_remove, + .id_table = spacemit_pm_domain_ids, +}; +module_auxiliary_driver(spacemit_pm_domain_driver); + +MODULE_DESCRIPTION("SpacemiT Generic Power Domains driver"); +MODULE_LICENSE("GPL"); -- 2.56.0 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v4 2/2] pmdomain: spacemit: Add power domain driver 2026-11-12 8:00 ` Yixun Lan (?) @ 2026-11-12 8:22 ` sashiko-bot -1 siblings, 0 replies; 7+ messages in thread From: sashiko-bot @ 2026-11-12 8:22 UTC (permalink / raw) To: Yixun Lan; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 2 potential issues to consider: - [High] pmdomain: spacemit: devres frees active domains when pm_genpd_remove() fails - [Low] pmdomain: spacemit: log ret instead of pm_index on power transition error -- commit 1bedd4d63c29dbe88ab766b2b331bdd5d01ee76c Author: Yixun Lan <dlan@kernel.org> pmdomain: spacemit: Add power domain driver This commit adds a generic power domain driver for SpacemiT K1 and K3 SoCs as an auxiliary bus driver supporting both software and hardware power sequencing modes. > diff --git a/drivers/pmdomain/spacemit/spacemit-pm-domains.c b/drivers/pmdomain/spacemit/spacemit-pm-domains.c > new file mode 100644 > index 0000000000000..dd0613c1912a0 > --- /dev/null > +++ b/drivers/pmdomain/spacemit/spacemit-pm-domains.c > @@ -0,0 +1,424 @@ [ ... ] > +static int spacemit_pd_power_off(struct generic_pm_domain *domain) > +{ > + struct spacemit_pm_domain *pd = to_spacemit_pd(domain); > + struct spacemit_pmu *pmu = pd->pmu; > + const struct spacemit_pm_domain_param *p = pd->param; > + unsigned int val; > + int ret; > + > + if (!pd->param->use_hw) { > + regmap_clear_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_isolation)); > + fsleep(15); > + > + val = BIT(p->bit_sleep1) | BIT(p->bit_sleep2); > + regmap_clear_bits(pmu->regmap, p->reg_pwr_ctrl, val); > + fsleep(15); > + > + ret = regmap_read_poll_timeout(pmu->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(pmu->regmap, p->reg_pwr_ctrl, val); > + fsleep(15); > + > + ret = regmap_read_poll_timeout(pmu->regmap, > + APMU_POWER_STATUS_REG, > + val, > + !(val & BIT(p->bit_hw_pwr_stat)), > + 5, > + APMU_POWER_TIMEOUT_US); > + } > + > + if (ret) > + dev_err(&domain->dev, "failed to power off domain %s: %d\n", > + p->name, pd->pm_index); > + > + return ret; > +} > + > +static int spacemit_pd_power_on(struct generic_pm_domain *domain) > +{ > + struct spacemit_pm_domain *pd = to_spacemit_pd(domain); > + struct spacemit_pmu *pmu = pd->pmu; > + const struct spacemit_pm_domain_param *p = pd->param; > + unsigned int val; > + int ret = 0; > + > + if (!p->use_hw) { > + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_sleep1)); > + fsleep(20); > + > + val = BIT(p->bit_sleep2) | BIT(p->bit_sleep1); > + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, val); > + fsleep(20); > + > + regmap_set_bits(pmu->regmap, p->reg_pwr_ctrl, BIT(p->bit_isolation)); > + fsleep(15); > + > + ret = regmap_read_poll_timeout(pmu->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(pmu->regmap, p->reg_pwr_ctrl, val); > + fsleep(300); > + > + ret = regmap_read_poll_timeout(pmu->regmap, > + APMU_POWER_STATUS_REG, > + val, > + (val & BIT(p->bit_hw_pwr_stat)), > + 5, > + APMU_POWER_TIMEOUT_US); > + } > + > + if (ret) > + dev_err(&domain->dev, "failed to power on domain %s: %d\n", > + p->name, pd->pm_index); > + > + return ret; > +} [Severity: Low] As reported by Troy Mitchell on v3 [1], should these dev_err() calls in spacemit_pd_power_off() and spacemit_pd_power_on() pass ret instead of pd->pm_index so that the error code is logged rather than the 0-based domain index? With that changed, pd->pm_index can also be dropped. [ ... ] > +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; > + int ret; > + > + pd = devm_kzalloc(pmu->dev, sizeof(*pd), GFP_KERNEL); > + if (!pd) > + return -ENOMEM; > + > + pd->pm_index = id; > + pd->param = param; > + pd->pmu = pmu; > + pd->genpd.name = param->name; > + pd->genpd.flags = param->flags; > + pd->genpd.power_off = spacemit_pd_power_off; > + pd->genpd.power_on = spacemit_pd_power_on; > + > + ret = pm_genpd_init(&pd->genpd, NULL, spacemit_pm_is_off_state(pmu, pd)); > + if (ret) > + return ret; > + > + pmu->domains[id] = &pd->genpd; > + > + return 0; > +} > + > +static void spacemit_pm_domain_cleanup(struct spacemit_pmu *pmu) > +{ > + struct spacemit_pm_domain *pd; > + int i, ret; > + > + for (i = 0; i < pmu->genpd_data.num_domains; i++) { > + if (!pmu->domains[i]) > + continue; > + > + pd = to_spacemit_pd(pmu->domains[i]); > + ret = pm_genpd_remove(pmu->domains[i]); > + if (ret) > + dev_err(pmu->dev, "failed to remove domain %s: %d - state may be inconsistent\n", > + pd->param->name, ret); > + } > +} [ ... ] > +static void spacemit_pm_domain_remove(struct auxiliary_device *adev) > +{ > + struct spacemit_pmu *pmu = auxiliary_get_drvdata(adev); > + struct device *dev = &adev->dev; > + > + of_genpd_del_provider(dev->of_node); > + spacemit_pm_domain_cleanup(pmu); > +} [Severity: High] As reported by Troy Mitchell on v3 [1], can unbinding this driver or unloading the module while a consumer device is attached cause a use-after-free on pd->genpd? Because the auxiliary device shares its parent CCU platform device's of_node, fw_devlink links consumer devices to the parent CCU device rather than to adev->dev, so consumer devices are not detached when spacemit_pm_domain_remove() runs. pm_genpd_remove() then fails with -EBUSY because genpd->device_count > 0, leaving pd->genpd linked in the global gpd_list and referenced by consumers' dev->pm_domain pointers right before devres frees the devm_kzalloc()-allocated struct spacemit_pm_domain. [1] https://lore.kernel.org/r/DMQHSDTR177I.1HWHAF4FWTFQ5@linux.spacemit.com -- Sashiko AI review · https://sashiko.dev/#/patchset/20261112-04-k3-pm-support-v4-0-de3931aa5153@kernel.org?part=2 ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-12 8:22 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-12 8:00 [PATCH v4 0/2] PM: spacemit: Add power domain support Yixun Lan 2026-11-12 8:00 ` Yixun Lan 2026-11-12 8:00 ` [PATCH v4 1/2] dt-bindings: power: spacemit: Add power domain controller IDs Yixun Lan 2026-11-12 8:00 ` Yixun Lan 2026-11-12 8:00 ` [PATCH v4 2/2] pmdomain: spacemit: Add power domain driver Yixun Lan 2026-11-12 8:00 ` Yixun Lan 2026-11-12 8:22 ` sashiko-bot
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.