From 49827d4d2eed097288e7de6a380de1de647362c3 Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Mon, 13 Jul 2026 23:04:34 +0530 Subject: [PATCH 1/3] FROMLIST scsi: ufs: ufs-qcom: Enable only lane clocks in lane clock APIs ufs_qcom_enable_lane_clks() and ufs_qcom_disable_lane_clks() currently use clk_bulk_prepare_enable()/clk_bulk_disable_unprepare() on the entire host->clks array obtained from devm_clk_bulk_get_all(). This array contains all device clocks, not just lane symbol clocks. Since the UFS core framework already manages the non-lane clocks via the setup_clocks callback, the bulk enable/disable in the lane clock APIs resulted in duplicate reference count increments on those shared clocks. The extra enable counts were never balanced by a corresponding disable from the framework's clock gating path, preventing the clock reference counts from reaching zero and ultimately blocking CXO shutdown during low-power states. Fix this by restricting the lane clock APIs to only prepare/enable and disable/unprepare the three lane symbol clocks (tx_lane0_sync_clk, rx_lane0_sync_clk, rx_lane1_sync_clk), leaving the handling of all other clocks to the UFS core framewore. Link: https://lore.kernel.org/linux-scsi/20260713173434.883386-1-nitin.rawat@oss.qualcomm.com/ Signed-off-by: Nitin Rawat Signed-off-by: Giri Prasad Goriparthi --- drivers/ufs/host/ufs-qcom.c | 41 ++++++++++++++++++++++++++++++++----- drivers/ufs/host/ufs-qcom.h | 3 +++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index 1e347092a7d8d..ff61f930c7ac6 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -361,7 +361,9 @@ static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host) if (!host->is_lane_clks_enabled) return; - clk_bulk_disable_unprepare(host->num_clks, host->clks); + clk_disable_unprepare(host->rx_lane1_sync_clk); + clk_disable_unprepare(host->rx_lane0_sync_clk); + clk_disable_unprepare(host->tx_lane0_sync_clk); host->is_lane_clks_enabled = false; } @@ -370,18 +372,35 @@ static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host) { int err; - err = clk_bulk_prepare_enable(host->num_clks, host->clks); + if (host->is_lane_clks_enabled) + return 0; + + err = clk_prepare_enable(host->tx_lane0_sync_clk); if (err) - return err; + goto out; - host->is_lane_clks_enabled = true; + err = clk_prepare_enable(host->rx_lane0_sync_clk); + if (err) + goto out_disable_tx_lane0; + + err = clk_prepare_enable(host->rx_lane1_sync_clk); + if (err) + goto out_disable_rx_lane0; + host->is_lane_clks_enabled = true; return 0; + +out_disable_rx_lane0: + clk_disable_unprepare(host->rx_lane0_sync_clk); +out_disable_tx_lane0: + clk_disable_unprepare(host->tx_lane0_sync_clk); +out: + return err; } static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host) { - int err; + int err, i; struct device *dev = host->hba->dev; if (has_acpi_companion(dev)) @@ -393,6 +412,18 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host) host->num_clks = err; + for (i = 0; i < host->num_clks; i++) { + if (!host->clks[i].id) + continue; + if (!strcmp(host->clks[i].id, "tx_lane0_sync_clk")) + host->tx_lane0_sync_clk = host->clks[i].clk; + else if (!strcmp(host->clks[i].id, "rx_lane0_sync_clk")) + host->rx_lane0_sync_clk = host->clks[i].clk; + else if (!strcmp(host->clks[i].id, "rx_lane1_sync_clk")) + if (host->hba->lanes_per_direction > 1) + host->rx_lane1_sync_clk = host->clks[i].clk; + } + return 0; } diff --git a/drivers/ufs/host/ufs-qcom.h b/drivers/ufs/host/ufs-qcom.h index 380d02333d382..5d09668bdbbd7 100644 --- a/drivers/ufs/host/ufs-qcom.h +++ b/drivers/ufs/host/ufs-qcom.h @@ -280,6 +280,9 @@ struct ufs_qcom_host { struct ufs_hba *hba; struct ufs_pa_layer_attr dev_req_params; struct clk_bulk_data *clks; + struct clk *tx_lane0_sync_clk; + struct clk *rx_lane0_sync_clk; + struct clk *rx_lane1_sync_clk; u32 num_clks; bool is_lane_clks_enabled; From b095ec9123ee0ef0fbddde0bd711d87bd5ac2156 Mon Sep 17 00:00:00 2001 From: Ziqi Chen Date: Sat, 29 Aug 2026 13:13:54 +0530 Subject: [PATCH 2/3] FROMLIST scsi: ufs: ufs-qcom: Add specified gear support for multi gear scaling The UFS clock frequency and gear speed do not necessarily have a strict one-to-one correspondence on all platforms. Introduce a device tree based configuration interface that allows specifying the HS gear speed for each supported operating frequency via the "opp-level" property in the OPP table. When this property is not configured, the driver falls back to the default frequency-to-gear mapping table. Signed-off-by: Ziqi Chen Signed-off-by: Nitin Rawat Signed-off-by: Giri Prasad Goriparthi --- drivers/ufs/host/ufs-qcom.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c index ff61f930c7ac6..7e98865b014c8 100644 --- a/drivers/ufs/host/ufs-qcom.c +++ b/drivers/ufs/host/ufs-qcom.c @@ -2213,8 +2213,9 @@ static unsigned long ufs_qcom_opp_freq_to_clk_freq(struct ufs_hba *hba, bool found = false; opp = dev_pm_opp_find_freq_exact_indexed(hba->dev, freq, 0, true); - if (IS_ERR(opp)) { - dev_err(hba->dev, "Failed to find OPP for exact frequency %lu\n", freq); + if (IS_ERR_OR_NULL(opp)) { + dev_err(hba->dev, "%s: Failed to find OPP for exact frequency %lu\n", + __func__, freq); return 0; } @@ -2242,12 +2243,32 @@ static unsigned long ufs_qcom_opp_freq_to_clk_freq(struct ufs_hba *hba, static u32 ufs_qcom_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq) { - u32 gear = UFS_HS_DONT_CHANGE; + struct dev_pm_opp *opp; unsigned long unipro_freq; + u32 gear = UFS_HS_DONT_CHANGE; if (!hba->use_pm_opp) return gear; + opp = dev_pm_opp_find_freq_exact_indexed(hba->dev, freq, 0, true); + if (IS_ERR_OR_NULL(opp)) { + dev_err(hba->dev, "%s: Failed to find OPP for exact frequency %lu\n", + __func__, freq); + return gear; + } + + /* Get HS gear speed from 'opp-level' */ + gear = dev_pm_opp_get_level(opp); + dev_pm_opp_put(opp); + + /* + * Greater than max gear means that there is no specified gear configured in DT + * or the specified gear is invalid. + */ + if (gear <= hba->max_pwr_info.info.gear_rx) + return gear; + + gear = UFS_HS_DONT_CHANGE; unipro_freq = ufs_qcom_opp_freq_to_clk_freq(hba, freq, "core_clk_unipro"); switch (unipro_freq) { case 403000000: @@ -2268,7 +2289,8 @@ static u32 ufs_qcom_freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq) gear = UFS_HS_G1; break; default: - dev_err(hba->dev, "%s: Unsupported clock freq : %lu\n", __func__, freq); + dev_err(hba->dev, "%s: Unsupported clock freq [sys_clk: %lu, unipro_clk: %lu]\n", + __func__, freq, unipro_freq); return UFS_HS_DONT_CHANGE; } From 50105fadfd570e7f7321ec26b1406580422df386 Mon Sep 17 00:00:00 2001 From: Nitin Rawat Date: Sat, 29 Aug 2026 13:13:55 +0530 Subject: [PATCH 3/3] FROMLIST arm64: dts: qcom: Set specified gear configuration for Hamoa Assign the appropriate UFS HS gear speed levels to each OPP frequency entry in the device tree, enabling proper per-frequency gear scaling (G1 at 75MHz, G3 at 150MHz, G5 at 300MHz). Signed-off-by: Nitin Rawat Signed-off-by: Giri Prasad Goriparthi --- arch/arm64/boot/dts/qcom/hamoa.dtsi | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/hamoa.dtsi b/arch/arm64/boot/dts/qcom/hamoa.dtsi index d42b5264ccbcc..99a8b0174a860 100644 --- a/arch/arm64/boot/dts/qcom/hamoa.dtsi +++ b/arch/arm64/boot/dts/qcom/hamoa.dtsi @@ -3947,6 +3947,7 @@ /bits/ 64 <0>, /bits/ 64 <0>, /bits/ 64 <0>; + opp-level = <1>; /* HS-G1 */ required-opps = <&rpmhpd_opp_low_svs>; }; @@ -3959,6 +3960,7 @@ /bits/ 64 <0>, /bits/ 64 <0>, /bits/ 64 <0>; + opp-level = <3>; /* HS-G3 */ required-opps = <&rpmhpd_opp_svs>; }; @@ -3971,6 +3973,7 @@ /bits/ 64 <0>, /bits/ 64 <0>, /bits/ 64 <0>; + opp-level = <5>; /* HS-G5 */ required-opps = <&rpmhpd_opp_nom>; }; };