[PATCH v3 1/4] power-domain: Add QCOM RPMH Power Domain Driver Support
neil.armstrong at linaro.org
neil.armstrong at linaro.org
Thu Jul 3 09:39:54 CEST 2025
Hi,
On 02/07/2025 20:01, Aswin Murugan wrote:
> 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.
Could you explain why you need 2 U_BOOT_DRIVER() ? since you simply
bind the node with the qcom_rpmhpd_drv from the qcom_rpmhpd probe
this could be a single driver, right ?
>
> [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.selvanathan at oss.qualcomm.com>
> Signed-off-by: Aswin Murugan <aswin.murugan at 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
Unused, drop
> + * @active_only: True if it represents an Active only peer
Unused in code, only in structs, you can drop and add it later on.
> + * @corner: current corner
Ditto
> + * @active_corner: current active corner
Ditto
> + * @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
Ditto
> + * @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 },
> + { }
> +};
Just add all SoC entries from the Linux driver, we can test and validate them.
Thanks,
Neil
> +
> +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,
> +};
More information about the U-Boot
mailing list