[PATCH v4 2/4] power-domain: Add QCOM RPMH Power Domain Driver Support
Casey Connolly
casey.connolly at linaro.org
Mon Oct 20 16:11:26 CEST 2025
On 01/10/2025 11:24, neil.armstrong at linaro.org wrote:
> On 10/1/25 11:21, Neil Armstrong via groups.io wrote:
>> On 7/11/25 16:38, 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 of sa8775p; support for other soc entries power
>>> domains are stubbed, in future, the required soc support can be
>>> added.
>>>
>>> [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>
>>> ---
>>> v4:
>>> - Added all SoC entries from the Linux driver and stubbed it
>>> - Removed "qcom_rpmhpd" driver
>>> - Removed unused members in struct rpmhpd
>>>
>>> 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 | 278 +++++++++++++++++++++++++++++
>>> 3 files changed, 287 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..906af2aa0ba
>>> --- /dev/null
>>> +++ b/drivers/power/domain/qcom-rpmhpd.c
>>> @@ -0,0 +1,278 @@
>>> +// 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
>>> + * @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
>>> + * @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;
>>> + unsigned int enable_corner;
>>> + u32 level[RPMH_ARC_MAX_LEVELS];
>>> + size_t level_count;
>>> + bool enabled;
>>> + const char *res_name;
>>> + u32 addr;
>>> + 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 = {
>>> + .res_name = "mmcx.lvl",
>>> +};
>>> +
>>> +static struct rpmhpd mmcx_ao = {
>>> + .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),
>>> +};
>>> +
>>> +/* stub RPMH power domains mapped for unsupported platforms */
>>> +static struct rpmhpd *stub_rpmhpds[] = {};
>>> +
>>> +static const struct rpmhpd_desc stub_desc = {
>>> + .rpmhpds = stub_rpmhpds,
>>> + .num_pds = ARRAY_SIZE(stub_rpmhpds),
>>> +};
>>> +
>>> +static const struct udevice_id rpmhpd_match_table[] = {
>>> + { .compatible = "qcom,sa8775p-rpmhpd", .data =
>>> (ulong)&sa8775p_desc },
>>> + { .compatible = "qcom,qcs615-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,qcs8300-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,qdu1000-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sa8155p-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sa8540p-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sar2130p-rpmhpd", .data = &stub_desc},
>>> + { .compatible = "qcom,sc7180-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sc7280-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sc8180x-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sc8280xp-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sdm670-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sdm845-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sdx55-rpmhpd", .data = &stub_desc},
>>> + { .compatible = "qcom,sdx65-rpmhpd", .data = &stub_desc},
>>> + { .compatible = "qcom,sdx75-rpmhpd", .data = &stub_desc},
>>> + { .compatible = "qcom,sm4450-rpmhpd", .data = &stub_desc},
>>> + { .compatible = "qcom,sm6350-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm7150-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8150-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8250-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8350-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8450-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8550-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8650-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,sm8750-rpmhpd", .data = &stub_desc },
>>> + { .compatible = "qcom,x1e80100-rpmhpd", .data = &stub_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);
>>
>>
>> This is wrong, it shoudl be rpmh_write(pd, state, &cmd, 1),
>
> Sorry rpmh_write(pd->dev, state, &cmd, 1)
I'm confused how/if this worked before... Aswin any chance you can
respin and confirm?
Kind regards,>
>> since rpmh_write gets the parent in get_rpmh_ctrlr().
>>
>> Neil
>>
>>> +}
>>> +
>>> +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,
>>> + .of_match = rpmhpd_match_table,
>>> + .probe = rpmhpd_probe,
>>> + .ops = &qcom_rpmhpd_power_ops,
>>> +};
>>
>>
>>
>> -=-=-=-=-=-=-=-=-=-=-=-
>> Groups.io Links: You receive all messages sent to this group.
>> View/Reply Online (#2311): https://groups.io/g/u-boot-qcom/message/2311
>> Mute This Topic: https://groups.io/mt/114163755/900740
>> Group Owner: u-boot-qcom+owner at groups.io
>> Unsubscribe: https://groups.io/g/u-boot-qcom/
>> leave/13197875/900740/1403832380/xyzzy [neil.armstrong at linaro.org]
>> -=-=-=-=-=-=-=-=-=-=-=-
>>
>>
>
--
// Casey (she/her)
More information about the U-Boot
mailing list