From: Anjana Hari <anjana.hari@oss.qualcomm.com>
To: Adrian Hunter <adrian.hunter@intel.com>,
Ulf Hansson <ulfh@kernel.org>, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>
Cc: linux-arm-msm@vger.kernel.org, linux-mmc@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
Shazad Hussain <shazad.hussain@oss.qualcomm.com>,
Manivannan Sadhasivam <manivannan.sadhasivam@oss.qualcomm.com>,
Nitin Rawat <nitin.rawat@oss.qualcomm.com>,
Ram Gupta <ram.gupta@oss.qualcomm.com>
Subject: Re: [PATCH 2/2] mmc: sdhci-msm: Add support for firmware-managed resource platforms
Date: Fri, 6 Nov 2026 16:08:04 +0530 [thread overview]
Message-ID: <67f05266-77ea-4018-bb0a-e877e288f9da@oss.qualcomm.com> (raw)
In-Reply-To: <afa7f2f6-17f3-468a-a7c8-4b1b7840bde8@intel.com>
On 11/4/2026 5:32 PM, Adrian Hunter wrote:
> On 02/11/2026 16:03, Anjana Hari wrote:
>> On certain Qualcomm automotive SoCs such as Nord, platform resources
>> including clocks, interconnect bandwidth, resets, and regulators are
>> owned and managed by firmware rather than directly by Linux. The
>> firmware abstracts these resources behind logical power domains and
>> exposes control through the SCMI power protocol.
>>
>> Introduce a firmware-managed variant of the sdhci-msm driver that
>> operates through this abstraction instead of directly programming GCC
>> clocks and PMIC regulators.
>>
>> The firmware-managed model uses two logical power domains, attached at
>> probe time via dev_pm_domain_attach_list():
>>
>> - "power" domain: governs SDCC rail power state. It is activated at
>> probe and released at remove via pm_runtime_resume_and_get() and
>> pm_runtime_put_sync() on the domain device. These calls are
>> translated by the SCMI power protocol into firmware power-state
>> requests.
>>
>> - "perf" domain: the SCMI perf domain driver populates OPPs at probe
>> time by querying the firmware-reported performance levels via
>> perf_ops->device_opps_add(). Clock rate selection uses
>> dev_pm_opp_find_freq_floor() to locate the nearest OPP at or below
>> the requested frequency, then dev_pm_opp_set_opp() to apply it,
>> rather than directly programming the GCC clock tree.
>>
>> A new variant sdhci_msm_fw_v5_var is introduced with the compatible
>> string "qcom,nord-sdhci". Detection is automatic at probe: a non-NULL
>> pd_data.pd_names in the variant descriptor marks the platform as
>> firmware-managed, causing probe to select firmware-specific platform
>> data (sdhci_msm_fw_pdata) and ops (sdhci_msm_fw_ops).
>>
>> The firmware-managed ops provide:
>> - sdhci_msm_fw_get_max_clock: queries the perf domain OPP table
>> using dev_pm_opp_find_freq_floor() at ULONG_MAX to discover the
>> maximum supported frequency, replacing the direct GCC readout.
>> - sdhci_msm_fw_set_clock: routes clock rate programming through the
>> perf domain OPP path, reusing the existing bus-mode multiplier
>> logic to derive the target rate.
>>
>> Suspend and resume sequences bypass local clock and regulator management
>> for firmware-managed platforms, as firmware handles those transitions
>> autonomously in response to SCMI power-state transitions from the
>> runtime PM framework.
>>
>> Co-developed by: Shazad Hussain <shazad.hussain@oss.qualcomm.com>
>> Signed-off-by: Shazad Hussain <shazad.hussain@oss.qualcomm.com>
>> Signed-off-by: Anjana Hari <anjana.hari@oss.qualcomm.com>
>
> Doesn't compile:
>
> drivers/mmc/host/sdhci-msm.c: In function ‘sdhci_msm_probe’:
> drivers/mmc/host/sdhci-msm.c:3105:1: error: label ‘fw_detach’ defined but not used [-Werror=unused-label]
> 3105 | fw_detach:
> | ^~~~~~~~~
> cc1: all warnings being treated as errors
>
> Please check other Sashiko issues also:
>
> https://sashiko.dev/#/patchset/20261102-sdcc_scmi-v1-0-244dbd893051%40oss.qualcomm.com?part=2
>
Acked, will address in the upcoming v2 commit.>> ---
>> drivers/mmc/host/sdhci-msm.c | 213 ++++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 208 insertions(+), 5 deletions(-)
>>
>> diff --git a/drivers/mmc/host/sdhci-msm.c b/drivers/mmc/host/sdhci-msm.c
>> index aecd425bf095..da52a45203a5 100644
>> --- a/drivers/mmc/host/sdhci-msm.c
>> +++ b/drivers/mmc/host/sdhci-msm.c
>> @@ -8,8 +8,9 @@
>> #include <linux/module.h>
>> #include <linux/delay.h>
>> #include <linux/mmc/mmc.h>
>> -#include <linux/pm_runtime.h>
>> +#include <linux/pm_domain.h>
>> #include <linux/pm_opp.h>
>> +#include <linux/pm_runtime.h>
>> #include <linux/slab.h>
>> #include <linux/iopoll.h>
>> #include <linux/regulator/consumer.h>
>> @@ -168,6 +169,9 @@
>> #define ICE_HCI_PARAM_CCI GENMASK(7, 0)
>> #define ICE_HCI_PARAM_CE GENMASK(8, 8)
>>
>> +#define DOMAIN_IDX_POWER 0
>> +#define DOMAIN_IDX_PERF 1
>> +
>> struct sdhci_msm_offset {
>> u32 core_hc_mode;
>> u32 core_mci_data_cnt;
>> @@ -275,6 +279,11 @@ struct sdhci_msm_variant_info {
>> bool restore_dll_config;
>> const struct sdhci_msm_variant_ops *var_ops;
>> const struct sdhci_msm_offset *offset;
>> + /*
>> + * pd_names != NULL marks this variant as firmware managed;
>> + * see fw_managed detection in sdhci_msm_probe().
>> + */
>> + const struct dev_pm_domain_attach_data pd_data;
>> };
>>
>> struct sdhci_msm_host {
>> @@ -285,6 +294,7 @@ struct sdhci_msm_host {
>> struct clk *xo_clk; /* TCXO clk needed for FLL feature of cm_dll*/
>> /* core, iface, cal and sleep clocks */
>> struct clk_bulk_data bulk_clks[4];
>> + struct dev_pm_domain_list *pd_list;
>> #ifdef CONFIG_MMC_CRYPTO
>> struct qcom_ice *ice;
>> #endif
>> @@ -302,6 +312,7 @@ struct sdhci_msm_host {
>> u32 caps_0;
>> bool mci_removed;
>> bool restore_dll_config;
>> + bool fw_managed;
>> const struct sdhci_msm_variant_ops *var_ops;
>> const struct sdhci_msm_offset *offset;
>> bool use_cdr;
>> @@ -414,6 +425,45 @@ static void msm_set_clock_rate_for_bus_mode(struct sdhci_host *host,
>> mmc_hostname(host->mmc), achieved_rate, timing);
>> }
>>
>> +static int msm_fw_set_clock_rate_for_bus_mode(struct sdhci_host *host,
>> + unsigned int clock)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
>> + struct device *dev = mmc_dev(host->mmc);
>> + struct dev_pm_opp *opp;
>> + struct device *perf_dev;
>> + struct mmc_ios ios = host->mmc->ios;
>> + unsigned long clock_long = (unsigned long)clock;
>> + unsigned long desired_rate;
>> + unsigned int mult;
>> + int ret;
>> +
>> + perf_dev = msm_host->pd_list->pd_devs[DOMAIN_IDX_PERF];
>> +
>> + mult = msm_get_clock_mult_for_bus_mode(host, clock, ios.timing);
>> + desired_rate = clock_long * mult;
>> +
>> + /* Find the nearest OPP at or below the requested frequency. */
>> + opp = dev_pm_opp_find_freq_floor(perf_dev, &desired_rate);
>> + if (IS_ERR(opp)) {
>> + dev_err(dev, "Failed to find OPP for freq %lu: %pe\n", clock_long, opp);
>> + return PTR_ERR(opp);
>> + }
>> +
>> + ret = dev_pm_opp_set_opp(perf_dev, opp);
>> + dev_pm_opp_put(opp);
>> + if (ret) {
>> + dev_err(dev, "Failed to set OPP for freq %lu: %d\n", clock_long, ret);
>> + return ret;
>> + }
>> +
>> + /* desired_rate was rounded down by find_freq_floor to the actual OPP rate. */
>> + host->mmc->actual_clock = mult ? desired_rate / mult : desired_rate;
>> + msm_host->clk_rate = desired_rate;
>> + return 0;
>> +}
>> +
>> /* Platform specific tuning */
>> static inline int msm_dll_poll_ck_out_en(struct sdhci_host *host, u8 poll)
>> {
>> @@ -1253,7 +1303,10 @@ static int sdhci_msm_execute_tuning(struct mmc_host *mmc, u32 opcode)
>> */
>> if (host->flags & SDHCI_HS400_TUNING) {
>> sdhci_msm_hc_select_mode(host);
>> - msm_set_clock_rate_for_bus_mode(host, ios.clock, ios.timing);
>> + if (msm_host->fw_managed)
>> + msm_fw_set_clock_rate_for_bus_mode(host, ios.clock);
>> + else
>> + msm_set_clock_rate_for_bus_mode(host, ios.clock, ios.timing);
>> host->flags &= ~SDHCI_HS400_TUNING;
>> }
>>
>> @@ -1847,6 +1900,28 @@ static unsigned int sdhci_msm_get_min_clock(struct sdhci_host *host)
>> return SDHCI_MSM_MIN_CLOCK;
>> }
>>
>> +static unsigned int sdhci_msm_fw_get_max_clock(struct sdhci_host *host)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
>> + unsigned long max_freq = ULONG_MAX;
>> + struct dev_pm_opp *opp;
>> + struct device *perf_dev;
>> + struct device *dev = mmc_dev(host->mmc);
>> +
>> + perf_dev = msm_host->pd_list->pd_devs[DOMAIN_IDX_PERF];
>> +
>> + opp = dev_pm_opp_find_freq_floor(perf_dev, &max_freq);
>> + if (IS_ERR(opp)) {
>> + dev_err(dev, "Failed to find opp for freq %lu\n", max_freq);
>> + return SDHCI_MSM_MIN_CLOCK;
>> + }
>> +
>> + dev_pm_opp_put(opp);
>> +
>> + return max_freq;
>> +}
>> +
>> /*
>> * __sdhci_msm_set_clock - sdhci_msm clock control.
>> *
>> @@ -1892,6 +1967,40 @@ static void sdhci_msm_set_clock(struct sdhci_host *host, unsigned int clock)
>> __sdhci_msm_set_clock(host, clock);
>> }
>>
>> +static void sdhci_msm_fw_set_clock(struct sdhci_host *host, unsigned int clock)
>> +{
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
>> +
>> + if (!clock) {
>> + host->mmc->actual_clock = 0;
>> + msm_host->clk_rate = 0;
>> + goto out;
>> + }
>> +
>> + sdhci_msm_hc_select_mode(host);
>> +
>> + if (msm_fw_set_clock_rate_for_bus_mode(host, clock))
>> + return;
>> +out:
>> + __sdhci_msm_set_clock(host, clock);
>> +}
>> +
>> +static int sdhci_msm_fw_power_state(struct device *dev, bool power_on)
>> +{
>> + struct sdhci_host *host = dev_get_drvdata(dev);
>> + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
>> + struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
>> + struct device *pwr_dev;
>> +
>> + pwr_dev = msm_host->pd_list->pd_devs[DOMAIN_IDX_POWER];
>> +
>> + if (power_on)
>> + return pm_runtime_resume_and_get(pwr_dev);
>> + else
>> + return pm_runtime_put_sync(pwr_dev);
>> +}
>> +
>> /*****************************************************************************\
>> * *
>> * Inline Crypto Engine (ICE) support *
>> @@ -2550,6 +2659,17 @@ static const struct sdhci_msm_variant_info sdhci_msm_v5_var = {
>> .offset = &sdhci_msm_v5_offset,
>> };
>>
>> +static const struct sdhci_msm_variant_info sdhci_msm_fw_v5_var = {
>> + .mci_removed = true,
>> + .var_ops = &v5_var_ops,
>> + .offset = &sdhci_msm_v5_offset,
>> + .pd_data = {
>> + .pd_flags = PD_FLAG_DEV_LINK_ON,
>> + .pd_names = (const char * []) { "power", "perf" },
>> + .num_pd_names = 2,
>> + },
>> +};
>> +
>> static const struct sdhci_msm_variant_info sdm845_sdhci_var = {
>> .mci_removed = true,
>> .restore_dll_config = true,
>> @@ -2566,6 +2686,7 @@ static const struct of_device_id sdhci_msm_dt_match[] = {
>> {.compatible = "qcom,sdhci-msm-v5", .data = &sdhci_msm_v5_var},
>> {.compatible = "qcom,sdm670-sdhci", .data = &sdm845_sdhci_var},
>> {.compatible = "qcom,sdm845-sdhci", .data = &sdm845_sdhci_var},
>> + {.compatible = "qcom,nord-sdhci", .data = &sdhci_msm_fw_v5_var},
>> {.compatible = "qcom,sc7180-sdhci", .data = &sdm845_sdhci_var},
>> {},
>> };
>> @@ -2587,6 +2708,21 @@ static const struct sdhci_ops sdhci_msm_ops = {
>> .set_timeout = sdhci_msm_set_timeout,
>> };
>>
>> +static const struct sdhci_ops sdhci_msm_fw_ops = {
>> + .reset = sdhci_and_cqhci_reset,
>> + .set_clock = sdhci_msm_fw_set_clock,
>> + .get_min_clock = sdhci_msm_get_min_clock,
>> + .get_max_clock = sdhci_msm_fw_get_max_clock,
>> + .set_bus_width = sdhci_set_bus_width,
>> + .set_uhs_signaling = sdhci_msm_set_uhs_signaling,
>> + .write_w = sdhci_msm_writew,
>> + .write_b = sdhci_msm_writeb,
>> + .irq = sdhci_msm_cqe_irq,
>> + .dump_vendor_regs = sdhci_msm_dump_vendor_regs,
>> + .set_power = sdhci_set_power_noreg,
>> + .set_timeout = sdhci_msm_set_timeout,
>> +};
>> +
>> static const struct sdhci_pltfm_data sdhci_msm_pdata = {
>> .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION |
>> SDHCI_QUIRK_SINGLE_POWER_WRITE |
>> @@ -2597,6 +2733,16 @@ static const struct sdhci_pltfm_data sdhci_msm_pdata = {
>> .ops = &sdhci_msm_ops,
>> };
>>
>> +static const struct sdhci_pltfm_data sdhci_msm_fw_pdata = {
>> + .quirks = SDHCI_QUIRK_BROKEN_CARD_DETECTION |
>> + SDHCI_QUIRK_SINGLE_POWER_WRITE |
>> + SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
>> + SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12,
>> +
>> + .quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
>> + .ops = &sdhci_msm_fw_ops,
>> +};
>> +
>> static inline void sdhci_msm_get_of_property(struct platform_device *pdev,
>> struct sdhci_host *host)
>> {
>> @@ -2665,8 +2811,21 @@ static int sdhci_msm_probe(struct platform_device *pdev)
>> const struct sdhci_msm_offset *msm_offset;
>> const struct sdhci_msm_variant_info *var_info;
>> struct device_node *node = pdev->dev.of_node;
>> + bool fw_managed;
>>
>> - host = sdhci_pltfm_init(pdev, &sdhci_msm_pdata, sizeof(*msm_host));
>> + var_info = of_device_get_match_data(&pdev->dev);
>> + if (!var_info)
>> + return -ENODEV;
>> + fw_managed = !!var_info->pd_data.pd_names;
>> +
>> + /*
>> + * Based on variant info, differentiate platform initialization
>> + * for local and remote managed resource platforms.
>> + */
>> + if (fw_managed)
>> + host = sdhci_pltfm_init(pdev, &sdhci_msm_fw_pdata, sizeof(*msm_host));
>> + else
>> + host = sdhci_pltfm_init(pdev, &sdhci_msm_pdata, sizeof(*msm_host));
>> if (IS_ERR(host))
>> return PTR_ERR(host);
>>
>> @@ -2684,12 +2843,11 @@ static int sdhci_msm_probe(struct platform_device *pdev)
>> * Based on the compatible string, load the required msm host info from
>> * the data associated with the version info.
>> */
>> - var_info = of_device_get_match_data(&pdev->dev);
>> -
>> msm_host->mci_removed = var_info->mci_removed;
>> msm_host->restore_dll_config = var_info->restore_dll_config;
>> msm_host->var_ops = var_info->var_ops;
>> msm_host->offset = var_info->offset;
>> + msm_host->fw_managed = fw_managed;
>>
>> msm_offset = msm_host->offset;
>>
>> @@ -2698,6 +2856,32 @@ static int sdhci_msm_probe(struct platform_device *pdev)
>>
>> msm_host->saved_tuning_phase = INVALID_TUNING_PHASE;
>>
>> + if (msm_host->fw_managed) {
>> + ret = dev_pm_domain_attach_list(&pdev->dev, &var_info->pd_data,
>> + &msm_host->pd_list);
>> + if (ret < 0)
>> + return dev_err_probe(&pdev->dev, ret,
>> + "Failed to attach power domains\n");
>> +
>> + if (ret != var_info->pd_data.num_pd_names)
>> + return dev_err_probe(&pdev->dev, -ENODEV,
>> + "Expected %d power domains, got %d\n",
>> + var_info->pd_data.num_pd_names, ret);
>> +
>> + ret = sdhci_msm_fw_power_state(&pdev->dev, true);
>> + if (ret) {
>> + dev_pm_domain_detach_list(msm_host->pd_list);
>> + return dev_err_probe(&pdev->dev, ret,
>> + "Failed to power on domain\n");
>> + }
>> +
>> + /*
>> + * Skip initializing clocks, regulators and reset in kernel
>> + * for firmware managed platforms.
>> + */
>> + goto skip_resource_init;
>> + }
>> +
>> ret = sdhci_msm_gcc_reset(&pdev->dev, host);
>> if (ret)
>> return ret;
>> @@ -2786,6 +2970,7 @@ static int sdhci_msm_probe(struct platform_device *pdev)
>> }
>> }
>>
>> +skip_resource_init:
>> /* Reset the vendor spec register to power on reset state */
>> writel_relaxed(CORE_VENDOR_SPEC_POR_VAL,
>> host->ioaddr + msm_offset->core_vendor_spec);
>> @@ -2917,6 +3102,12 @@ static int sdhci_msm_probe(struct platform_device *pdev)
>> bus_clk_disable:
>> if (!IS_ERR(msm_host->bus_clk))
>> clk_disable_unprepare(msm_host->bus_clk);
>> +fw_detach:
>> + if (msm_host->fw_managed) {
>> + sdhci_msm_fw_power_state(&pdev->dev, false);
>> + dev_pm_domain_detach_list(msm_host->pd_list);
>> + msm_host->pd_list = NULL;
>> + }
>> return ret;
>> }
>>
>> @@ -2930,6 +3121,12 @@ static void sdhci_msm_remove(struct platform_device *pdev)
>>
>> sdhci_remove_host(host, dead);
>>
>> + if (msm_host->fw_managed) {
>> + sdhci_msm_fw_power_state(&pdev->dev, false);
>> + dev_pm_domain_detach_list(msm_host->pd_list);
>> + msm_host->pd_list = NULL;
>> + }
>> +
>> pm_runtime_get_sync(&pdev->dev);
>> pm_runtime_disable(&pdev->dev);
>> pm_runtime_put_noidle(&pdev->dev);
>> @@ -2947,6 +3144,9 @@ static int sdhci_msm_runtime_suspend(struct device *dev)
>> struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
>> unsigned long flags;
>>
>> + if (msm_host->fw_managed)
>> + return sdhci_msm_fw_power_state(dev, false);
>> +
>> spin_lock_irqsave(&host->lock, flags);
>> host->runtime_suspended = true;
>> spin_unlock_irqrestore(&host->lock, flags);
>> @@ -2967,6 +3167,9 @@ static int sdhci_msm_runtime_resume(struct device *dev)
>> unsigned long flags;
>> int ret;
>>
>> + if (msm_host->fw_managed)
>> + return sdhci_msm_fw_power_state(dev, true);
>> +
>> ret = clk_bulk_prepare_enable(ARRAY_SIZE(msm_host->bulk_clks),
>> msm_host->bulk_clks);
>> if (ret)
>>
>
prev parent reply other threads:[~2026-11-06 10:38 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 14:03 [PATCH 0/2] mmc: sdhci-msm: Add firmware-managed resource support for Nord SDCC Anjana Hari
2026-11-02 14:03 ` [PATCH 1/2] dt-bindings: mmc: sdhci-msm: Document bindings " Anjana Hari
2026-11-04 13:58 ` Krzysztof Kozlowski
2026-11-06 10:36 ` Anjana Hari
2026-11-02 14:03 ` [PATCH 2/2] mmc: sdhci-msm: Add support for firmware-managed resource platforms Anjana Hari
2026-11-04 12:02 ` Adrian Hunter
2026-11-06 10:38 ` Anjana Hari [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=67f05266-77ea-4018-bb0a-e877e288f9da@oss.qualcomm.com \
--to=anjana.hari@oss.qualcomm.com \
--cc=adrian.hunter@intel.com \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mmc@vger.kernel.org \
--cc=manivannan.sadhasivam@oss.qualcomm.com \
--cc=nitin.rawat@oss.qualcomm.com \
--cc=ram.gupta@oss.qualcomm.com \
--cc=robh@kernel.org \
--cc=shazad.hussain@oss.qualcomm.com \
--cc=ulfh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®