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