Added support for Qualcomm RPMH power domain driver, responsible
for managing power domains on Qualcomm SoCs. This is a port of
the Linux RPMHPD driver [1] and sa8775p related changes. The
power domain driver currently has support to power on and off
MMCX power domain; support for other power domains can be added
in this driver.

[1]:
https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/pmdomain/qcom/rpmhpd.c?id=3d25d46a255a83f94d7d4d4216f38aafc8e116b

Signed-off-by: Balaji Selvanathan <balaji.selvanat...@oss.qualcomm.com>
Signed-off-by: Aswin Murugan <aswin.muru...@oss.qualcomm.com>
---
v3:
- No changes to this patch in v3

v2:
- Added ARCH_SNAPDRAGON dependency to QCOM_POWER_DOMAIN Kconfig
- In qcom-rpmhpd driver, the un-supported power domains are handled with warning
---
 drivers/power/domain/Kconfig       |   8 +
 drivers/power/domain/Makefile      |   1 +
 drivers/power/domain/qcom-rpmhpd.c | 285 +++++++++++++++++++++++++++++
 3 files changed, 294 insertions(+)
 create mode 100644 drivers/power/domain/qcom-rpmhpd.c

diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig
index 5f5218bd8b5..1456df96cd1 100644
--- a/drivers/power/domain/Kconfig
+++ b/drivers/power/domain/Kconfig
@@ -82,6 +82,14 @@ config MESON_SECURE_POWER_DOMAIN
          Enable support for manipulating Amlogic Meson Secure power domains.
          Support for Amlogic A1 series.
 
+config QCOM_POWER_DOMAIN
+       bool "Enable the QCOM RPMH Power domain driver"
+       depends on POWER_DOMAIN && ARCH_SNAPDRAGON
+       help
+         Generic RPMH power domain implementation for QCOM devices.
+         The RPMH power domain driver is responsible for managing power
+         domains on Qualcomm SoCs.
+
 config SANDBOX_POWER_DOMAIN
        bool "Enable the sandbox power domain test driver"
        depends on POWER_DOMAIN && SANDBOX
diff --git a/drivers/power/domain/Makefile b/drivers/power/domain/Makefile
index 4d20c97d26c..950f83972dc 100644
--- a/drivers/power/domain/Makefile
+++ b/drivers/power/domain/Makefile
@@ -21,3 +21,4 @@ obj-$(CONFIG_TEGRA186_POWER_DOMAIN) += tegra186-power-domain.o
 obj-$(CONFIG_TI_SCI_POWER_DOMAIN) += ti-sci-power-domain.o
 obj-$(CONFIG_TI_POWER_DOMAIN) += ti-power-domain.o
 obj-$(CONFIG_ZYNQMP_POWER_DOMAIN) += zynqmp-power-domain.o
