From 019f9223d7d7c2fe886e7b3576ae25849e5f1e86 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 30 Oct 2026 13:07:05 +0000 Subject: [PATCH 1/4] PENDING: soundwire: qcom: gate dependent controller hclk with runtime PM On controllers borrowing DATA lanes, the lane provider has no bus of its own and no runtime PM, so the hclk enabled at probe stays on even when the consumer has suspended and the link is idle. Give the provider minimal runtime PM callbacks that only gate its hclk. Mark it active and enable runtime PM before advertising it as ready. Add DL_FLAG_PM_RUNTIME and DL_FLAG_RPM_ACTIVE to the consumer-to-provider device link, so the provider resumes before the consumer accesses its registers and suspends after the consumer stops the bus clock. In remove(), disable runtime PM on the provider and drop hclk if it is still active. Resume the provider around the consumer's final register writes that disable it, then clear the cross-pointers. Restore the provider's MM_SYNC configuration when the consumer resumes the link. Adapted to this tree's lane-provider/consumer implementation. Signed-off-by: Srinivas Kandagatla --- drivers/soundwire/qcom.c | 49 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 8b71513662bb6..437e707c8d670 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -1929,10 +1929,12 @@ static int qcom_swrm_setup_lane_provider(struct qcom_swrm_ctrl *ctrl) /* * Hold a link to the provider: this controller dereferences it on every * bank switch and on every DPn access for a borrowed lane, so it must - * not be unbound first. + * not be unbound first. Keep its hclk active while the consumer is, + * and resume it now before probe programs the provider's registers. */ ret = device_link_add(ctrl->dev, &pdev->dev, - DL_FLAG_AUTOREMOVE_CONSUMER) ? 0 : -EINVAL; + DL_FLAG_AUTOREMOVE_CONSUMER | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE) ? 0 : -EINVAL; put_device(&pdev->dev); if (ret) return dev_err_probe(ctrl->dev, ret, @@ -2093,6 +2095,9 @@ static int qcom_swrm_probe(struct platform_device *pdev) * touch the uninitialised sdw_bus. The consumer starts the * frame generator once MM_SYNC ties the two together. */ + /* runtime PM is driven by the consumer through the device link */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); ctrl->lane_provider_ready = true; dev_dbg(dev, "Qualcomm SoundWire lane provider v%x.%x.%x registered\n", @@ -2220,8 +2225,31 @@ static void qcom_swrm_remove(struct platform_device *pdev) { struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev); - if (!ctrl->is_lane_provider) - sdw_bus_master_delete(&ctrl->bus); + if (ctrl->is_lane_provider) { + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + clk_disable_unprepare(ctrl->hclk); + return; + } + + /* delete bus before dropping lane_provider; in-flight ops deref it */ + sdw_bus_master_delete(&ctrl->bus); + + if (ctrl->lane_provider) { + struct qcom_swrm_ctrl *provider = ctrl->lane_provider; + + /* provider hclk may be gated by runtime PM */ + if (pm_runtime_resume_and_get(provider->dev) >= 0) { + provider->reg_write(provider, + provider->reg_layout[SWRM_REG_INTERRUPT_CPU_EN], 0); + /* disable provider SWRM; releases shared SWR_CLK */ + provider->reg_write(provider, SWRM_COMP_CFG_ADDR, 0); + pm_runtime_put(provider->dev); + } + provider->lane_consumer = NULL; + ctrl->lane_provider = NULL; + } + clk_disable_unprepare(ctrl->hclk); } @@ -2230,6 +2258,10 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev) struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dev); int ret; + /* no bus of its own; the consumer restarts the link */ + if (ctrl->is_lane_provider) + return clk_prepare_enable(ctrl->hclk); + if (ctrl->wake_irq > 0) { if (!irqd_irq_disabled(irq_get_irq_data(ctrl->wake_irq))) disable_irq_nosync(ctrl->wake_irq); @@ -2257,6 +2289,9 @@ static int __maybe_unused swrm_runtime_resume(struct device *dev) reset_control_reset(ctrl->audio_cgcr); qcom_swrm_program_mm_sync(ctrl); + /* provider was resumed first via the device link */ + if (ctrl->lane_provider) + qcom_swrm_program_mm_sync(ctrl->lane_provider); if (ctrl->version == SWRM_VERSION_1_7_0) { ctrl->reg_write(ctrl, SWRM_LINK_MANAGER_EE, ctrl->ee); @@ -2300,6 +2335,12 @@ static int __maybe_unused swrm_runtime_suspend(struct device *dev) struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dev); int ret; + /* the consumer has already stopped the bus clock */ + if (ctrl->is_lane_provider) { + clk_disable_unprepare(ctrl->hclk); + return 0; + } + swrm_wait_for_wr_fifo_done(ctrl); if (!ctrl->clock_stop_not_supported) { /* Mask bus clash interrupt */ From 8e5fe6b0d938c91b7bba5095790c4074b6305f17 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Fri, 30 Oct 2026 13:24:56 +0000 Subject: [PATCH 2/4] PENDING: ASoC: codecs: lpass-macro: register output clocks before runtime PM The rx, tx, wsa and va macros register their output clock (SWR clock gate or fsgen) after enabling runtime PM. The clk core then marks each macro as a runtime PM managed provider, and clk_summary resumes it through clk_pm_runtime_get_all() before walking the tree. Resuming the macro enables its macro, dcodec, mclk, npl and fsgen clocks, so the summary always shows them enabled even when audio is idle. The clk core doesn't need to resume these providers. The gate prepare/unprepare callbacks already take and drop runtime PM themselves. is_enabled reads a cached, non-volatile register, so it never touches the hardware. Register the clk_hw before devm_pm_runtime_enable(). Keep adding the OF provider at the end of probe, so a consumer can't prepare the gate before runtime PM is enabled. Signed-off-by: Srinivas Kandagatla --- sound/soc/codecs/lpass-rx-macro.c | 18 +++++++++++------- sound/soc/codecs/lpass-tx-macro.c | 18 +++++++++++------- sound/soc/codecs/lpass-va-macro.c | 18 +++++++++++------- sound/soc/codecs/lpass-wsa-macro.c | 18 +++++++++++------- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/sound/soc/codecs/lpass-rx-macro.c b/sound/soc/codecs/lpass-rx-macro.c index b920868abef14..042bf57bbde20 100644 --- a/sound/soc/codecs/lpass-rx-macro.c +++ b/sound/soc/codecs/lpass-rx-macro.c @@ -3797,7 +3797,6 @@ static int rx_macro_register_mclk_output(struct rx_macro *rx) const char *clk_name = "lpass-rx-mclk"; struct clk_hw *hw; struct clk_init_data init; - int ret; if (rx->npl) parent_clk_name = __clk_get_name(rx->npl); @@ -3811,11 +3810,7 @@ static int rx_macro_register_mclk_output(struct rx_macro *rx) init.num_parents = 1; rx->hw.init = &init; hw = &rx->hw; - ret = devm_clk_hw_register(rx->dev, hw); - if (ret) - return ret; - - return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); + return devm_clk_hw_register(dev, hw); } static const struct snd_soc_component_driver rx_macro_component_drv = { @@ -3946,6 +3941,15 @@ static int rx_macro_probe(struct platform_device *pdev) if (ret) return ret; + /* + * Register before enabling runtime PM so the clk core doesn't wake + * the macro on every clk_summary read. The gate takes runtime PM in + * prepare and is_enabled reads a cached register. + */ + ret = rx_macro_register_mclk_output(rx); + if (ret) + return ret; + pm_runtime_set_autosuspend_delay(dev, 100); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); @@ -3973,7 +3977,7 @@ static int rx_macro_probe(struct platform_device *pdev) if (ret) goto err_rpm_put; - ret = rx_macro_register_mclk_output(rx); + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &rx->hw); if (ret) goto err_rpm_put; diff --git a/sound/soc/codecs/lpass-tx-macro.c b/sound/soc/codecs/lpass-tx-macro.c index b9c44c258e275..add51f771e4c5 100644 --- a/sound/soc/codecs/lpass-tx-macro.c +++ b/sound/soc/codecs/lpass-tx-macro.c @@ -2685,7 +2685,6 @@ static int tx_macro_register_mclk_output(struct tx_macro *tx) const char *clk_name = "lpass-tx-mclk"; struct clk_hw *hw; struct clk_init_data init; - int ret; if (tx->npl) parent_clk_name = __clk_get_name(tx->npl); @@ -2699,11 +2698,7 @@ static int tx_macro_register_mclk_output(struct tx_macro *tx) init.num_parents = 1; tx->hw.init = &init; hw = &tx->hw; - ret = devm_clk_hw_register(dev, hw); - if (ret) - return ret; - - return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); + return devm_clk_hw_register(dev, hw); } static const struct snd_soc_component_driver tx_macro_component_drv = { @@ -2801,6 +2796,15 @@ static int tx_macro_probe(struct platform_device *pdev) if (ret) goto err; + /* + * Register before enabling runtime PM so the clk core doesn't wake + * the macro on every clk_summary read. The gate takes runtime PM in + * prepare and is_enabled reads a cached register. + */ + ret = tx_macro_register_mclk_output(tx); + if (ret) + goto err; + pm_runtime_set_autosuspend_delay(dev, 100); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); @@ -2830,7 +2834,7 @@ static int tx_macro_probe(struct platform_device *pdev) if (ret) goto err_rpm_put; - ret = tx_macro_register_mclk_output(tx); + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &tx->hw); if (ret) goto err_rpm_put; diff --git a/sound/soc/codecs/lpass-va-macro.c b/sound/soc/codecs/lpass-va-macro.c index 6c9214aa167fa..74f956c736867 100644 --- a/sound/soc/codecs/lpass-va-macro.c +++ b/sound/soc/codecs/lpass-va-macro.c @@ -1759,7 +1759,6 @@ static int va_macro_register_fsgen_output(struct va_macro *va) const char *parent_clk_name; const char *clk_name = "fsgen"; struct clk_init_data init; - int ret; if (va->has_npl_clk) parent = va->npl; @@ -1774,11 +1773,7 @@ static int va_macro_register_fsgen_output(struct va_macro *va) init.parent_names = &parent_clk_name; init.num_parents = 1; va->hw.init = &init; - ret = devm_clk_hw_register(va->dev, &va->hw); - if (ret) - return ret; - - return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &va->hw); + return devm_clk_hw_register(dev, &va->hw); } static int va_macro_validate_dmic_sample_rate(u32 dmic_sample_rate, @@ -1977,6 +1972,15 @@ static int va_macro_probe(struct platform_device *pdev) goto err; } + /* + * Register before enabling runtime PM so the clk core doesn't wake + * the macro on every clk_summary read. The gate takes runtime PM in + * prepare and is_enabled reads a cached register. + */ + ret = va_macro_register_fsgen_output(va); + if (ret) + goto err; + pm_runtime_set_autosuspend_delay(dev, 100); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); @@ -2031,7 +2035,7 @@ static int va_macro_probe(struct platform_device *pdev) if (ret) goto err_rpm_put; - ret = va_macro_register_fsgen_output(va); + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &va->hw); if (ret) goto err_rpm_put; diff --git a/sound/soc/codecs/lpass-wsa-macro.c b/sound/soc/codecs/lpass-wsa-macro.c index 8e1f502d06e2d..071c337bad3b6 100644 --- a/sound/soc/codecs/lpass-wsa-macro.c +++ b/sound/soc/codecs/lpass-wsa-macro.c @@ -2691,7 +2691,6 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) const char *parent_clk_name; struct clk_hw *hw; struct clk_init_data init; - int ret; if (wsa->npl) parent_clk_name = __clk_get_name(wsa->npl); @@ -2707,11 +2706,7 @@ static int wsa_macro_register_mclk_output(struct wsa_macro *wsa) init.num_parents = 1; wsa->hw.init = &init; hw = &wsa->hw; - ret = devm_clk_hw_register(wsa->dev, hw); - if (ret) - return ret; - - return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw); + return devm_clk_hw_register(dev, hw); } static const struct snd_soc_component_driver wsa_macro_component_drv = { @@ -2832,6 +2827,15 @@ static int wsa_macro_probe(struct platform_device *pdev) if (ret) return ret; + /* + * Register before enabling runtime PM so the clk core doesn't wake + * the macro on every clk_summary read. The gate takes runtime PM in + * prepare and is_enabled reads a cached register. + */ + ret = wsa_macro_register_mclk_output(wsa); + if (ret) + return ret; + pm_runtime_set_autosuspend_delay(dev, 100); pm_runtime_use_autosuspend(dev); ret = devm_pm_runtime_enable(dev); @@ -2859,7 +2863,7 @@ static int wsa_macro_probe(struct platform_device *pdev) if (ret) goto err_rpm_put; - ret = wsa_macro_register_mclk_output(wsa); + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &wsa->hw); if (ret) goto err_rpm_put; From eacce5b9446c396e6bb9757107e06525edcd8777 Mon Sep 17 00:00:00 2001 From: Gopi Chandra Sekhar Reddy Kanala Date: Mon, 2 Nov 2026 13:27:29 +0530 Subject: [PATCH 3/4] PENDING: soundwire: qcom: force runtime suspend across system sleep The controller only implements runtime PM callbacks and relies on runtime suspend to stop the bus clock and release hclk. That is not enough across system suspend: from the prepare phase until complete the PM core holds a runtime PM usage count on every device, so a controller that is runtime active at that point, or that one of its peripherals resumes from its own system suspend callback, stays active until the system resumes. This happens on Glymur CRD with the WCD9378 SDCA codec. The SDCA class function's system suspend callback resumes the function to quiesce its interrupts, which resumes the codec, the SoundWire bus master and, through runtime PM device links, the lane provider controller, the RX/TX macros and the LPASS LPI pin controller. Nothing suspends them again, so their LPASS clock votes are held for the whole suspend and CX and DDR never collapse. Use pm_runtime_force_suspend() and pm_runtime_force_resume() as the system sleep callbacks, so the runtime suspend path (clock stop, hclk off, wake IRQ armed) always runs at system suspend and is undone on resume. Peripherals are children of the bus and suspend before it. The lane provider is a device link supplier of its consumer, so it suspends after the consumer has stopped the bus and resumes before the consumer restarts it, which is the order the runtime callbacks already expect. Signed-off-by: Gopi Chandra Sekhar Reddy Kanala --- drivers/soundwire/qcom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 437e707c8d670..3fee3f38a44e8 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -2378,6 +2378,7 @@ static int __maybe_unused swrm_runtime_suspend(struct device *dev) } static const struct dev_pm_ops swrm_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) SET_RUNTIME_PM_OPS(swrm_runtime_suspend, swrm_runtime_resume, NULL) }; From a43156f9948eccbf696b7bfbedaa84e4fec3d14c Mon Sep 17 00:00:00 2001 From: Gopi Chandra Sekhar Reddy Kanala Date: Mon, 2 Nov 2026 13:27:29 +0530 Subject: [PATCH 4/4] PENDING: pinctrl: qcom: sm8650-lpass-lpi: Force runtime suspend on system sleep The LPASS LPI pin controller votes for the LPASS HW_MACRO and HW_DCODEC clocks through pm_clk and only has runtime PM callbacks. Its consumers, such as the SoundWire controllers, keep it runtime active through their device links. If a consumer is active when system suspend starts, or is resumed during it, the usage count the PM core holds from the prepare phase keeps the pin controller from runtime suspending, and both votes are held for the whole suspend. On Glymur CRD this keeps CX and DDR from collapsing. Use pm_runtime_force_suspend() and pm_runtime_force_resume() as the system sleep callbacks so the clocks are always released at system suspend. Consumers are suspended before their suppliers, so the pin controller is the last of them to go down and the first to come back. Signed-off-by: Gopi Chandra Sekhar Reddy Kanala --- drivers/pinctrl/qcom/pinctrl-sm8650-lpass-lpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pinctrl/qcom/pinctrl-sm8650-lpass-lpi.c b/drivers/pinctrl/qcom/pinctrl-sm8650-lpass-lpi.c index bc7889c993d05..e33d4360c094f 100644 --- a/drivers/pinctrl/qcom/pinctrl-sm8650-lpass-lpi.c +++ b/drivers/pinctrl/qcom/pinctrl-sm8650-lpass-lpi.c @@ -220,6 +220,7 @@ static const struct of_device_id lpi_pinctrl_of_match[] = { MODULE_DEVICE_TABLE(of, lpi_pinctrl_of_match); static const struct dev_pm_ops lpi_pinctrl_pm_ops = { + SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) RUNTIME_PM_OPS(pm_clk_suspend, pm_clk_resume, NULL) };