Re: [PATCH RFC 3/5] pmdomain: marvell: Add PXA1908 power domains

Ulf Hansson
2025-08-19T12:13:18Z

On Wed, 6 Aug 2025 at 19:35, Duje Mihanović <duje@dujemihanovic.xyz> wrote:
>
> Marvell's PXA1908 SoC has a few power domains for its VPU, GPU, image
> processor and DSI PHY. Add a driver to control these.
>
> Signed-off-by: Duje Mihanović <duje@dujemihanovic.xyz>

FYI, overall this looks okay to me, but I will review your next
version more in detail.

Kind regards
Uffe

> ---
> drivers/pmdomain/Kconfig | 1 +
> drivers/pmdomain/Makefile | 1 +
> drivers/pmdomain/marvell/Kconfig | 16 +
> drivers/pmdomain/marvell/Makefile | 3 +
> .../pmdomain/marvell/pxa1908-power-controller.c | 347 +++++++++++++++++++++
> 5 files changed, 368 insertions(+)
>
> diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig
> index 91f04ace35d4b024fafdf6af4e26a179640eb82f..23076ae90e6641dea8e5dbc851d041cd7929cee6 100644
> --- a/drivers/pmdomain/Kconfig
> +++ b/drivers/pmdomain/Kconfig
> @@ -7,6 +7,7 @@ source "drivers/pmdomain/apple/Kconfig"
> source "drivers/pmdomain/arm/Kconfig"
> source "drivers/pmdomain/bcm/Kconfig"
> source "drivers/pmdomain/imx/Kconfig"
> +source "drivers/pmdomain/marvell/Kconfig"
> source "drivers/pmdomain/mediatek/Kconfig"
> source "drivers/pmdomain/qcom/Kconfig"
> source "drivers/pmdomain/renesas/Kconfig"
> diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile
> index 7030f44a49df9e91b1c9d1b6d12690a6248671fb..ebc802f13eb953db750f5a9507caa64c637a957a 100644
> --- a/drivers/pmdomain/Makefile
> +++ b/drivers/pmdomain/Makefile
> @@ -5,6 +5,7 @@ obj-y += apple/
> obj-y += arm/
> obj-y += bcm/
> obj-y += imx/
> +obj-y += marvell/
> obj-y += mediatek/
> obj-y += qcom/
> obj-y += renesas/
> diff --git a/drivers/pmdomain/marvell/Kconfig b/drivers/pmdomain/marvell/Kconfig
> new file mode 100644
> index 0000000000000000000000000000000000000000..be2036726cc563ba2a3d1a82ca24763e2148fec2
> --- /dev/null
> +++ b/drivers/pmdomain/marvell/Kconfig
> @@ -0,0 +1,16 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +menu "Marvell PM Domains"
> + depends on ARCH_MMP || COMPILE_TEST
> +
> +config PXA1908_PM_DOMAINS
> + tristate "Marvell PXA1908 power domains"
> + depends on OF
> + depends on PM
> + default ARCH_MMP && ARM64
> + select REGMAP
> + select PM_GENERIC_DOMAINS
> + help
> + Say Y here to enable support for Marvell PXA1908's power domains.
> +
> +endmenu
> diff --git a/drivers/pmdomain/marvell/Makefile b/drivers/pmdomain/marvell/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..6163bcbcb00ca7256e4c893117b7443b6fb195e7
> --- /dev/null
> +++ b/drivers/pmdomain/marvell/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +obj-$(CONFIG_PXA1908_PM_DOMAINS) += pxa1908-power-controller.o
> diff --git a/drivers/pmdomain/marvell/pxa1908-power-controller.c b/drivers/pmdomain/marvell/pxa1908-power-controller.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..a8940e6dc2eaad2b14e9e6d8aa875c11e114b9dd
> --- /dev/null
> +++ b/drivers/pmdomain/marvell/pxa1908-power-controller.c
> @@ -0,0 +1,347 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright 2025 Duje Mihanović <duje@dujemihanovic.xyz>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/container_of.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/of.h>
> +#include <linux/of_clk.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/regmap.h>
> +#include <linux/units.h>
> +
> +#include <dt-bindings/power/marvell,pxa1908-power.h>
> +
> +/* VPU, GPU, ISP */
> +#define APMU_PWR_CTRL_REG 0xd8
> +#define APMU_PWR_BLK_TMR_REG 0xdc
> +#define APMU_PWR_STATUS_REG 0xf0
> +
> +/* DSI */
> +#define APMU_DEBUG 0x88
> +#define DSI_PHY_DVM_MASK BIT(31)
> +
> +#define POWER_ON_LATENCY_US 300
> +#define POWER_OFF_LATENCY_US 20
> +
> +struct pxa1908_pd_ctrl {
> + struct genpd_onecell_data onecell_data;
> + struct regmap *base;
> + struct generic_pm_domain *domains[];
> +};
> +
> +struct pxa1908_pd_data {
> + u32 reg_clk_res_ctrl;
> + u32 hw_mode;
> + u32 pwr_state;
> + bool keep_on;
> + int id;
> +};
> +
> +struct pxa1908_pd {
> + const struct pxa1908_pd_data data;
> + struct generic_pm_domain genpd;
> + struct clk_bulk_data *clks;
> + struct device *dev;
> + bool initialized;
> + int num_clks;
> +};
> +
> +static bool pxa1908_pd_is_on(struct pxa1908_pd *pd)
> +{
> + struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(pd->dev);
> +
> + return regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, pd->data.pwr_state);
> +}
> +
> +static int pxa1908_pd_power_on(struct generic_pm_domain *genpd)
> +{
> + struct pxa1908_pd *pd = container_of(genpd, struct pxa1908_pd, genpd);
> + struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(pd->dev);
> + const struct pxa1908_pd_data *data = &pd->data;
> + unsigned int status;
> + int ret = 0;
> +
> + if (pd->clks)
> + ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks);
> +
> + regmap_set_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
> + if (data->id != PXA1908_POWER_DOMAIN_ISP)
> + regmap_write(ctrl->base, APMU_PWR_BLK_TMR_REG, 0x20001fff);
> + regmap_set_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
> +
> + usleep_range(POWER_ON_LATENCY_US, POWER_ON_LATENCY_US * 2);
> +
> + ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
> + status & data->pwr_state, 6, 25 * USEC_PER_MSEC);
> + if (ret == -ETIMEDOUT)
> + dev_err(pd->dev, "timed out powering on domain '%s'\n", pd->genpd.name);
> +
> + if (pd->clks)
> + clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
> +
> + return ret;
> +}
> +
> +static int pxa1908_pd_power_off(struct generic_pm_domain *genpd)
> +{
> + struct pxa1908_pd *pd = container_of(genpd, struct pxa1908_pd, genpd);
> + struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(pd->dev);
> + const struct pxa1908_pd_data *data = &pd->data;
> + unsigned int status;
> + int ret;
> +
> + regmap_clear_bits(ctrl->base, APMU_PWR_CTRL_REG, data->pwr_state);
> +
> + usleep_range(POWER_OFF_LATENCY_US, POWER_OFF_LATENCY_US * 2);
> +
> + ret = regmap_read_poll_timeout(ctrl->base, APMU_PWR_STATUS_REG, status,
> + !(status & data->pwr_state), 6, 25 * USEC_PER_MSEC);
> + if (ret == -ETIMEDOUT) {
> + dev_err(pd->dev, "timed out powering off domain '%s'\n", pd->genpd.name);
> + return ret;
> + }
> +
> + regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, data->hw_mode);
> +
> + return 0;
> +}
> +
> +static int pxa1908_dsi_power_on(struct generic_pm_domain *genpd)
> +{
> + struct pxa1908_pd *pd = container_of(genpd, struct pxa1908_pd, genpd);
> + struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(pd->dev);
> +
> + if (pd->clks) {
> + int ret = clk_bulk_prepare_enable(pd->num_clks, pd->clks);
> +
> + if (ret) {
> + dev_err(pd->dev, "failed to enable clocks for domain '%s': %d\n",
> + pd->genpd.name, ret);
> + return ret;
> + }
> + }
> +
> + regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
> +
> + return 0;
> +}
> +
> +static int pxa1908_dsi_power_off(struct generic_pm_domain *genpd)
> +{
> + struct pxa1908_pd *pd = container_of(genpd, struct pxa1908_pd, genpd);
> + struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(pd->dev);
> +
> + regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
> +
> + if (pd->clks)
> + clk_bulk_disable_unprepare(pd->num_clks, pd->clks);
> +
> + return 0;
> +}
> +
> +#define DOMAIN(_id, _name, ctrl, mode, state) \
> + [_id] = { \
> + .data = { \
> + .reg_clk_res_ctrl = ctrl, \
> + .hw_mode = BIT(mode), \
> + .pwr_state = BIT(state), \
> + .id = _id, \
> + }, \
> + .genpd = { \
> + .name = _name, \
> + .power_on = pxa1908_pd_power_on, \
> + .power_off = pxa1908_pd_power_off, \
> + }, \
> + }
> +
> +static struct pxa1908_pd domains[] = {
> + DOMAIN(PXA1908_POWER_DOMAIN_VPU, "vpu", 0xa4, 19, 2),
> + DOMAIN(PXA1908_POWER_DOMAIN_GPU, "gpu", 0xcc, 11, 0),
> + DOMAIN(PXA1908_POWER_DOMAIN_GPU2D, "gpu2d", 0xf4, 11, 6),
> + DOMAIN(PXA1908_POWER_DOMAIN_ISP, "isp", 0x38, 15, 4),
> + [PXA1908_POWER_DOMAIN_DSI] = {
> + .genpd = {
> + .name = "dsi",
> + .power_on = pxa1908_dsi_power_on,
> + .power_off = pxa1908_dsi_power_off,
> + /*
> + * TODO: There is no DSI driver written yet and until then we probably
> + * don't want to power off the DSI PHY ever.
> + */
> + .flags = GENPD_FLAG_ALWAYS_ON,
> + },
> + .data = {
> + /* See above. */
> + .keep_on = true,
> + },
> + },
> +};
> +
> +static void pxa1908_pd_cleanup(struct pxa1908_pd_ctrl *ctrl)
> +{
> + struct pxa1908_pd *pd;
> + int ret;
> +
> + for (int i = ARRAY_SIZE(domains) - 1; i >= 0; i--) {
> + pd = &domains[i];
> +
> + if (!pd->initialized)
> + continue;
> +
> + ret = pm_genpd_remove(&pd->genpd);
> + if (ret)
> + dev_err(pd->dev, "failed to remove domain '%s': %d\n",
> + pd->genpd.name, ret);
> + if (pxa1908_pd_is_on(pd) && !pd->data.keep_on)
> + pxa1908_pd_power_off(&pd->genpd);
> +
> + clk_bulk_put_all(pd->num_clks, pd->clks);
> + }
> +}
> +
> +static int
> +pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, struct device_node *node, struct device *dev)
> +{
> + struct pxa1908_pd *pd;
> + int clk_idx = 0, ret;
> + u32 id;
> +
> + ret = of_property_read_u32(node, "reg", &id);
> + if (ret) {
> + dev_err(dev, "failed to get domain id from reg: %d\n", ret);
> + return ret;
> + }
> +
> + if (id >= ARRAY_SIZE(domains)) {
> + dev_err(dev, "invalid domain id %d\n", id);
> + return ret;
> + }
> +
> + pd = &domains[id];
> + pd->dev = dev;
> + pd->num_clks = of_clk_get_parent_count(node);
> + ctrl->domains[id] = &pd->genpd;
> +
> + if (pd->num_clks > 0) {
> + pd->clks = devm_kcalloc(dev, pd->num_clks, sizeof(*pd->clks), GFP_KERNEL);
> + if (!pd->clks)
> + return -ENOMEM;
> + }
> +
> + for (int i = 0; i < pd->num_clks; i++) {
> + struct clk *clk = of_clk_get(node, i);
> +
> + if (IS_ERR(clk)) {
> + ret = PTR_ERR(clk);
> + dev_err(dev, "failed to get clk for domain '%s': %d\n",
> + pd->genpd.name, ret);
> + goto err;
> + }
> +
> + pd->clks[clk_idx++].clk = clk;
> + }
> +
> + /* Make sure the state of the hardware is synced with the domain table above. */
> + if (pd->data.keep_on) {
> + ret = pd->genpd.power_on(&pd->genpd);
> + if (ret) {
> + dev_err(dev, "failed to power on domain '%s': %d\n", pd->genpd.name, ret);
> + goto err;
> + }
> + } else {
> + if (pxa1908_pd_is_on(pd)) {
> + dev_warn(dev,
> + "domain '%s' is on despite being default off; powering off\n",
> + pd->genpd.name);
> +
> + ret = pxa1908_pd_power_off(&pd->genpd);
> + if (ret) {
> + dev_err(dev, "failed to power off domain '%s': %d\n",
> + pd->genpd.name, ret);
> + goto err;
> + }
> + }
> + }
> +
> + ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on);
> + if (ret) {
> + dev_err(dev, "domain '%s' failed to initialize: %d\n", pd->genpd.name, ret);
> + goto err;
> + }
> +
> + pd->initialized = true;
> +
> + return 0;
> +
> +err:
> + clk_bulk_put_all(pd->num_clks, pd->clks);
> + return ret;
> +}
> +
> +static int pxa1908_pd_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct pxa1908_pd_ctrl *ctrl;
> + struct device_node *node;
> + int ret;
> +
> + ctrl = devm_kzalloc(dev, struct_size(ctrl, domains, ARRAY_SIZE(domains)), GFP_KERNEL);
> + if (!ctrl)
> + return -ENOMEM;
> +
> + ctrl->base = syscon_node_to_regmap(dev->parent->of_node);
> + if (IS_ERR(ctrl->base)) {
> + dev_err(dev, "no regmap available\n");
> + return PTR_ERR(ctrl->base);
> + }
> +
> + platform_set_drvdata(pdev, ctrl);
> +
> + ctrl->onecell_data.domains = ctrl->domains;
> + ctrl->onecell_data.num_domains = ARRAY_SIZE(domains);
> +
> + for_each_available_child_of_node(dev->of_node, node) {
> + ret = pxa1908_pd_init(ctrl, node, dev);
> + if (ret)
> + goto err;
> + }
> +
> + return of_genpd_add_provider_onecell(dev->of_node, &ctrl->onecell_data);
> +
> +err:
> + pxa1908_pd_cleanup(ctrl);
> + return ret;
> +}
> +
> +static void pxa1908_pd_remove(struct platform_device *pdev)
> +{
> + pxa1908_pd_cleanup(platform_get_drvdata(pdev));
> +}
> +
> +static const struct of_device_id pxa1908_pd_match[] = {
> + {
> + .compatible = "marvell,pxa1908-power-controller",
> + },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, pxa1908_pd_match);
> +
> +static struct platform_driver pxa1908_pd_driver = {
> + .probe = pxa1908_pd_probe,
> + .remove = pxa1908_pd_remove,
> + .driver = {
> + .name = "pxa1908-power-controller",
> + .of_match_table = pxa1908_pd_match,
> + },
> +};
> +module_platform_driver(pxa1908_pd_driver);
> +
> +MODULE_AUTHOR("Duje Mihanović <duje@dujemihanovic.xyz>");
> +MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
> +MODULE_LICENSE("GPL");
>
> --
> 2.50.1
>