diff options
Diffstat (limited to 'drivers/power/domain')
| -rw-r--r-- | drivers/power/domain/Kconfig | 24 | ||||
| -rw-r--r-- | drivers/power/domain/Makefile | 2 | ||||
| -rw-r--r-- | drivers/power/domain/apple-pmgr.c | 5 | ||||
| -rw-r--r-- | drivers/power/domain/bcm6328-power-domain.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/imx8-power-domain-legacy.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/imx8-power-domain.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/imx8m-power-domain.c | 6 | ||||
| -rw-r--r-- | drivers/power/domain/imx8mp-mediamix.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/meson-ee-pwrc.c | 15 | ||||
| -rw-r--r-- | drivers/power/domain/meson-gx-pwrc-vpu.c | 15 | ||||
| -rw-r--r-- | drivers/power/domain/meson-secure-pwrc.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/mtk-power-domain.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/power-domain-uclass.c | 7 | ||||
| -rw-r--r-- | drivers/power/domain/qcom-rpmhpd.c | 278 | ||||
| -rw-r--r-- | drivers/power/domain/renesas-r8a78000-power-domain.c | 425 | ||||
| -rw-r--r-- | drivers/power/domain/sandbox-power-domain.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/scmi-power-domain.c | 10 | ||||
| -rw-r--r-- | drivers/power/domain/tegra186-power-domain.c | 2 | ||||
| -rw-r--r-- | drivers/power/domain/zynqmp-power-domain.c | 2 |
19 files changed, 759 insertions, 46 deletions
diff --git a/drivers/power/domain/Kconfig b/drivers/power/domain/Kconfig index 2f63a8e54e5..bb9c52155d2 100644 --- a/drivers/power/domain/Kconfig +++ b/drivers/power/domain/Kconfig @@ -35,10 +35,10 @@ config BCM6328_POWER_DOMAIN config IMX8_POWER_DOMAIN bool "Enable i.MX8 power domain driver" - depends on ARCH_IMX8 - help - Enable support for manipulating NXP i.MX8 on-SoC power domains via IPC - requests to the SCU. + depends on ARCH_IMX8 + help + Enable support for manipulating NXP i.MX8 on-SoC power domains via IPC + requests to the SCU. config IMX8M_POWER_DOMAIN bool "Enable i.MX8M power domain driver" @@ -90,6 +90,22 @@ config MESON_SECURE_POWER_DOMAIN Enable support for manipulating Amlogic Meson Secure power domains. Support for Amlogic A1 series. +config QCOM_RPMH_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 RENESAS_R8A78000_POWER_DOMAIN + bool "Enable the Renesas R-Car MDLC Power domain and reset driver" + depends on POWER_DOMAIN && ARCH_RENESAS + help + Enable support for Renesas R-Car R8A78000 X5H MDLC Power domain + and reset driver. The MDLC is responsible for managing both + power domains and resets on R-Car R8A78000 X5H SoC. + 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 b2c0bd8a61a..110153d5cf8 100644 --- a/drivers/power/domain/Makefile +++ b/drivers/power/domain/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_MTK_POWER_DOMAIN) += mtk-power-domain.o obj-$(CONFIG_MESON_GX_VPU_POWER_DOMAIN) += meson-gx-pwrc-vpu.o obj-$(CONFIG_MESON_EE_POWER_DOMAIN) += meson-ee-pwrc.o obj-$(CONFIG_MESON_SECURE_POWER_DOMAIN) += meson-secure-pwrc.o +obj-$(CONFIG_RENESAS_R8A78000_POWER_DOMAIN) += renesas-r8a78000-power-domain.o obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain.o obj-$(CONFIG_SANDBOX_POWER_DOMAIN) += sandbox-power-domain-test.o obj-$(CONFIG_SCMI_POWER_DOMAIN) += scmi-power-domain.o @@ -23,3 +24,4 @@ obj-$(CONFIG_TI_SCI_POWER_DOMAIN) += ti-sci-power-domain.o obj-$(CONFIG_TI_POWER_DOMAIN) += ti-power-domain.o obj-$(CONFIG_TI_OMAP_PRM_POWER_DOMAIN) += ti-omap-prm.o obj-$(CONFIG_ZYNQMP_POWER_DOMAIN) += zynqmp-power-domain.o +obj-$(CONFIG_QCOM_RPMH_POWER_DOMAIN) += qcom-rpmhpd.o diff --git a/drivers/power/domain/apple-pmgr.c b/drivers/power/domain/apple-pmgr.c index bf9940621ee..9873d3cd8db 100644 --- a/drivers/power/domain/apple-pmgr.c +++ b/drivers/power/domain/apple-pmgr.c @@ -67,7 +67,7 @@ static int apple_reset_deassert(struct reset_ctl *reset_ctl) return 0; } -struct reset_ops apple_reset_ops = { +static const struct reset_ops apple_reset_ops = { .of_xlate = apple_reset_of_xlate, .rst_assert = apple_reset_assert, .rst_deassert = apple_reset_deassert, @@ -110,6 +110,7 @@ static int apple_pmgr_of_xlate(struct power_domain *power_domain, } static const struct udevice_id apple_pmgr_ids[] = { + { .compatible = "apple,t8103-pmgr-pwrstate" }, { .compatible = "apple,pmgr-pwrstate" }, { /* sentinel */ } }; @@ -138,7 +139,7 @@ static int apple_pmgr_probe(struct udevice *dev) return 0; } -struct power_domain_ops apple_pmgr_ops = { +static const struct power_domain_ops apple_pmgr_ops = { .on = apple_pmgr_on, .of_xlate = apple_pmgr_of_xlate, }; diff --git a/drivers/power/domain/bcm6328-power-domain.c b/drivers/power/domain/bcm6328-power-domain.c index 36b5a933748..5b449f4c29d 100644 --- a/drivers/power/domain/bcm6328-power-domain.c +++ b/drivers/power/domain/bcm6328-power-domain.c @@ -57,7 +57,7 @@ static const struct udevice_id bcm6328_power_domain_ids[] = { { /* sentinel */ } }; -struct power_domain_ops bcm6328_power_domain_ops = { +static const struct power_domain_ops bcm6328_power_domain_ops = { .off = bcm6328_power_domain_off, .on = bcm6328_power_domain_on, .request = bcm6328_power_domain_request, diff --git a/drivers/power/domain/imx8-power-domain-legacy.c b/drivers/power/domain/imx8-power-domain-legacy.c index 713a51d7807..a646f667039 100644 --- a/drivers/power/domain/imx8-power-domain-legacy.c +++ b/drivers/power/domain/imx8-power-domain-legacy.c @@ -347,7 +347,7 @@ static const struct udevice_id imx8_power_domain_ids[] = { { } }; -struct power_domain_ops imx8_power_domain_ops = { +static const struct power_domain_ops imx8_power_domain_ops = { .on = imx8_power_domain_on, .off = imx8_power_domain_off, .of_xlate = imx8_power_domain_of_xlate, diff --git a/drivers/power/domain/imx8-power-domain.c b/drivers/power/domain/imx8-power-domain.c index e8dcc057fee..5740cd686db 100644 --- a/drivers/power/domain/imx8-power-domain.c +++ b/drivers/power/domain/imx8-power-domain.c @@ -51,7 +51,7 @@ static const struct udevice_id imx8_power_domain_ids[] = { { } }; -struct power_domain_ops imx8_power_domain_ops_v2 = { +static const struct power_domain_ops imx8_power_domain_ops_v2 = { .on = imx8_power_domain_on, .off = imx8_power_domain_off, }; diff --git a/drivers/power/domain/imx8m-power-domain.c b/drivers/power/domain/imx8m-power-domain.c index 1c731b897cc..a3ef49aa67c 100644 --- a/drivers/power/domain/imx8m-power-domain.c +++ b/drivers/power/domain/imx8m-power-domain.c @@ -476,7 +476,7 @@ static int imx8m_power_domain_bind(struct udevice *dev) const char *name; int ret = 0; - ofnode_for_each_subnode(subnode, dev_ofnode(dev)) { + dev_for_each_subnode(subnode, dev) { /* Bind the subnode to this driver */ name = ofnode_get_name(subnode); @@ -531,7 +531,7 @@ static int imx8m_power_domain_of_to_plat(struct udevice *dev) struct imx_pgc_domain_data *domain_data = (struct imx_pgc_domain_data *)dev_get_driver_data(dev); - pdata->resource_id = ofnode_read_u32_default(dev_ofnode(dev), "reg", -1); + pdata->resource_id = dev_read_u32_default(dev, "reg", -1); pdata->domain = &domain_data->domains[pdata->resource_id]; pdata->regs = domain_data->pgc_regs; pdata->base = dev_read_addr_ptr(dev->parent); @@ -558,7 +558,7 @@ static const struct udevice_id imx8m_power_domain_ids[] = { { } }; -struct power_domain_ops imx8m_power_domain_ops = { +static const struct power_domain_ops imx8m_power_domain_ops = { .on = imx8m_power_domain_on, .off = imx8m_power_domain_off, .of_xlate = imx8m_power_domain_of_xlate, diff --git a/drivers/power/domain/imx8mp-mediamix.c b/drivers/power/domain/imx8mp-mediamix.c index 504c22f7d36..66ea5d8e60c 100644 --- a/drivers/power/domain/imx8mp-mediamix.c +++ b/drivers/power/domain/imx8mp-mediamix.c @@ -194,7 +194,7 @@ static const struct udevice_id imx8mp_mediamix_ids[] = { { } }; -struct power_domain_ops imx8mp_mediamix_ops = { +static const struct power_domain_ops imx8mp_mediamix_ops = { .on = imx8mp_mediamix_on, .off = imx8mp_mediamix_off, .of_xlate = imx8mp_mediamix_of_xlate, diff --git a/drivers/power/domain/meson-ee-pwrc.c b/drivers/power/domain/meson-ee-pwrc.c index 4d9f3bba644..882238f2937 100644 --- a/drivers/power/domain/meson-ee-pwrc.c +++ b/drivers/power/domain/meson-ee-pwrc.c @@ -386,7 +386,7 @@ static int meson_ee_pwrc_of_xlate(struct power_domain *power_domain, return 0; } -struct power_domain_ops meson_ee_pwrc_ops = { +static const struct power_domain_ops meson_ee_pwrc_ops = { .off = meson_ee_pwrc_off, .on = meson_ee_pwrc_on, .of_xlate = meson_ee_pwrc_of_xlate, @@ -435,8 +435,7 @@ static const struct udevice_id meson_ee_pwrc_ids[] = { static int meson_ee_pwrc_probe(struct udevice *dev) { struct meson_ee_pwrc_priv *priv = dev_get_priv(dev); - u32 ao_phandle; - ofnode ao_node; + struct ofnode_phandle_args args; int ret; priv->data = (void *)dev_get_driver_data(dev); @@ -447,16 +446,12 @@ static int meson_ee_pwrc_probe(struct udevice *dev) if (IS_ERR(priv->regmap_hhi)) return PTR_ERR(priv->regmap_hhi); - ret = ofnode_read_u32(dev_ofnode(dev), "amlogic,ao-sysctrl", - &ao_phandle); + ret = dev_read_phandle_with_args(dev, "amlogic,ao-sysctrl", NULL, 0, 0, + &args); if (ret) return ret; - ao_node = ofnode_get_by_phandle(ao_phandle); - if (!ofnode_valid(ao_node)) - return -EINVAL; - - priv->regmap_ao = syscon_node_to_regmap(ao_node); + priv->regmap_ao = syscon_node_to_regmap(args.node); if (IS_ERR(priv->regmap_ao)) return PTR_ERR(priv->regmap_ao); diff --git a/drivers/power/domain/meson-gx-pwrc-vpu.c b/drivers/power/domain/meson-gx-pwrc-vpu.c index 1c56e8508c3..e08c0fac49a 100644 --- a/drivers/power/domain/meson-gx-pwrc-vpu.c +++ b/drivers/power/domain/meson-gx-pwrc-vpu.c @@ -262,7 +262,7 @@ static int meson_pwrc_vpu_of_xlate(struct power_domain *power_domain, return 0; } -struct power_domain_ops meson_gx_pwrc_vpu_ops = { +static const struct power_domain_ops meson_gx_pwrc_vpu_ops = { .off = meson_pwrc_vpu_off, .on = meson_pwrc_vpu_on, .of_xlate = meson_pwrc_vpu_of_xlate, @@ -283,24 +283,19 @@ static const struct udevice_id meson_gx_pwrc_vpu_ids[] = { static int meson_gx_pwrc_vpu_probe(struct udevice *dev) { struct meson_gx_pwrc_vpu_priv *priv = dev_get_priv(dev); - u32 hhi_phandle; - ofnode hhi_node; + struct ofnode_phandle_args args; int ret; priv->regmap_ao = syscon_node_to_regmap(dev_ofnode(dev_get_parent(dev))); if (IS_ERR(priv->regmap_ao)) return PTR_ERR(priv->regmap_ao); - ret = ofnode_read_u32(dev_ofnode(dev), "amlogic,hhi-sysctrl", - &hhi_phandle); + ret = dev_read_phandle_with_args(dev, "amlogic,hhi-sysctrl", NULL, 0, 0, + &args); if (ret) return ret; - hhi_node = ofnode_get_by_phandle(hhi_phandle); - if (!ofnode_valid(hhi_node)) - return -EINVAL; - - priv->regmap_hhi = syscon_node_to_regmap(hhi_node); + priv->regmap_hhi = syscon_node_to_regmap(args.node); if (IS_ERR(priv->regmap_hhi)) return PTR_ERR(priv->regmap_hhi); diff --git a/drivers/power/domain/meson-secure-pwrc.c b/drivers/power/domain/meson-secure-pwrc.c index f70f8e02423..1b82b58f3e5 100644 --- a/drivers/power/domain/meson-secure-pwrc.c +++ b/drivers/power/domain/meson-secure-pwrc.c @@ -120,7 +120,7 @@ static struct meson_secure_pwrc_domain_desc a1_pwrc_domains[] = { SEC_PD(RSA), }; -struct power_domain_ops meson_secure_pwrc_ops = { +static const struct power_domain_ops meson_secure_pwrc_ops = { .on = meson_secure_pwrc_on, .off = meson_secure_pwrc_off, .of_xlate = meson_secure_pwrc_of_xlate, diff --git a/drivers/power/domain/mtk-power-domain.c b/drivers/power/domain/mtk-power-domain.c index 2d1ba1855a5..24dd540897d 100644 --- a/drivers/power/domain/mtk-power-domain.c +++ b/drivers/power/domain/mtk-power-domain.c @@ -392,7 +392,7 @@ static const struct udevice_id mtk_power_domain_ids[] = { { /* sentinel */ } }; -struct power_domain_ops mtk_power_domain_ops = { +static const struct power_domain_ops mtk_power_domain_ops = { .off = scpsys_power_off, .on = scpsys_power_on, .request = scpsys_power_request, diff --git a/drivers/power/domain/power-domain-uclass.c b/drivers/power/domain/power-domain-uclass.c index cea68945cbd..b4cda5f6c16 100644 --- a/drivers/power/domain/power-domain-uclass.c +++ b/drivers/power/domain/power-domain-uclass.c @@ -10,6 +10,7 @@ #include <malloc.h> #include <power-domain.h> #include <power-domain-uclass.h> +#include <dm/device_compat.h> #include <dm/device-internal.h> struct power_domain_priv { @@ -187,6 +188,12 @@ static int dev_power_domain_ctrl(struct udevice *dev, bool on) "#power-domain-cells", 0); for (i = 0; i < count; i++) { ret = power_domain_get_by_index(dev, &pd, i); + + if (ret == -ENODEV) { + dev_warn(dev, "power-domain driver not found\n"); + return 0; + } + if (ret) return ret; if (on) diff --git a/drivers/power/domain/qcom-rpmhpd.c b/drivers/power/domain/qcom-rpmhpd.c new file mode 100644 index 00000000000..f51bc9a4bbb --- /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 = (ulong)&stub_desc }, + { .compatible = "qcom,qcs8300-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,qdu1000-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sa8155p-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sa8540p-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sar2130p-rpmhpd", .data = (ulong)&stub_desc}, + { .compatible = "qcom,sc7180-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sc7280-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sc8180x-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sc8280xp-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sdm670-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sdm845-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sdx55-rpmhpd", .data = (ulong)&stub_desc}, + { .compatible = "qcom,sdx65-rpmhpd", .data = (ulong)&stub_desc}, + { .compatible = "qcom,sdx75-rpmhpd", .data = (ulong)&stub_desc}, + { .compatible = "qcom,sm4450-rpmhpd", .data = (ulong)&stub_desc}, + { .compatible = "qcom,sm6350-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm7150-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8150-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8250-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8350-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8450-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8550-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8650-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,sm8750-rpmhpd", .data = (ulong)&stub_desc }, + { .compatible = "qcom,x1e80100-rpmhpd", .data = (ulong)&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, 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 (%ld) 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 (%ld) not supported\n", + pd->id); + 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 = 0; + 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, +}; diff --git a/drivers/power/domain/renesas-r8a78000-power-domain.c b/drivers/power/domain/renesas-r8a78000-power-domain.c new file mode 100644 index 00000000000..57b3b56b2d9 --- /dev/null +++ b/drivers/power/domain/renesas-r8a78000-power-domain.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas R-Car Gen5 MDLC driver + * + * Copyright (C) 2026 Marek Vasut <[email protected]> + */ + +#include <dm.h> +#include <dm/device_compat.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/bitfield.h> +#include <power-domain-uclass.h> +#include <reset-uclass.h> + +#include <scmi_agent.h> +#include <scmi_agent-uclass.h> +#include <scmi_protocols.h> + +#include <dt-bindings/power/r8a78000-power-scmi.h> +#include <dt-bindings/reset/r8a78000-reset-scmi.h> + +#define PKC_PROT_LOCK 0xa5a5a500 +#define PKC_PROT_UNLOCK 0xa5a5a501 + +#define MDLC_MSRESS_STANDBY 0 +#define MDLC_MSRESS_RESET 1 +#define MDLC_MSRESS_STOP 2 +#define MDLC_MSRESS_RUN 3 + +#define MDLC_MSRES00 0x900 +#define MDLC_MSRESS00 0x960 +#define MDLC_PKCPROT1 0xcf4 + +struct gen5_mdlc_priv { +#if IS_ENABLED(CONFIG_SCMI_POWER_DOMAIN) + struct udevice *pd; +#endif +#if IS_ENABLED(CONFIG_RESET_SCMI) + struct udevice *rst; +#endif +#if IS_ENABLED(CONFIG_SCMI_POWER_DOMAIN) || IS_ENABLED(CONFIG_RESET_SCMI) + u32 basever; +#endif +#if !IS_ENABLED(CONFIG_SCMI_POWER_DOMAIN) && !IS_ENABLED(CONFIG_RESET_SCMI) + void __iomem *base; +#endif +}; + +static int gen5_pd_of_xlate(struct power_domain *power_domain, + struct ofnode_phandle_args *args) +{ + /* Perform direct remap until the bindings stabilize. */ + power_domain->id = args->args[0]; + + return 0; +} + +#if IS_ENABLED(CONFIG_SCMI_POWER_DOMAIN) +static int gen5_pd_on(struct power_domain *power_domain) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(power_domain->dev->parent); + struct power_domain_ops *ops = (struct power_domain_ops *)priv->pd->driver->ops; + struct power_domain scmi = { + .dev = priv->pd, + .id = power_domain->id + }; + + return ops->on(&scmi); +} + +static int gen5_pd_off(struct power_domain *power_domain) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(power_domain->dev->parent); + struct power_domain_ops *ops = (struct power_domain_ops *)priv->pd->driver->ops; + struct power_domain scmi = { + .dev = priv->pd, + .id = power_domain->id + }; + + return ops->off(&scmi); +} + +static const struct power_domain_ops pd_gen5_ops = { + .on = gen5_pd_on, + .off = gen5_pd_off, + .of_xlate = gen5_pd_of_xlate, +}; + +static int gen5_pd_probe(struct udevice *dev) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(dev->parent); + struct udevice *agent; + int ret; + + if (!priv->basever) { + ret = uclass_get_device(UCLASS_SCMI_AGENT, 0, &agent); + if (ret) + return ret; + + if (!agent) + return -ENODEV; + + priv->basever = scmi_impl_version(agent); + } + + return uclass_get_device_by_driver(UCLASS_POWER_DOMAIN, + DM_DRIVER_GET(scmi_power_domain), + &priv->pd); +} + +U_BOOT_DRIVER(pd_gen5) = { + .name = "pd_gen5", + .id = UCLASS_POWER_DOMAIN, + .ops = &pd_gen5_ops, + .probe = gen5_pd_probe, + .flags = DM_FLAG_OS_PREPARE | DM_FLAG_VITAL, +}; +#else +static const struct power_domain_ops pd_gen5_ops = { + .of_xlate = gen5_pd_of_xlate, +}; + +U_BOOT_DRIVER(pd_gen5) = { + .name = "pd_gen5", + .id = UCLASS_POWER_DOMAIN, + .ops = &pd_gen5_ops, + .flags = DM_FLAG_OS_PREPARE | DM_FLAG_VITAL, +}; +#endif + +#if IS_ENABLED(CONFIG_RESET_SCMI) +static int gen5_reset_assert(struct reset_ctl *reset_ctl) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(reset_ctl->dev->parent); + struct reset_ops *ops = (struct reset_ops *)priv->rst->driver->ops; + struct reset_ctl scmi = { + .dev = priv->rst, + .id = reset_ctl->id + }; + + return ops->rst_assert(&scmi); +} + +static int gen5_reset_deassert(struct reset_ctl *reset_ctl) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(reset_ctl->dev->parent); + struct reset_ops *ops = (struct reset_ops *)priv->rst->driver->ops; + struct reset_ctl scmi = { + .dev = priv->rst, + .id = reset_ctl->id + }; + + return ops->rst_deassert(&scmi); +} + +struct rst_map_in { + u16 dt_id; /* DT binding clock ID */ + u16 fw_id; /* SCMI firmware clock ID */ +}; + +#define GEN5_SCMI_SDK_4_28 0x010a0000 +#define GEN5_SCMI_SDK_4_29 0x010b0000 +#define GEN5_SCMI_SDK_4_30 0x010c0000 +#define GEN5_SCMI_SDK_4_31 0x010d0000 +#define GEN5_SCMI_SDK_4_32 0x010e0000 + +static const struct rst_map_in gen5_rst_map_dt_sdk_4_28[] = { + { SCP_RESET_DOMAIN_ID_UFS0, 202 }, + { SCP_RESET_DOMAIN_ID_UFS1, 203 }, + { SCP_RESET_DOMAIN_ID_XPCS0, 316 }, + { SCP_RESET_DOMAIN_ID_XPCS1, 317 }, + { SCP_RESET_DOMAIN_ID_XPCS2, 318 }, + { SCP_RESET_DOMAIN_ID_XPCS3, 319 }, + { SCP_RESET_DOMAIN_ID_XPCS4, 320 }, + { SCP_RESET_DOMAIN_ID_XPCS5, 321 }, + { SCP_RESET_DOMAIN_ID_XPCS6, 322 }, + { SCP_RESET_DOMAIN_ID_XPCS7, 323 }, + { SCP_RESET_DOMAIN_ID_MPPHY01, 344 }, + { SCP_RESET_DOMAIN_ID_MPPHY11, 345 }, + { SCP_RESET_DOMAIN_ID_MPPHY21, 346 }, + { SCP_RESET_DOMAIN_ID_MPPHY31, 347 }, + { SCP_RESET_DOMAIN_ID_MPPHY02, 348 }, +}; + +static const struct rst_map_in gen5_rst_map_dt_sdk_4_31[] = { + { SCP_RESET_DOMAIN_ID_UFS0, 198 }, + { SCP_RESET_DOMAIN_ID_UFS1, 199 }, + { SCP_RESET_DOMAIN_ID_XPCS0, 312 }, + { SCP_RESET_DOMAIN_ID_XPCS1, 313 }, + { SCP_RESET_DOMAIN_ID_XPCS2, 314 }, + { SCP_RESET_DOMAIN_ID_XPCS3, 315 }, + { SCP_RESET_DOMAIN_ID_XPCS4, 316 }, + { SCP_RESET_DOMAIN_ID_XPCS5, 317 }, + { SCP_RESET_DOMAIN_ID_XPCS6, 318 }, + { SCP_RESET_DOMAIN_ID_XPCS7, 319 }, + { SCP_RESET_DOMAIN_ID_MPPHY01, 340 }, + { SCP_RESET_DOMAIN_ID_MPPHY11, 341 }, + { SCP_RESET_DOMAIN_ID_MPPHY21, 342 }, + { SCP_RESET_DOMAIN_ID_MPPHY31, 343 }, + { SCP_RESET_DOMAIN_ID_MPPHY02, 344 }, +}; + +static int gen5_reset_of_xlate(struct reset_ctl *reset_ctl, + struct ofnode_phandle_args *args) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(reset_ctl->dev->parent); + const struct rst_map_in *map; + unsigned int map_size; + int i; + + if (args->args_count != 1) { + debug("Invalid args_count: %d\n", args->args_count); + return -EINVAL; + } + + if (priv->basever == GEN5_SCMI_SDK_4_28) { + map = gen5_rst_map_dt_sdk_4_28; + map_size = ARRAY_SIZE(gen5_rst_map_dt_sdk_4_28); + } else if (priv->basever == GEN5_SCMI_SDK_4_31 || + priv->basever == GEN5_SCMI_SDK_4_32) { + map = gen5_rst_map_dt_sdk_4_31; + map_size = ARRAY_SIZE(gen5_rst_map_dt_sdk_4_31); + } else { + printf("Unsupported SCMI base protocol version %x\n", priv->basever); + return -EINVAL; + } + + reset_ctl->id = -1; + for (i = 0; i < map_size; i++) { + if (map[i].dt_id != args->args[0]) + continue; + reset_ctl->id = map[i].fw_id; + break; + } + + return 0; +} + +static const struct reset_ops rst_gen5_ops = { + .rst_assert = gen5_reset_assert, + .rst_deassert = gen5_reset_deassert, + .of_xlate = gen5_reset_of_xlate, +}; + +static int gen5_rst_probe(struct udevice *dev) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(dev->parent); + struct udevice *agent; + int ret = 0; + + if (!priv->basever) { + ret = uclass_get_device(UCLASS_SCMI_AGENT, 0, &agent); + if (ret) + return ret; + + if (!agent) + return -ENODEV; + + priv->basever = scmi_impl_version(agent); + } + + return uclass_get_device_by_driver(UCLASS_RESET, + DM_DRIVER_GET(scmi_reset_domain), + &priv->rst); +} +#else +static int mdlc_wait_for_reset(struct reset_ctl *reset_ctl) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(reset_ctl->dev->parent); + const u32 offset = (reset_ctl->id / 16) * 4; + void __iomem *res = priv->base + MDLC_MSRES00 + offset; + void __iomem *stat = priv->base + MDLC_MSRESS00 + offset; + u32 val; + int ret; + + /* Wait 100ms for reset controller to synchronize. */ + ret = readl_poll_timeout(res, val, val == readl(stat), 100000); + if (ret < 0) + dev_err(reset_ctl->dev, "Reset controller out of sync!\n"); + + return ret; +} + +static void mdlc_rmw_msres(struct reset_ctl *reset_ctl, const int val) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(reset_ctl->dev->parent); + const u32 offset = (reset_ctl->id / 16) * 4; + const u32 mask = 3 << ((reset_ctl->id % 16) * 2); + void __iomem *prot = priv->base + MDLC_PKCPROT1; + void __iomem *res = priv->base + MDLC_MSRES00 + offset; + u32 reg; + + reg = readl(res); + reg &= ~mask; + reg |= field_prep(mask, val); + + writel(PKC_PROT_UNLOCK, prot); + writel(reg, res); + writel(PKC_PROT_LOCK, prot); +} + +static int gen5_reset_toggle(struct reset_ctl *reset_ctl, const u8 step1, + const u8 step2, const u8 step3, const u8 step4) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(reset_ctl->dev->parent); + const u32 offset = (reset_ctl->id / 16) * 4; + const u32 mask = 3 << ((reset_ctl->id % 16) * 2); + void __iomem *stat = priv->base + MDLC_MSRESS00 + offset; + u32 status; + int ret; + + ret = mdlc_wait_for_reset(reset_ctl); + if (ret) + return ret; + + status = field_get(mask, readl(stat)); + if (status == step1) { + mdlc_rmw_msres(reset_ctl, step2); + ret = mdlc_wait_for_reset(reset_ctl); + if (ret) + return ret; + status = field_get(mask, readl(stat)); + } + + if (status == step2 || status == step3) { + mdlc_rmw_msres(reset_ctl, step4); + ret = mdlc_wait_for_reset(reset_ctl); + if (ret) + return ret; + } + + return 0; +} + +static int gen5_reset_assert(struct reset_ctl *reset_ctl) +{ + return gen5_reset_toggle(reset_ctl, + MDLC_MSRESS_STOP, MDLC_MSRESS_STANDBY, + MDLC_MSRESS_RUN, MDLC_MSRESS_RESET); +} + +static int gen5_reset_deassert(struct reset_ctl *reset_ctl) +{ + return gen5_reset_toggle(reset_ctl, + MDLC_MSRESS_STANDBY, MDLC_MSRESS_RESET, + MDLC_MSRESS_STOP, MDLC_MSRESS_RUN); +} + +static int gen5_reset_of_xlate(struct reset_ctl *reset_ctl, + struct ofnode_phandle_args *args) +{ + /* Perform direct remap until the bindings stabilize. */ + reset_ctl->id = args->args[0]; + + return 0; +} + +static const struct reset_ops rst_gen5_ops = { + .rst_assert = gen5_reset_assert, + .rst_deassert = gen5_reset_deassert, + .of_xlate = gen5_reset_of_xlate, +}; + +static int gen5_rst_probe(struct udevice *dev) +{ + struct gen5_mdlc_priv *priv = dev_get_priv(dev->parent); + + priv->base = dev_read_addr_ptr(dev); + if (!priv->base) + return -EINVAL; + + return 0; +} +#endif + +U_BOOT_DRIVER(rst_gen5) = { + .name = "rst_gen5", + .id = UCLASS_RESET, + .ops = &rst_gen5_ops, + .probe = gen5_rst_probe, + .flags = DM_FLAG_OS_PREPARE | DM_FLAG_VITAL, +}; + +int gen5_mdlc_bind(struct udevice *parent) +{ + struct udevice *pdev, *rdev; + struct driver *pdrv, *rdrv; + int ret; + + pdrv = lists_driver_lookup_name("pd_gen5"); + if (!pdrv) + return -ENOENT; + + rdrv = lists_driver_lookup_name("rst_gen5"); + if (!rdrv) + return -ENOENT; + + ret = device_bind_with_driver_data(parent, pdrv, "pd_gen5", 0, + dev_ofnode(parent), &pdev); + if (ret) + return ret; + + ret = device_bind_with_driver_data(parent, rdrv, "rst_gen5", (ulong)pdev, + dev_ofnode(parent), &rdev); + if (ret) + device_unbind(pdev); + + return ret; +} + +static const struct udevice_id r8a78000_mdlc_ids[] = { + { .compatible = "renesas,r8a78000-mdlc", }, + { } +}; + +U_BOOT_DRIVER(mdlc_gen5) = { + .name = "mdlc_gen5", + .id = UCLASS_NOP, + .of_match = r8a78000_mdlc_ids, + .bind = gen5_mdlc_bind, + .priv_auto = sizeof(struct gen5_mdlc_priv), +}; diff --git a/drivers/power/domain/sandbox-power-domain.c b/drivers/power/domain/sandbox-power-domain.c index a8031657638..a30826a3b4d 100644 --- a/drivers/power/domain/sandbox-power-domain.c +++ b/drivers/power/domain/sandbox-power-domain.c @@ -78,7 +78,7 @@ static const struct udevice_id sandbox_power_domain_ids[] = { { } }; -struct power_domain_ops sandbox_power_domain_ops = { +static const struct power_domain_ops sandbox_power_domain_ops = { .request = sandbox_power_domain_request, .rfree = sandbox_power_domain_free, .on = sandbox_power_domain_on, diff --git a/drivers/power/domain/scmi-power-domain.c b/drivers/power/domain/scmi-power-domain.c index e8c0ba8878e..a369fe52f2f 100644 --- a/drivers/power/domain/scmi-power-domain.c +++ b/drivers/power/domain/scmi-power-domain.c @@ -165,21 +165,15 @@ static int scmi_power_domain_probe(struct udevice *dev) for (i = 0; i < priv->num_pwdoms; i++) { ret = scmi_pwd_attrs(dev, i, &priv->prop[i].attributes, &priv->prop[i].name); - if (ret) { + if (ret) dev_err(dev, "failed to get attributes pwd:%d (%d)\n", i, ret); - for (i--; i >= 0; i--) - free(priv->prop[i].name); - free(priv->prop); - - return ret; - } } return 0; } -struct power_domain_ops scmi_power_domain_ops = { +static const struct power_domain_ops scmi_power_domain_ops = { .on = scmi_power_domain_on, .off = scmi_power_domain_off, }; diff --git a/drivers/power/domain/tegra186-power-domain.c b/drivers/power/domain/tegra186-power-domain.c index 334c460c805..3865cd4cf47 100644 --- a/drivers/power/domain/tegra186-power-domain.c +++ b/drivers/power/domain/tegra186-power-domain.c @@ -55,7 +55,7 @@ static int tegra186_power_domain_off(struct power_domain *power_domain) return tegra186_power_domain_common(power_domain, false); } -struct power_domain_ops tegra186_power_domain_ops = { +static const struct power_domain_ops tegra186_power_domain_ops = { .on = tegra186_power_domain_on, .off = tegra186_power_domain_off, }; diff --git a/drivers/power/domain/zynqmp-power-domain.c b/drivers/power/domain/zynqmp-power-domain.c index a54de5c1439..0acfc54e787 100644 --- a/drivers/power/domain/zynqmp-power-domain.c +++ b/drivers/power/domain/zynqmp-power-domain.c @@ -57,7 +57,7 @@ static int zynqmp_power_domain_off(struct power_domain *power_domain) return 0; } -struct power_domain_ops zynqmp_power_domain_ops = { +static const struct power_domain_ops zynqmp_power_domain_ops = { .request = zynqmp_power_domain_request, .rfree = zynqmp_power_domain_free, .on = zynqmp_power_domain_on, |