+obj-$(CONFIG_QCOM_POWER_DOMAIN) += qcom-rpmhpd.o
diff --git a/drivers/power/domain/qcom-rpmhpd.c 
b/drivers/power/domain/qcom-rpmhpd.c
new file mode 100644
index 00000000000..a8eab26bd23
--- /dev/null
+++ b/drivers/power/domain/qcom-rpmhpd.c
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018, The Linux Foundation. All rights reserved.
+// Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+
+#include <dm.h>
+#include <dm/lists.h>
+#include <power-domain.h>
+#include <asm/io.h>
+#include <linux/errno.h>
+
+#include <power-domain-uclass.h>
+#include <soc/qcom/cmd-db.h>
+#include <soc/qcom/rpmh.h>
+#include <dt-bindings/power/qcom-rpmpd.h>
+#include <dm/device_compat.h>
+
+#define RPMH_ARC_MAX_LEVELS    16
+
+/**
+ * struct rpmhpd - top level RPMh power domain resource data structure
+ * @dev:                rpmh power domain controller device
+ * @pd:                 generic_pm_domain corresponding to the power domain
+ * @parent:             generic_pm_domain corresponding to the parent's power 
domain
+ * @peer:               A peer power domain in case Active only Voting is
+ *                      supported
+ * @active_only:        True if it represents an Active only peer
+ * @corner:             current corner
+ * @active_corner:      current active corner
+ * @enable_corner:      lowest non-zero corner
+ * @level:              An array of level (vlvl) to corner (hlvl) mappings
+ *                      derived from cmd-db
+ * @level_count:        Number of levels supported by the power domain. max
+ *                      being 16 (0 - 15)
+ * @enabled:            true if the power domain is enabled
+ * @res_name:           Resource name used for cmd-db lookup
+ * @addr:               Resource address as looped up using resource name from
+ *                      cmd-db
+ * @state_synced:       Indicator that sync_state has been invoked for the 
rpmhpd resource
+ * @skip_retention_level: Indicate that retention level should not be used for 
the power domain
+ */
+struct rpmhpd {
+       struct udevice  *dev;
+       struct power_domain pd;
+       struct power_domain *parent;
+       struct rpmhpd   *peer;
+       const bool      active_only;
+       unsigned int    corner;
+       unsigned int    active_corner;
+       unsigned int    enable_corner;
+       u32             level[RPMH_ARC_MAX_LEVELS];
+       size_t          level_count;
+       bool            enabled;
+       const char      *res_name;
+       u32             addr;
+       bool            state_synced;
+       bool            skip_retention_level;
+};
+
+struct rpmhpd_desc {
+       struct rpmhpd **rpmhpds;
+       size_t num_pds;
+};
+
+/* RPMH powerdomains */
+static struct rpmhpd mmcx_ao;
+static struct rpmhpd mmcx = {
+       .peer = &mmcx_ao,
+       .res_name = "mmcx.lvl",
+};
+
+static struct rpmhpd mmcx_ao = {
+       .active_only = true,
+       .peer = &mmcx,
+       .res_name = "mmcx.lvl",
+};
+
+/* SA8775P RPMH power domains */
+static struct rpmhpd *sa8775p_rpmhpds[] = {
+       [SA8775P_MMCX] = &mmcx,
+       [SA8775P_MMCX_AO] = &mmcx_ao,
+};
+
+static const struct rpmhpd_desc sa8775p_desc = {
+       .rpmhpds = sa8775p_rpmhpds,
+       .num_pds = ARRAY_SIZE(sa8775p_rpmhpds),
+};
+
+static const struct udevice_id rpmhpd_match_table[] = {
+       { .compatible = "qcom,sa8775p-rpmhpd", .data = (ulong)&sa8775p_desc },
+       { }
+};
+
+static int rpmhpd_send_corner(struct rpmhpd *pd, int state,
+                             unsigned int corner, bool sync)
+{
+       struct tcs_cmd cmd = {
+               .addr = pd->addr,
+               .data = corner,
+       };
+
+       return rpmh_write(pd->dev->parent, state, &cmd, 1);
+}
+
+static int rpmhpd_power_on(struct power_domain *pd)
+{
+       int ret;
+       unsigned int corner;
+       struct rpmhpd **rpmhpds;
+       const struct rpmhpd_desc *desc;
+       struct rpmhpd *curr_rpmhpd;
+
+       desc = (const struct rpmhpd_desc *)dev_get_driver_data(pd->dev);
+       if (!desc)
+               return -EINVAL;
+
+       rpmhpds = desc->rpmhpds;
+       curr_rpmhpd = rpmhpds[pd->id];
+
+       /* Do nothing for undefined power domains */
+       if (!curr_rpmhpd) {
+               log_warning("Power domain id (%d) not supported\n",
+                           pd->id);
+               return 0;
+       }
+
+       corner = curr_rpmhpd->enable_corner;
+
+       ret = rpmhpd_send_corner(curr_rpmhpd, RPMH_ACTIVE_ONLY_STATE, corner,
+                                false);
+       if (!ret)
+               curr_rpmhpd->enabled = true;
+
+       return ret;
+}
+
+static int rpmhpd_power_off(struct power_domain *pd)
+{
+       int ret;
+       unsigned int corner;
+       struct rpmhpd **rpmhpds;
+       const struct rpmhpd_desc *desc;
+       struct rpmhpd *curr_rpmhpd;
+
+       desc = (const struct rpmhpd_desc *)dev_get_driver_data(pd->dev);
+       if (!desc)
+               return -EINVAL;
+
+       rpmhpds = desc->rpmhpds;
+       curr_rpmhpd = rpmhpds[pd->id];
+
+       /* Do nothing for undefined power domains */
+       if (!curr_rpmhpd) {
+               log_warning("Power domain id (%d) not supported\n",
+                           pd->id, 0);
+               return 0;
+       }
+
+       corner = 0;
+
+       ret = rpmhpd_send_corner(curr_rpmhpd, RPMH_ACTIVE_ONLY_STATE, corner,
+                                false);
+       if (!ret)
+               curr_rpmhpd->enabled = false;
+
+       return ret;
+}
+
+static int rpmhpd_update_level_mapping(struct rpmhpd *rpmhpd)
+{
+       int i;
+       const u16 *buf;
+
+       buf = cmd_db_read_aux_data(rpmhpd->res_name, &rpmhpd->level_count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       /* 2 bytes used for each command DB aux data entry */
+       rpmhpd->level_count >>= 1;
+
+       if (rpmhpd->level_count > RPMH_ARC_MAX_LEVELS)
+               return -EINVAL;
+
+       for (i = 0; i < rpmhpd->level_count; i++) {
+               if (rpmhpd->skip_retention_level && buf[i] == 
RPMH_REGULATOR_LEVEL_RETENTION)
+                       continue;
+
+               rpmhpd->level[i] = buf[i];
+
+               /* Remember the first corner with non-zero level */
+               if (!rpmhpd->level[rpmhpd->enable_corner] && rpmhpd->level[i])
+                       rpmhpd->enable_corner = i;
+
+               /*
+                * The AUX data may be zero padded. These 0 valued entries at
+                * the end of the map must be ignored.
+                */
+               if (i > 0 && rpmhpd->level[i] == 0) {
+                       rpmhpd->level_count = i;
+                       break;
+               }
+               debug("%s: ARC hlvl=%2d --> vlvl=%4u\n", rpmhpd->res_name, i,
+                     rpmhpd->level[i]);
+       }
+
+       return 0;
+}
+
+static int rpmhpd_probe(struct udevice *dev)
+{
+       int i, ret;
+       struct rpmhpd **rpmhpds;
+       struct rpmhpd *priv;
+       const struct rpmhpd_desc *desc;
+
+       desc = (const struct rpmhpd_desc *)dev_get_driver_data(dev);
+       if (!desc)
+               return -EINVAL;
+
+       rpmhpds = desc->rpmhpds;
+
+       for (i = 0; i < desc->num_pds; i++) {
+               if (!rpmhpds[i])
+                       continue;
+
+               priv = rpmhpds[i];
+               priv->dev = dev;
+               priv->addr = cmd_db_read_addr(priv->res_name);
+               if (!priv->addr) {
+                       dev_err(dev, "Could not find RPMh address for resource 
%s\n",
+                               priv->res_name);
+                       return -ENODEV;
+               }
+
+               ret = cmd_db_read_slave_id(priv->res_name);
+               if (ret != CMD_DB_HW_ARC) {
+                       dev_err(dev, "RPMh slave ID mismatch\n");
+                       return -EINVAL;
+               }
+
+               ret = rpmhpd_update_level_mapping(priv);
+               if (ret)
+                       return ret;
+       }
+
+       return ret;
+}
+
+static const struct power_domain_ops qcom_rpmhpd_power_ops = {
+       .on = rpmhpd_power_on,
+       .off = rpmhpd_power_off,
+};
+
+U_BOOT_DRIVER(qcom_rpmhpd_drv) = {
+       .name = "qcom_rpmhpd_drv",
+       .id = UCLASS_POWER_DOMAIN,
+       .probe = rpmhpd_probe,
+       .ops = &qcom_rpmhpd_power_ops,
+};
+
+int qcom_rpmhpd_bind(struct udevice *parent)
+{
+       struct rpmhpd_desc *data = (struct rpmhpd_desc 
*)dev_get_driver_data(parent);
+       struct driver *drv;
+       int ret;
+
+       /* Get a handle to the rpmhpd handler */
+       drv = lists_driver_lookup_name("qcom_rpmhpd_drv");
+       if (!drv)
+               return -ENOENT;
+
+       /* Register the rpmhpd dev */
+       ret = device_bind_with_driver_data(parent, drv, "qcom_rpmhpd_dev", 
(ulong)data,
+                                          dev_ofnode(parent), NULL);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+U_BOOT_DRIVER(qcom_rpmhpd) = {
+       .name = "qcom_rpmhpd",
+       .id = UCLASS_NOP,
+       .of_match = rpmhpd_match_table,
+       .bind = qcom_rpmhpd_bind,
+};
-- 
2.34.1

Reply via email to