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ć <d...@dujemihanovic.xyz>
---
v3:
- Move driver back to pmdomain subsystem, use auxiliary bus to
  instantiate the driver
- Drop redundant 'struct device' pointer in 'struct pxa1908_pd'
- Fix pxa1908_pd_is_on() for DSI domain
- Replace usleep_range() with fsleep()
- Use dev_err_probe() where sensible

v2:
- Move to clk subsystem, instantiate the driver from the APMU clock
  driver
- Drop clock handling
- Squash MAINTAINERS patch
---
 MAINTAINERS                                        |   1 +
 drivers/pmdomain/Kconfig                           |   1 +
 drivers/pmdomain/Makefile                          |   1 +
 drivers/pmdomain/marvell/Kconfig                   |  18 ++
 drivers/pmdomain/marvell/Makefile                  |   3 +
 .../pmdomain/marvell/pxa1908-power-controller.c    | 268 +++++++++++++++++++++
 6 files changed, 292 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 
88f7bd50686eb1f6bcd4f34c6827f27ad44ea4e8..34e5e218e83e0ed9882b111f5251601dd6549d4e
 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2871,6 +2871,7 @@ L:        linux-arm-ker...@lists.infradead.org (moderated 
for non-subscribers)
 S:     Maintained
 F:     arch/arm64/boot/dts/marvell/mmp/
 F:     drivers/clk/mmp/clk-pxa1908*.c
+F:     drivers/pmdomain/marvell/
 F:     include/dt-bindings/clock/marvell,pxa1908.h
 F:     include/dt-bindings/power/marvell,pxa1908-power.h
 
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..6c4084c826670266b7d948438f6e6d76acb416e2
--- /dev/null
+++ b/drivers/pmdomain/marvell/Kconfig
@@ -0,0 +1,18 @@
+# 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 y if ARCH_MMP && ARM64
+       select AUXILIARY_BUS
+       select MFD_SYSCON
+       select PM_GENERIC_DOMAINS
+       select PM_GENERIC_DOMAINS_OF
+       help
+         Say Y here to enable support for Marvell PXA1908's power domanis.
+
+endmenu
diff --git a/drivers/pmdomain/marvell/Makefile 
b/drivers/pmdomain/marvell/Makefile
new file mode 100644
index 
0000000000000000000000000000000000000000..22c25013f6c856a2ca01a121e830279ee88eb0ed
--- /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..29134629861abcf46959f9dcc98d3f05a4cc5b72
--- /dev/null
+++ b/drivers/pmdomain/marvell/pxa1908-power-controller.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2025 Duje Mihanović <d...@dujemihanovic.xyz>
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mod_devicetable.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
+
+#define NR_DOMAINS     5
+
+struct pxa1908_pd_ctrl {
+       struct generic_pm_domain *domains[NR_DOMAINS];
+       struct genpd_onecell_data onecell_data;
+       struct regmap *base;
+};
+
+struct pxa1908_pd_data {
+       u32 reg_clk_res_ctrl;
+       u32 pwr_state;
+       u32 hw_mode;
+       bool keep_on;
+       int id;
+};
+
+struct pxa1908_pd {
+       const struct pxa1908_pd_data data;
+       struct generic_pm_domain genpd;
+       bool initialized;
+};
+
+static inline bool pxa1908_pd_is_on(struct pxa1908_pd *pd)
+{
+       struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(&pd->genpd.dev);
+
+       return pd->data.id != PXA1908_POWER_DOMAIN_DSI
+               ? regmap_test_bits(ctrl->base, APMU_PWR_STATUS_REG, 
pd->data.pwr_state)
+               : regmap_test_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
+}
+
+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(&genpd->dev);
+       const struct pxa1908_pd_data *data = &pd->data;
+       unsigned int status;
+       int ret = 0;
+
+       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);
+
+       fsleep(POWER_ON_LATENCY_US);
+
+       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(&genpd->dev, "timed out powering on domain '%s'\n", 
pd->genpd.name);
+
+       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(&genpd->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);
+
+       fsleep(POWER_OFF_LATENCY_US);
+
+       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(&genpd->dev, "timed out powering off domain '%s'\n", 
pd->genpd.name);
+               return ret;
+       }
+
+       return regmap_clear_bits(ctrl->base, data->reg_clk_res_ctrl, 
data->hw_mode);
+}
+
+static inline int pxa1908_dsi_power_on(struct generic_pm_domain *genpd)
+{
+       struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(&genpd->dev);
+
+       return regmap_set_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
+}
+
+static inline int pxa1908_dsi_power_off(struct generic_pm_domain *genpd)
+{
+       struct pxa1908_pd_ctrl *ctrl = dev_get_drvdata(&genpd->dev);
+
+       return regmap_clear_bits(ctrl->base, APMU_DEBUG, DSI_PHY_DVM_MASK);
+}
+
+#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[NR_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_remove(struct auxiliary_device *auxdev)
+{
+       struct pxa1908_pd *pd;
+       int ret;
+
+       for (int i = NR_DOMAINS - 1; i >= 0; i--) {
+               pd = &domains[i];
+
+               if (!pd->initialized)
+                       continue;
+
+               if (pxa1908_pd_is_on(pd) && !pd->data.keep_on)
+                       pxa1908_pd_power_off(&pd->genpd);
+
+               ret = pm_genpd_remove(&pd->genpd);
+               if (ret)
+                       dev_err(&pd->genpd.dev, "failed to remove domain '%s': 
%d\n",
+                               pd->genpd.name, ret);
+       }
+}
+
+static int
+pxa1908_pd_init(struct pxa1908_pd_ctrl *ctrl, int id, struct device *dev)
+{
+       struct pxa1908_pd *pd = &domains[id];
+       int ret;
+
+       ctrl->domains[id] = &pd->genpd;
+
+       ret = pm_genpd_init(&pd->genpd, NULL, !pd->data.keep_on);
+       if (ret)
+               return dev_err_probe(dev, ret, "domain '%s' failed to 
initialize\n",
+                                    pd->genpd.name);
+
+       dev_set_drvdata(&pd->genpd.dev, ctrl);
+
+       /* 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)
+                       return dev_err_probe(dev, ret, "failed to power on 
domain '%s'\n",
+                                            pd->genpd.name);
+       } 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 = pd->genpd.power_off(&pd->genpd);
+                       if (ret)
+                               return dev_err_probe(dev, ret,
+                                                    "failed to power off 
domain '%s'\n",
+                                                    pd->genpd.name);
+               }
+       }
+
+       pd->initialized = true;
+
+       return 0;
+}
+
+static int
+pxa1908_pd_probe(struct auxiliary_device *auxdev, const struct 
auxiliary_device_id *aux_id)
+{
+       struct pxa1908_pd_ctrl *ctrl;
+       struct device *dev = &auxdev->dev;
+       int ret;
+
+       ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+       if (!ctrl)
+               return -ENOMEM;
+
+       auxiliary_set_drvdata(auxdev, ctrl);
+
+       ctrl->base = syscon_node_to_regmap(dev->parent->of_node);
+       if (IS_ERR(ctrl->base))
+               return dev_err_probe(dev, PTR_ERR(ctrl->base), "no regmap 
available\n");
+
+       ctrl->onecell_data.domains = ctrl->domains;
+       ctrl->onecell_data.num_domains = NR_DOMAINS;
+
+       for (int i = 0; i < NR_DOMAINS; i++) {
+               ret = pxa1908_pd_init(ctrl, i, dev);
+               if (ret)
+                       goto err;
+       }
+
+       return of_genpd_add_provider_onecell(dev->parent->of_node, 
&ctrl->onecell_data);
+
+err:
+       pxa1908_pd_remove(auxdev);
+       return ret;
+}
+
+static const struct auxiliary_device_id pxa1908_pd_id[] = {
+       { .name = "clk_pxa1908_apmu.power" },
+       { }
+};
+MODULE_DEVICE_TABLE(auxiliary, pxa1908_pd_id);
+
+static struct auxiliary_driver pxa1908_pd_driver = {
+       .probe = pxa1908_pd_probe,
+       .remove = pxa1908_pd_remove,
+       .id_table = pxa1908_pd_id,
+};
+module_auxiliary_driver(pxa1908_pd_driver);
+
+MODULE_AUTHOR("Duje Mihanović <d...@dujemihanovic.xyz>");
+MODULE_DESCRIPTION("Marvell PXA1908 power domain driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0


Reply via email to