From 4641395ed5723df89b0d338976c9d97f8a7eec9d Mon Sep 17 00:00:00 2001 From: Luke Johnson Date: Wed, 1 Jul 2026 15:55:01 +1200 Subject: [PATCH 1/4] SM8550: enable UFS inline crypto Add the ICE iface-clock and SM8550 ICE resource patches needed by the standalone Qualcomm ICE driver, including the iface-clock resume unwind fix. Enable block inline encryption, UFS crypto, and the Qualcomm ICE driver in the SM8550 kernel config. --- .../devices/SM8550/linux/linux.aarch64.conf | 5 +- ...-allow-explicit-votes-on-iface-clock.patch | 72 +++++++++++++++++++ ...550-add-ice-iface-clock-power-domain.patch | 37 ++++++++++ ...ce-unwind-core-clk-on-iface-clk-fail.patch | 22 ++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0204-soc-qcom-ice-allow-explicit-votes-on-iface-clock.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0205-arm64-dts-qcom-sm8550-add-ice-iface-clock-power-domain.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0206-soc-qcom-ice-unwind-core-clk-on-iface-clk-fail.patch diff --git a/projects/ROCKNIX/devices/SM8550/linux/linux.aarch64.conf b/projects/ROCKNIX/devices/SM8550/linux/linux.aarch64.conf index c6c1190e5a6..fd454616c6c 100644 --- a/projects/ROCKNIX/devices/SM8550/linux/linux.aarch64.conf +++ b/projects/ROCKNIX/devices/SM8550/linux/linux.aarch64.conf @@ -933,7 +933,8 @@ CONFIG_BLK_DEV_WRITE_MOUNTED=y # CONFIG_BLK_CGROUP_IOPRIO is not set CONFIG_BLK_DEBUG_FS=y # CONFIG_BLK_SED_OPAL is not set -# CONFIG_BLK_INLINE_ENCRYPTION is not set +CONFIG_BLK_INLINE_ENCRYPTION=y +# CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK is not set # # Partition Types @@ -5785,6 +5786,7 @@ CONFIG_MMC_HSQ=y # CONFIG_MMC_SDHCI_XENON is not set CONFIG_SCSI_UFSHCD=y CONFIG_SCSI_UFS_BSG=y +CONFIG_SCSI_UFS_CRYPTO=y # CONFIG_SCSI_UFS_HWMON is not set # CONFIG_SCSI_UFSHCD_PCI is not set CONFIG_SCSI_UFSHCD_PLATFORM=y @@ -6550,6 +6552,7 @@ CONFIG_QCOM_STATS=y CONFIG_QCOM_WCNSS_CTRL=m CONFIG_QCOM_APR=y CONFIG_QCOM_ICC_BWMON=y +CONFIG_QCOM_INLINE_CRYPTO_ENGINE=y CONFIG_QCOM_PBS=y # end of Qualcomm SoC drivers diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0204-soc-qcom-ice-allow-explicit-votes-on-iface-clock.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0204-soc-qcom-ice-allow-explicit-votes-on-iface-clock.patch new file mode 100644 index 00000000000..3ed09655294 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0204-soc-qcom-ice-allow-explicit-votes-on-iface-clock.patch @@ -0,0 +1,72 @@ +From 5f93b127bc17fda3fbd2ca4d9a7d8cae82624022 Mon Sep 17 00:00:00 2001 +From: Harshal Dev +Date: Thu, 16 Apr 2026 17:29:19 +0530 +Subject: [PATCH] soc: qcom: ice: allow explicit votes on iface clock + +Since Qualcomm inline-crypto engine (ICE) is now a dedicated driver +de-coupled from the QCOM UFS driver, it explicitly votes for its required +clocks during probe. For scenarios where the clk_ignore_unused flag is not +passed on the kernel command line, avoid potential unclocked ICE hardware +register access during probe by also voting the iface clock. + +Also update the suspend and resume callbacks to handle un-voting and voting +the iface clock. +--- + drivers/soc/qcom/ice.c | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/drivers/soc/qcom/ice.c b/drivers/soc/qcom/ice.c +index 9b4e167925fd..e37d6985e10e 100644 +--- a/drivers/soc/qcom/ice.c ++++ b/drivers/soc/qcom/ice.c +@@ -108,6 +108,7 @@ struct qcom_ice { + void __iomem *base; + + struct clk *core_clk; ++ struct clk *iface_clk; + bool use_hwkm; + bool hwkm_init_complete; + u8 hwkm_version; +@@ -312,8 +313,13 @@ int qcom_ice_resume(struct qcom_ice *ice) + + err = clk_prepare_enable(ice->core_clk); + if (err) { +- dev_err(dev, "failed to enable core clock (%d)\n", +- err); ++ dev_err(dev, "Failed to enable core clock: %d\n", err); ++ return err; ++ } ++ ++ err = clk_prepare_enable(ice->iface_clk); ++ if (err) { ++ dev_err(dev, "Failed to enable iface clock: %d\n", err); + return err; + } + qcom_ice_hwkm_init(ice); +@@ -323,6 +329,7 @@ EXPORT_SYMBOL_GPL(qcom_ice_resume); + + int qcom_ice_suspend(struct qcom_ice *ice) + { ++ clk_disable_unprepare(ice->iface_clk); + clk_disable_unprepare(ice->core_clk); + ice->hwkm_init_complete = false; + +@@ -579,9 +586,15 @@ static struct qcom_ice *qcom_ice_create(struct device *dev, + engine->core_clk = devm_clk_get_optional_enabled(dev, "ice_core_clk"); + if (!engine->core_clk) + engine->core_clk = devm_clk_get_optional_enabled(dev, "ice"); ++ if (!engine->core_clk) ++ engine->core_clk = devm_clk_get_optional_enabled(dev, "core"); + if (!engine->core_clk) + engine->core_clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(engine->core_clk)) + return ERR_CAST(engine->core_clk); + ++ engine->iface_clk = devm_clk_get_optional_enabled(dev, "iface"); ++ if (IS_ERR(engine->iface_clk)) ++ return ERR_CAST(engine->iface_clk); ++ + if (!qcom_ice_check_supported(engine)) + return ERR_PTR(-EOPNOTSUPP); +-- +2.51.0 diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0205-arm64-dts-qcom-sm8550-add-ice-iface-clock-power-domain.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0205-arm64-dts-qcom-sm8550-add-ice-iface-clock-power-domain.patch new file mode 100644 index 00000000000..d9af3c49131 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0205-arm64-dts-qcom-sm8550-add-ice-iface-clock-power-domain.patch @@ -0,0 +1,37 @@ +From 6a135f4986c0e4c97362ae5154db76eaadf1a692 Mon Sep 17 00:00:00 2001 +From: Harshal Dev +Date: Thu, 16 Apr 2026 17:29:19 +0530 +Subject: [PATCH] arm64: dts: qcom: sm8550: add ICE iface clock and power + domain + +Qualcomm inline crypto engine (ICE) specifies and votes for its own +resources. Before accessing ICE hardware during probe, to avoid potential +unclocked register access issues when clk_ignore_unused is not passed, the +driver should enable the iface clock in addition to the core clock. This can +only be done if the UFS_PHY_GDSC power domain is enabled. + +Specify both the UFS_PHY_GDSC power domain and the iface clock in the ICE node +for SM8550. +--- + arch/arm64/boot/dts/qcom/sm8550.dtsi | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/qcom/sm8550.dtsi b/arch/arm64/boot/dts/qcom/sm8550.dtsi +index f2770521a48d..ec10bba5ca81 100644 +--- a/arch/arm64/boot/dts/qcom/sm8550.dtsi ++++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi +@@ -2887,7 +2887,11 @@ ice: crypto@1d88000 { + "qcom,inline-crypto-engine"; + reg = <0 0x01d88000 0 0x18000>; + +- clocks = <&gcc GCC_UFS_PHY_ICE_CORE_CLK>; ++ clocks = <&gcc GCC_UFS_PHY_ICE_CORE_CLK>, ++ <&gcc GCC_UFS_PHY_AHB_CLK>; ++ clock-names = "core", ++ "iface"; ++ power-domains = <&gcc UFS_PHY_GDSC>; + }; + + tcsr_mutex: hwlock@1f40000 { +-- +2.51.0 diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0206-soc-qcom-ice-unwind-core-clk-on-iface-clk-fail.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0206-soc-qcom-ice-unwind-core-clk-on-iface-clk-fail.patch new file mode 100644 index 00000000000..1fb9a386a24 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0206-soc-qcom-ice-unwind-core-clk-on-iface-clk-fail.patch @@ -0,0 +1,22 @@ +From: jaewun +Subject: [PATCH] soc: qcom: ice: unwind core clock if iface clock enable fails + +qcom_ice_resume() enables core_clk then iface_clk. If the iface_clk enable +fails it returns without disabling the core_clk it just enabled, leaking an +enable reference. Unwind core_clk on that error path. + +Fixes the iface-clock support added in "soc: qcom: ice: allow explicit votes +on iface clock". + +Signed-off-by: jaewun +--- +--- a/drivers/soc/qcom/ice.c ++++ b/drivers/soc/qcom/ice.c +@@ -320,6 +320,7 @@ + err = clk_prepare_enable(ice->iface_clk); + if (err) { + dev_err(dev, "Failed to enable iface clock: %d\n", err); ++ clk_disable_unprepare(ice->core_clk); + return err; + } + qcom_ice_hwkm_init(ice); From bd764d9bc752a19dcf220fe27ea82a9f2029c83d Mon Sep 17 00:00:00 2001 From: Luke Johnson Date: Wed, 1 Jul 2026 15:55:17 +1200 Subject: [PATCH 2/4] SM8550: add UFS PM relink fixes Add the UFS core, qcom host, and QMP PHY patches needed for reliable suspend/resume relink on the SM8550 UFS host. The set covers non-MCQ PM completion draining, host-reset IRQ depth balance, hibern8 error propagation, auto-hibern8 versus clk-gating policy, no-retention PHY handling, and RX LineCfg control around link startup. --- ...in-relink-completions-out-of-band-pm.patch | 420 ++++++++++++++++++ ...qcom-balance-irq-on-host-reset-error.patch | 55 +++ ...agate-hibern8-exit-failure-clk-scale.patch | 34 ++ ...om-auto-hibern8-clk-gating-collision.patch | 38 ++ ...om-keep-mphy-powered-on-hibern8-park.patch | 80 ++++ ...isable-rx-linecfg-after-link-startup.patch | 160 +++++++ 6 files changed, 787 insertions(+) create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch new file mode 100644 index 00000000000..a2a6309f360 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch @@ -0,0 +1,420 @@ +From: jaewun +Subject: [PATCH] scsi: ufs: drain relink completions during PM + +On this non-MCQ controller the UFS interrupt is a threaded IRQF_ONESHOT +line. If the threaded handler is left pending across a PM transition, +genirq keeps the line masked and ufshcd_intr() is not re-entered. UIC and +UTP completions from the resume relink can then remain latched in +REG_INTERRUPT_STATUS while the relink waits for completion. + +Drain those completions without depending on a new UFS IRQ: + + - Use ufshcd_relinking() to cover both PM relink and the explicit + error-handler reset window. + - Use disable_irq_nosync() while relinking so the PM/EH path does not + wait on the threaded handler it is bypassing. + - Drain UTP/UIC/TMF completions from the hardirq PM path and from a PM + poll timer, and read-clear UIC error-code registers after errors. + - Complete failed DME_LINK_STARTUP UIC commands with + UIC_CMD_RESULT_FAILURE so link startup can retry. + - On non-MCQ reset completion, requeue all outstanding SCSI commands + instead of trusting stale post-reset OCS state. + +Also recover a failed SW clk-gating hibern8 exit inline. That path runs +outside pm_op_in_progress during the freezer phase; scheduling fatal EH there +can leave the freezer waiting on I/O that depends on the same recovery. Keep +clk-gating hibern8 enabled, but suppress the fatal EH guard for that specific +ungate window and run link recovery directly. + +Signed-off-by: jaewun +--- +--- a/drivers/ufs/core/ufshcd.c ++++ b/drivers/ufs/core/ufshcd.c +@@ -348,6 +348,20 @@ + static void ufshcd_hba_vreg_set_lpm(struct ufs_hba *hba); + static void ufshcd_hba_vreg_set_hpm(struct ufs_hba *hba); + ++/* ++ * True while the controller is being relinked during PM or during the explicit ++ * error-handler reset bracket. In these windows the threaded IRQ handler may ++ * not run, so ufshcd_disable_irq() must not synchronize on it and completions ++ * are drained without relying on a new UFS IRQ. A dedicated flag is used rather ++ * than UFSHCD_STATE_RESET so generic resets/probe are unaffected. Lock-free ++ * cross-context reads -> READ_ONCE. ++ */ ++static inline bool ufshcd_relinking(struct ufs_hba *hba) ++{ ++ return READ_ONCE(hba->pm_op_in_progress) || ++ READ_ONCE(hba->relink_poll_active); ++} ++ + void ufshcd_enable_irq(struct ufs_hba *hba) + { + if (!hba->is_irq_enabled) { +@@ -360,7 +374,16 @@ + void ufshcd_disable_irq(struct ufs_hba *hba) + { + if (hba->is_irq_enabled) { +- disable_irq(hba->irq); ++ /* ++ * hba->irq is threaded and IRQF_ONESHOT. During PM/EH relink the ++ * threaded handler is bypassed and completions are serviced directly, ++ * so do not wait for the thread here. The controller reset and direct ++ * completion drain handle any in-flight state. ++ */ ++ if (ufshcd_relinking(hba)) ++ disable_irq_nosync(hba->irq); ++ else ++ disable_irq(hba->irq); + hba->is_irq_enabled = false; + } + } +@@ -1921,11 +1944,33 @@ + hba->clk_gating.is_suspended = true; + if (ufshcd_is_link_hibern8(hba)) { + ret = ufshcd_uic_hibern8_exit(hba); +- if (ret) +- dev_err(hba->dev, "%s: hibern8 exit failed %d\n", ++ if (ret) { ++ /* ++ * This SW clk-gating hibern8 exit runs outside ++ * pm_op_in_progress. During the freezer phase, ++ * scheduling fatal EH can leave I/O waiting on the ++ * same recovery. Run the relink inline and bracket it ++ * with the poller so direct completion draining covers ++ * link startup. ++ */ ++ dev_err(hba->dev, "%s: hibern8 exit failed %d, recovering link\n", + __func__, ret); +- else ++ if (!hba->mcq_enabled) { ++ WRITE_ONCE(hba->relink_poll_active, true); ++ mod_timer(&hba->pm_poll_timer, ++ jiffies + msecs_to_jiffies(2)); ++ } ++ ret = ufshcd_link_recovery(hba); ++ if (!hba->mcq_enabled) { ++ WRITE_ONCE(hba->relink_poll_active, false); ++ timer_delete_sync(&hba->pm_poll_timer); ++ } ++ if (ret) ++ dev_err(hba->dev, "%s: link recovery after hibern8 exit failed %d\n", ++ __func__, ret); ++ } else { + ufshcd_set_link_active(hba); ++ } + } + hba->clk_gating.is_suspended = false; + } +@@ -4382,7 +4427,12 @@ + spin_lock_irqsave(hba->host->host_lock, flags); + hba->active_uic_cmd = NULL; + hba->uic_async_done = NULL; +- if (ret && !hba->pm_op_in_progress) { ++ /* ++ * Suppress fatal link-broken+EH for the SW clk-gating ungate hibern8 ++ * exit. That path runs outside pm_op_in_progress during the freezer ++ * phase; ufshcd_ungate_work handles recovery inline instead. ++ */ ++ if (ret && !hba->pm_op_in_progress && !hba->clk_gating.is_suspended) { + ufshcd_set_link_broken(hba); + ufshcd_schedule_eh_work(hba); + } +@@ -6383,11 +6433,53 @@ + ufs_debugfs_exception_event(hba, status); + } + +-/* Complete requests that have door-bell cleared */ ++/* ++ * Non-MCQ counterpart of ufshcd_mcq_force_compl_one(): invoked via ++ * ufshcd_complete_requests(force_compl=true) from ufshcd_host_reset_and_restore() ++ * (and the error handler) AFTER ufshcd_hba_stop() has reset the controller. The ++ * normal single-doorbell path (ufshcd_transfer_req_compl/ufshcd_poll) only ++ * completes tags whose doorbell bit reads back clear, so a request left ++ * outstanding by a failed UIC/link op is not completed. The in-memory OCS is ++ * stale after reset, so requeue SCSI commands rather than reporting a ++ * possibly-bogus result; reserved device-management tags are completed by ++ * their own paths and skipped (mirrors ufshcd_mcq_force_compl_one()). ++ */ ++static bool ufshcd_force_compl_one(struct request *rq, void *priv) ++{ ++ struct scsi_cmnd *cmd = blk_mq_rq_to_pdu(rq); ++ struct scsi_device *sdev = rq->q->queuedata; ++ struct ufs_hba *hba = shost_priv(sdev->host); ++ unsigned long flags; ++ ++ if (blk_mq_is_reserved_rq(rq)) ++ return true; ++ ++ spin_lock_irqsave(&hba->outstanding_lock, flags); ++ __clear_bit(rq->tag, &hba->outstanding_reqs); ++ spin_unlock_irqrestore(&hba->outstanding_lock, flags); ++ ++ if (!test_bit(SCMD_STATE_COMPLETE, &cmd->state)) { ++ set_host_byte(cmd, DID_REQUEUE); ++ ufshcd_release_scsi_cmd(hba, cmd); ++ scsi_done(cmd); ++ } ++ ++ return true; ++} ++ ++/* Complete requests that have door-bell cleared (or, on force, all outstanding) */ + static void ufshcd_complete_requests(struct ufs_hba *hba, bool force_compl) + { + if (hba->mcq_enabled) + ufshcd_mcq_compl_pending_transfer(hba, force_compl); ++ else if (force_compl) ++ /* ++ * The controller has been reset; complete every outstanding ++ * request (the doorbell no longer reflects them) so no request ++ * remains stuck. ++ */ ++ blk_mq_tagset_busy_iter(&hba->host->tag_set, ++ ufshcd_force_compl_one, NULL); + else + ufshcd_transfer_req_compl(hba); + +@@ -6863,6 +6955,16 @@ + + hba->force_reset = false; + spin_unlock_irqrestore(hba->host->host_lock, flags); ++ /* ++ * Arm the out-of-band completion poller across the error-handler ++ * relink. ufshcd_state == UFSHCD_STATE_RESET here and pm_op_in_progress ++ * is clear, so ufshcd_relinking() gates the drain and nosync IRQ ++ * disable while the reset is in progress. ++ */ ++ if (!hba->mcq_enabled) { ++ WRITE_ONCE(hba->relink_poll_active, true); ++ mod_timer(&hba->pm_poll_timer, jiffies + msecs_to_jiffies(2)); ++ } + err = ufshcd_reset_and_restore(hba); + if (err) + dev_err(hba->dev, "%s: reset and restore failed with err %d\n", +@@ -6889,6 +6991,14 @@ + } + ufshcd_clear_eh_in_progress(hba); + spin_unlock_irqrestore(hba->host->host_lock, flags); ++ /* ++ * Stop the relink poller before unprepare can release the clocks. Clear the ++ * flag first so the callback sees ufshcd_relinking() false and will not ++ * re-arm, then synchronously delete. Done after the host_lock is dropped ++ * (the drain takes host_lock). ++ */ ++ WRITE_ONCE(hba->relink_poll_active, false); ++ timer_delete_sync(&hba->pm_poll_timer); + ufshcd_err_handling_unprepare(hba); + up(&hba->host_sem); + +@@ -7207,6 +7317,112 @@ + return retval; + } + ++/* ++ * ufshcd_pm_drain_completions - service UTP/UIC/TMF completions without the IRQ ++ * @hba: per adapter instance ++ * ++ * The UFS interrupt is a threaded IRQF_ONESHOT line. If its threaded handler is ++ * left pending across a PM transition the genirq core keeps the line masked, so ++ * ufshcd_intr() is not re-invoked and relink completions can remain pending. ++ * Called inline from ufshcd_intr()'s PM fast path (hardirq) AND from the PM poll ++ * timer (softirq), so it must not depend on the UFS IRQ firing. pm_drain_active ++ * serialises the two callers so they cannot both W1C REG_INTERRUPT_STATUS. ++ */ ++static void ufshcd_pm_drain_completions(struct ufs_hba *hba) ++{ ++ int retries = hba->nutrs; ++ ++ if (test_and_set_bit(0, &hba->pm_drain_active)) ++ return; ++ ++ while (retries-- > 0) { ++ unsigned long flags; ++ u32 status, compl_bits; ++ ++ status = ufshcd_readl(hba, REG_INTERRUPT_STATUS) & ++ ufshcd_readl(hba, REG_INTERRUPT_ENABLE); ++ if (!status) ++ break; ++ ++ /* W1C exactly the snapshot we read. */ ++ ufshcd_writel(hba, status, REG_INTERRUPT_STATUS); ++ ++ if (status & UFSHCD_ERROR_MASK) { ++ /* ++ * read-to-clear all UIC error-code registers so a ++ * transient resume-time error cannot storm. ++ */ ++ ufshcd_readl(hba, REG_UIC_ERROR_CODE_PHY_ADAPTER_LAYER); ++ ufshcd_readl(hba, REG_UIC_ERROR_CODE_DATA_LINK_LAYER); ++ ufshcd_readl(hba, REG_UIC_ERROR_CODE_NETWORK_LAYER); ++ ufshcd_readl(hba, REG_UIC_ERROR_CODE_TRANSPORT_LAYER); ++ ufshcd_readl(hba, REG_UIC_ERROR_CODE_DME); ++ ++ /* ++ * If the relink's in-flight UIC command (e.g. ++ * DME_LINKSTARTUP) errored with no accompanying UIC ++ * completion, force-complete it with UIC_CMD_RESULT_FAILURE ++ * so ufshcd_wait_for_uic_cmd() returns promptly and the ++ * link startup can retry. Restricted to DME_LINK_STARTUP ++ * with no uic_async_done in flight so power-mode UIC ++ * commands are untouched. irqsave: this also runs in ++ * softirq (the PM poll timer). ++ */ ++ spin_lock_irqsave(hba->host->host_lock, flags); ++ if (!(status & UFSHCD_UIC_MASK) && !hba->uic_async_done && ++ hba->active_uic_cmd && ++ hba->active_uic_cmd->cmd_active && ++ hba->active_uic_cmd->command == ++ UIC_CMD_DME_LINK_STARTUP) { ++ struct uic_command *cmd = hba->active_uic_cmd; ++ ++ cmd->argument2 = (cmd->argument2 & ++ ~MASK_UIC_COMMAND_RESULT) | ++ UIC_CMD_RESULT_FAILURE; ++ cmd->cmd_active = false; ++ complete(&cmd->done); ++ } ++ spin_unlock_irqrestore(hba->host->host_lock, flags); ++ } ++ ++ compl_bits = status & (UTP_TRANSFER_REQ_COMPL | UFSHCD_UIC_MASK); ++ if (compl_bits & UFSHCD_UIC_MASK) ++ ufshcd_uic_cmd_compl(hba, compl_bits); ++ if (status & UTP_TASK_REQ_COMPL) ++ ufshcd_tmc_handler(hba); ++ if (compl_bits & UTP_TRANSFER_REQ_COMPL) ++ ufshcd_transfer_req_compl(hba); ++ } ++ ++ clear_bit(0, &hba->pm_drain_active); ++} ++ ++/* ++ * ufshcd_pm_poll_timer_fn - out-of-band UFS completion drain during PM resume. ++ * ++ * Armed across the PM relink window. Fires from the arch timer, so it services ++ * completions even if the IRQ line remains oneshot-masked. Self-rearms while ++ * relinking; the PM/EH paths clear the state and delete the timer on exit. ++ */ ++static void ufshcd_pm_poll_timer_fn(struct timer_list *t) ++{ ++ struct ufs_hba *hba = timer_container_of(hba, t, pm_poll_timer); ++ ++ if (hba->mcq_enabled || !ufshcd_relinking(hba)) ++ return; ++ ++ ufshcd_pm_drain_completions(hba); ++ ++ /* ++ * Re-check before re-arming so a concurrent disarm (which clears the ++ * relink state before timer_delete_sync()) cannot lose the race and leave ++ * the timer armed. A stray re-arm would be harmless anyway -- the next tick ++ * sees the state clear and returns without MMIO -- but avoid it. ++ */ ++ if (ufshcd_relinking(hba)) ++ mod_timer(&hba->pm_poll_timer, jiffies + msecs_to_jiffies(2)); ++} ++ + /** + * ufshcd_intr - Main interrupt service routine + * @irq: irq number +@@ -7222,6 +7438,18 @@ + struct ufs_hba *hba = __hba; + u32 intr_status, enabled_intr_status; + ++ /* ++ * On this non-MCQ controller interrupt handling is normally deferred to ++ * the threaded handler, which may not run during PM relink. Service ++ * UTP/UIC/TMF completions inline and read-clear UIC error-code registers. ++ * Do not run ufshcd_check_errors() here: it can schedule error handling ++ * while PM recovery is already in progress. ++ */ ++ if (!hba->mcq_enabled && ufshcd_relinking(hba)) { ++ ufshcd_pm_drain_completions(hba); ++ return IRQ_HANDLED; ++ } ++ + /* Move interrupt handling to thread when MCQ & ESI are not enabled */ + if (!hba->mcq_enabled || !hba->mcq_esi_enabled) + return IRQ_WAKE_THREAD; +@@ -9729,6 +9957,7 @@ + + static void ufshcd_hba_exit(struct ufs_hba *hba) + { ++ timer_delete_sync(&hba->pm_poll_timer); + if (hba->is_powered) { + ufshcd_pm_qos_exit(hba); + ufshcd_exit_clk_scaling(hba); +@@ -10182,6 +10411,15 @@ + if (ret) + goto out; + ++ /* ++ * Vendor resume is done so the controller clocks/registers are usable. ++ * Arm the completion poller for the relink. If the UFS IRQ remains ++ * oneshot-masked, the timer drains UIC/UTP completions from the arch timer ++ * instead. Disarmed at out: before PM state is released. ++ */ ++ if (!hba->mcq_enabled) ++ mod_timer(&hba->pm_poll_timer, jiffies + msecs_to_jiffies(2)); ++ + /* For DeepSleep, the only supported option is to have the link off */ + WARN_ON(ufshcd_is_ufs_dev_deepsleep(hba) && !ufshcd_is_link_off(hba)); + +@@ -10259,9 +10497,18 @@ + out: + if (ret) + ufshcd_update_evt_hist(hba, UFS_EVT_WL_RES_ERR, (u32)ret); ++ /* ++ * Stop the relink completion poller BEFORE ufshcd_release() (which can ++ * queue clock gating once is_suspended is cleared) so the timer can never ++ * read UFS MMIO after the clocks are gated. Clear pm_op_in_progress first so ++ * a concurrently-firing timer sees the flag false and does not re-arm, then ++ * synchronously delete it. From here normal IRQ delivery resumes: the ++ * now-runnable threaded handler drains any remaining IS and unmasks the line. ++ */ ++ hba->pm_op_in_progress = false; ++ timer_delete_sync(&hba->pm_poll_timer); + hba->clk_gating.is_suspended = false; + ufshcd_release(hba); +- hba->pm_op_in_progress = false; + return ret; + } + +@@ -10884,6 +11131,13 @@ + */ + hba->vcc_off_delay_us = 2000; + ++ /* ++ * Set up the relink completion poll timer before the first goto out_disable ++ * (which reaches ufshcd_hba_exit() -> timer_delete_sync()), so teardown ++ * never operates on an uninitialised timer. ++ */ ++ timer_setup(&hba->pm_poll_timer, ufshcd_pm_poll_timer_fn, 0); ++ + err = ufshcd_hba_init(hba); + if (err) + goto out_error; +--- a/include/ufs/ufshcd.h ++++ b/include/ufs/ufshcd.h +@@ -976,6 +976,18 @@ + enum ufs_pm_level pm_lvl_min; + int pm_op_in_progress; + ++ /* ++ * Out-of-band completion drain for PM and error-handler relink. If the ++ * threaded IRQ remains oneshot-masked, pm_poll_timer drains completions ++ * from the arch timer while pm_op_in_progress or relink_poll_active is ++ * set. pm_drain_active serialises the timer drain against the hardirq ++ * drain. relink_poll_active is set only around the explicit error-handler ++ * reset bracket, not for generic resets. ++ */ ++ struct timer_list pm_poll_timer; ++ unsigned long pm_drain_active; ++ bool relink_poll_active; ++ + /* Auto-Hibernate Idle Timer register value */ + u32 ahit; + diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch new file mode 100644 index 00000000000..625deb187bf --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch @@ -0,0 +1,55 @@ +From: jaewun +Subject: [PATCH] scsi: ufs: qcom: balance the IRQ disable on host_reset error exits + +ufs_qcom_host_reset() snapshots whether the IRQ was enabled, calls +ufshcd_disable_irq(), and only re-enables it on the success path. The two +error returns (reset_control_assert / reset_control_deassert failure) leave +the IRQ disabled, leaking the disable depth and leaving the controller IRQ +masked for the rest of the controller's life. + +The relink path can now mask the IRQ with disable_irq_nosync() while PM is in +progress, so these error exits must preserve IRQ depth as well. Route both +failures through a common exit that re-enables the IRQ when it was enabled on +entry. + +Signed-off-by: jaewun +--- +diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c +index 375fd24..61a5b3a 100644 +--- a/drivers/ufs/host/ufs-qcom.c ++++ b/drivers/ufs/host/ufs-qcom.c +@@ -449,7 +449,7 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) + if (ret) { + dev_err(hba->dev, "%s: core_reset assert failed, err = %d\n", + __func__, ret); +- return ret; ++ goto out; + } + + /* +@@ -463,15 +463,23 @@ static int ufs_qcom_host_reset(struct ufs_hba *hba) + if (ret) { + dev_err(hba->dev, "%s: core_reset deassert failed, err = %d\n", + __func__, ret); +- return ret; ++ goto out; + } + + usleep_range(1000, 1100); + ++ ret = 0; ++out: ++ /* ++ * Re-enable the IRQ on the error exits too, otherwise a reset ++ * assert/deassert failure leaks the disable and leaves the controller ++ * IRQ masked. (This path became reachable once ufshcd_disable_irq() stops ++ * synchronizing during PM resume.) ++ */ + if (reenable_intr) + ufshcd_enable_irq(hba); + +- return 0; ++ return ret; + } + + static u32 ufs_qcom_get_hs_gear(struct ufs_hba *hba) diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch new file mode 100644 index 00000000000..912fb147c80 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch @@ -0,0 +1,34 @@ +From: jaewun +Subject: [PATCH] scsi: ufs: qcom: propagate hibern8 exit failure from clk_scale_notify + +ufs_qcom_clk_scale_notify() wakes the link with ufshcd_uic_hibern8_exit() +after a POST_CHANGE clock scale but drops its return value and always returns 0. +Every other error in this function (hibern8 enter, the pre/post change helpers) +is captured and returned; only this success-path link-wake was silently ignored. + +If the link fails to exit hibern8, the function still reports success to the +clock-scaling core. ufshcd_scale_clks() consumes a non-zero POST_CHANGE return: +on failure it rolls back the OPP/clock frequency, leaves target_freq unchanged, +and lets devfreq retry on the next poll. Returning 0 bypasses that rollback. + +Capture and return the error so the failure is propagated to the +clock-scaling/devfreq accounting and rollback path instead of being masked as +success. Link recovery is still handled by the existing UIC error path; this +change only fixes the clk-scaling return value. + +Signed-off-by: jaewun +--- +diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c +--- a/drivers/ufs/host/ufs-qcom.c ++++ b/drivers/ufs/host/ufs-qcom.c +@@ -1779,8 +1779,8 @@ + } + + ufs_qcom_icc_update_bw(host); +- ufshcd_uic_hibern8_exit(hba); ++ err = ufshcd_uic_hibern8_exit(hba); + } + +- return 0; ++ return err; + } diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch new file mode 100644 index 00000000000..a8c47fcb8c5 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch @@ -0,0 +1,38 @@ +From: jaewun +Subject: [PATCH] scsi: ufs: qcom: disable HW auto-hibern8 with clk-gating hibern8 + +The qcom host sets UFSHCD_CAP_HIBERN8_WITH_CLK_GATING, so ufshcd_gate_work() +parks the link with a SW DME_HIBERNATE_ENTER before gating clocks. The core also +defaults HW auto-hibern8 on (ahit = 150 ms). On idle the HW can park the M-PHY +first; ufshcd_gate_work() then issues a redundant SW hibern8-enter on the +already-parked link. That can time out waiting for power-mode-change +completion and mark the link broken. + +HW auto-hibern8 and SW clk-gating hibern8 are mutually exclusive ways to park +the link. Keep the SW clk-gating path, which runs in ufshcd_gate_work() with +the IRQ still enabled, and disable HW auto-hibern8 via +UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8. + +Signed-off-by: jaewun +--- +--- a/drivers/ufs/host/ufs-qcom.c ++++ b/drivers/ufs/host/ufs-qcom.c +@@ -1157,6 +1157,18 @@ + + if (drvdata && drvdata->quirks) + hba->quirks |= drvdata->quirks; ++ ++ /* ++ * This host uses UFSHCD_CAP_HIBERN8_WITH_CLK_GATING, i.e. ufshcd_gate_work() ++ * issues a SW DME_HIBERNATE_ENTER to park the link before gating clocks. ++ * Leaving HW auto-hibern8 enabled too makes the HW park the link first; ++ * gate_work then issues a redundant SW hibern8-enter on the already-parked ++ * link, which can time out waiting for power-mode-change completion. ++ * The two mechanisms are mutually exclusive; keep the SW clk-gating ++ * path and disable HW auto-hibern8. ++ */ ++ if (hba->caps & UFSHCD_CAP_HIBERN8_WITH_CLK_GATING) ++ hba->quirks |= UFSHCD_QUIRK_BROKEN_AUTO_HIBERN8; + } + + static void ufs_qcom_set_phy_gear(struct ufs_qcom_host *host) diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch new file mode 100644 index 00000000000..17733b2c550 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch @@ -0,0 +1,80 @@ +From: jaewun +Subject: [PATCH] scsi: ufs: qcom: keep M-PHY powered across hibern8 parking on no_phy_retention + +The SM8550 qcom host is flagged no_phy_retention: phy_power_off() can drop +calibrated M-PHY state, and recalibration is done by full link startup through +ufs_qcom_power_up_sequence(). + +ufs_qcom_setup_clocks(on=false) is also used for SW clk-gating and runtime PM +cases where the UFS link is only parked in HIBERN8. The matching resume path +issues DME_HIBERNATE_EXIT, which assumes retained PHY state rather than running +a full link startup. + +On no_phy_retention hosts, keep the PHY powered when the link is merely parked +in HIBERN8: skip phy_power_off() on the way down and the matching phy_power_on() +on the way up. True LINK_OFF system suspend is unchanged and still powers the +PHY off before recalibrating on resume. + +Signed-off-by: jaewun + +--- a/drivers/ufs/host/ufs-qcom.c ++++ b/drivers/ufs/host/ufs-qcom.c +@@ -1253,7 +1253,9 @@ + enum ufs_notify_change_status status) + { + struct ufs_qcom_host *host = ufshcd_get_variant(hba); ++ const struct ufs_qcom_drvdata *drvdata; + struct phy *phy; ++ bool keep_phy; + int err; + + /* +@@ -1264,7 +1266,16 @@ + if (!host) + return 0; + ++ drvdata = of_device_get_match_data(hba->dev); + phy = host->generic_phy; ++ ++ /* ++ * no_phy_retention hosts can lose calibrated M-PHY state on phy_power_off(). ++ * Keep the PHY powered when the link is only parked in HIBERN8; true ++ * LINK_OFF still powers the PHY off and recalibrates on resume. ++ */ ++ keep_phy = drvdata && drvdata->no_phy_retention && ++ ufs_qcom_is_link_hibern8(hba); + + switch (status) { + case PRE_CHANGE: +@@ -1283,19 +1294,23 @@ + ufs_qcom_dev_ref_clk_ctrl(host, false); + } + +- err = phy_power_off(phy); +- if (err) { +- dev_err(hba->dev, "phy power off failed, ret=%d\n", err); +- return err; ++ if (!keep_phy) { ++ err = phy_power_off(phy); ++ if (err) { ++ dev_err(hba->dev, "phy power off failed, ret=%d\n", err); ++ return err; ++ } + } + } + break; + case POST_CHANGE: + if (on) { +- err = phy_power_on(phy); +- if (err) { +- dev_err(hba->dev, "phy power on failed, ret = %d\n", err); +- return err; ++ if (!keep_phy) { ++ err = phy_power_on(phy); ++ if (err) { ++ dev_err(hba->dev, "phy power on failed, ret = %d\n", err); ++ return err; ++ } + } + + /* enable the device ref clock for HS mode*/ diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch new file mode 100644 index 00000000000..6074339dcd9 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch @@ -0,0 +1,160 @@ +From: jaewun +Subject: [PATCH] phy: qcom-qmp-ufs: allow UFS hosts to control RX LineCfg + +Qualcomm downstream UFS hosts keep PHY RX LineCfg enabled for link startup and +disable it immediately afterward. The downstream comment says some UFS devices +send incorrect LineCfg during power-mode-change, which can put the host PHY in +an invalid state. + +Add a QMP UFS helper for the host driver to toggle the PCS LINECFG_DISABLE bit +and call it from the existing Qualcomm link-startup PRE/POST hooks. + +This keeps RX LineCfg enabled while the link starts, then disables host RX +LineCfg before post-startup power-mode-change traffic. + +Signed-off-by: jaewun + +--- a/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c ++++ b/drivers/phy/qualcomm/phy-qcom-qmp-ufs.c +@@ -14,5 +14,6 @@ + #include + #include ++#include + #include + #include + #include +@@ -40,12 +41,16 @@ + #define NUM_OVERLAY 2 + ++/* QPHY LINECFG_DISABLE bit */ ++#define RX_LINECFG_DISABLE BIT(1) ++ + /* set of registers with offsets different per-PHY */ + enum qphy_reg_layout { + /* PCS registers */ + QPHY_SW_RESET, + QPHY_START_CTRL, + QPHY_PCS_READY_STATUS, + QPHY_PCS_POWER_DOWN_CONTROL, ++ QPHY_LINECFG_DISABLE, + /* Keep last to ensure regs_layout arrays are properly initialized */ + QPHY_LAYOUT_SIZE + }; +@@ -66,6 +71,7 @@ static const unsigned int ufsphy_v4_regs_layout[QPHY_LAYOUT_SIZE] = { + [QPHY_PCS_READY_STATUS] = QPHY_V4_PCS_UFS_READY_STATUS, + [QPHY_SW_RESET] = QPHY_V4_PCS_UFS_SW_RESET, + [QPHY_PCS_POWER_DOWN_CONTROL] = QPHY_V4_PCS_UFS_POWER_DOWN_CONTROL, ++ [QPHY_LINECFG_DISABLE] = QPHY_V4_PCS_UFS_LINECFG_DISABLE, + }; + + static const unsigned int ufsphy_v5_regs_layout[QPHY_LAYOUT_SIZE] = { +@@ -81,6 +87,7 @@ static const unsigned int ufsphy_v6_regs_layout[QPHY_LAYOUT_SIZE] = { + [QPHY_PCS_READY_STATUS] = QPHY_V6_PCS_UFS_READY_STATUS, + [QPHY_SW_RESET] = QPHY_V6_PCS_UFS_SW_RESET, + [QPHY_PCS_POWER_DOWN_CONTROL] = QPHY_V6_PCS_UFS_POWER_DOWN_CONTROL, ++ [QPHY_LINECFG_DISABLE] = QPHY_V6_PCS_UFS_LINECFG_DISABLE, + }; + + static const struct qmp_phy_init_tbl milos_ufsphy_serdes[] = { +@@ -1920,6 +1927,39 @@ static void qmp_ufs_init(struct qmp_ufs *qmp) + qmp_ufs_init_all(qmp, &cfg->tbls_hs_b); + } + ++int qcom_qmp_ufs_ctrl_rx_linecfg(struct phy *phy, bool enable) ++{ ++ struct qmp_ufs *qmp; ++ const struct qmp_phy_cfg *cfg; ++ u32 offset; ++ u32 before; ++ u32 after; ++ ++ if (!phy) ++ return -ENODEV; ++ ++ qmp = phy_get_drvdata(phy); ++ if (!qmp || !qmp->pcs) ++ return -ENODEV; ++ ++ cfg = qmp->cfg; ++ offset = cfg->regs[QPHY_LINECFG_DISABLE]; ++ if (!offset) ++ return -EOPNOTSUPP; ++ ++ before = readl(qmp->pcs + offset); ++ after = enable ? before & ~RX_LINECFG_DISABLE : ++ before | RX_LINECFG_DISABLE; ++ writel(after, qmp->pcs + offset); ++ after = readl(qmp->pcs + offset); ++ ++ dev_dbg(qmp->dev, "UFS PHY RX LineCfg %s: reg=0x%x -> 0x%x\n", ++ enable ? "enabled" : "disabled", before, after); ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(qcom_qmp_ufs_ctrl_rx_linecfg); ++ + static int qmp_ufs_power_on(struct phy *phy) + { + struct qmp_ufs *qmp = phy_get_drvdata(phy); +--- /dev/null ++++ b/include/linux/phy/phy-qcom-qmp-ufs.h +@@ -0,0 +1,20 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef __PHY_QCOM_QMP_UFS_H__ ++#define __PHY_QCOM_QMP_UFS_H__ ++ ++#include ++#include ++#include ++ ++struct phy; ++ ++#if IS_ENABLED(CONFIG_PHY_QCOM_QMP_UFS) ++int qcom_qmp_ufs_ctrl_rx_linecfg(struct phy *phy, bool enable); ++#else ++static inline int qcom_qmp_ufs_ctrl_rx_linecfg(struct phy *phy, bool enable) ++{ ++ return -EOPNOTSUPP; ++} ++#endif ++ ++#endif +--- a/drivers/ufs/host/ufs-qcom.c ++++ b/drivers/ufs/host/ufs-qcom.c +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -715,9 +716,16 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, + enum ufs_notify_change_status status) + { ++ struct ufs_qcom_host *host = ufshcd_get_variant(hba); + int err = 0; ++ int linecfg_err; + + switch (status) { + case PRE_CHANGE: ++ linecfg_err = qcom_qmp_ufs_ctrl_rx_linecfg(host->generic_phy, true); ++ if (linecfg_err && linecfg_err != -EOPNOTSUPP) ++ dev_warn(hba->dev, "failed to enable RX LineCfg: %d\n", ++ linecfg_err); ++ + if (ufs_qcom_cfg_timers(hba, false, ULONG_MAX)) { + dev_err(hba->dev, "%s: ufs_qcom_cfg_timers() failed\n", + __func__); +@@ -738,5 +746,11 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, + err = ufshcd_disable_host_tx_lcc(hba); + + break; ++ case POST_CHANGE: ++ linecfg_err = qcom_qmp_ufs_ctrl_rx_linecfg(host->generic_phy, false); ++ if (linecfg_err && linecfg_err != -EOPNOTSUPP) ++ dev_warn(hba->dev, "failed to disable RX LineCfg: %d\n", ++ linecfg_err); ++ break; + default: + break; From 2f630040f96032790cce2624cfe2908fa8b27315 Mon Sep 17 00:00:00 2001 From: Luke Johnson Date: Wed, 1 Jul 2026 15:55:25 +1200 Subject: [PATCH 3/4] SM8550: adjust suspend wake IRQ handling Add the TSENS and qcom-ipcc patches used by the SM8550 suspend path. The TSENS patch leaves AYN Thor uplow threshold IRQs as non-wakeup. Critical thermal wake remains enabled. The IPCC patch lets the PM core mask the parent IRQ during suspend instead of keeping it enabled with IRQF_NO_SUSPEND. --- ...m-tsens-skip-ayn-thor-uplow-wake-irq.patch | 56 +++++++++++++++++++ ...eup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch | 24 ++++++++ 2 files changed, 80 insertions(+) create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0203-thermal-qcom-tsens-skip-ayn-thor-uplow-wake-irq.patch create mode 100644 projects/ROCKNIX/devices/SM8550/patches/linux/0502-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0203-thermal-qcom-tsens-skip-ayn-thor-uplow-wake-irq.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0203-thermal-qcom-tsens-skip-ayn-thor-uplow-wake-irq.patch new file mode 100644 index 00000000000..d1c45d39bca --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0203-thermal-qcom-tsens-skip-ayn-thor-uplow-wake-irq.patch @@ -0,0 +1,56 @@ +From 2b4adfaf66434bddbde753c3ae7229d8c73a0ea5 Mon Sep 17 00:00:00 2001 +From: jaewun +Date: Mon, 11 May 2026 01:20:00 +1200 +Subject: [PATCH] thermal: qcom: tsens: skip AYN Thor uplow wake IRQs + +On AYN Thor, the SM8550 TSENS uplow/passive threshold IRQ is not a useful +system wake source and can immediately wake the system from suspend. + +Keep normal TSENS interrupt handling active and keep critical TSENS wake +enabled. Only skip enable_irq_wake() for the ordinary "uplow" TSENS IRQs on +ayn,thor. + +Signed-off-by: jaewun +--- + drivers/thermal/qcom/tsens.c | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c +index fbdab9d00000..fbdab9d00001 100644 +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -1174,6 +1174,15 @@ static const struct thermal_zone_device_ops tsens_of_ops = { + .set_trips = tsens_set_trips, + }; + ++static bool tsens_irq_wake_enabled(const char *irqname) ++{ ++ if (!of_machine_is_compatible("ayn,thor")) ++ return true; ++ ++ /* Keep critical thermal wake; only passive threshold-window IRQs are noisy. */ ++ return strcmp(irqname, "uplow") != 0; ++} ++ + static int tsens_register_irq(struct tsens_priv *priv, char *irqname, + irq_handler_t thread_fn) + { +@@ -1203,11 +1212,15 @@ static int tsens_register_irq(struct tsens_priv *priv, char *irqname, + + if (ret) + dev_err(&pdev->dev, "%s: failed to get irq\n", + __func__); +- else ++ else if (tsens_irq_wake_enabled(irqname)) + enable_irq_wake(irq); ++ else ++ dev_info(&pdev->dev, ++ "leaving TSENS %s IRQ %d as non-wakeup on AYN Thor\n", ++ irqname, irq); + } + + put_device(&pdev->dev); + return ret; + } +-- +2.50.0 diff --git a/projects/ROCKNIX/devices/SM8550/patches/linux/0502-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch b/projects/ROCKNIX/devices/SM8550/patches/linux/0502-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch new file mode 100644 index 00000000000..41ca4806731 --- /dev/null +++ b/projects/ROCKNIX/devices/SM8550/patches/linux/0502-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch @@ -0,0 +1,24 @@ +From: jaewun +Subject: [PATCH] mailbox: qcom-ipcc: drop IRQF_NO_SUSPEND + +IRQF_NO_SUSPEND keeps the IPCC parent IRQ enabled through +suspend_device_irqs(). It is not wake configuration. + +Let the PM core mask the IRQ during suspend; wake-capable users should use the +normal wake IRQ path. + +Signed-off-by: jaewun + +diff --git a/drivers/mailbox/qcom-ipcc.c b/drivers/mailbox/qcom-ipcc.c +index d957d989c..2e4bb5efb 100644 +--- a/drivers/mailbox/qcom-ipcc.c ++++ b/drivers/mailbox/qcom-ipcc.c +@@ -321,7 +321,7 @@ static int qcom_ipcc_probe(struct platform_device *pdev) + goto err_mbox; + + ret = devm_request_irq(&pdev->dev, ipcc->irq, qcom_ipcc_irq_fn, +- IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | ++ IRQF_TRIGGER_HIGH | + IRQF_NO_THREAD, name, ipcc); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to register the irq: %d\n", ret); From abcc2bc87ea41416e88ba2c15557842c460193f9 Mon Sep 17 00:00:00 2001 From: Luke Johnson Date: Wed, 1 Jul 2026 15:55:36 +1200 Subject: [PATCH 4/4] SM8550: enable deep suspend policy Default SM8550 devices to mem suspend and wire power, suspend, and lid handling through logind. Also set the UFS command timeout and deep sleep default on the SM8550 kernel command line. --- projects/ROCKNIX/devices/SM8550/options | 2 +- .../quirks/platforms/SM8550/030-suspend_mode | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/projects/ROCKNIX/devices/SM8550/options b/projects/ROCKNIX/devices/SM8550/options index 7e9a0f46e54..8f33f334016 100644 --- a/projects/ROCKNIX/devices/SM8550/options +++ b/projects/ROCKNIX/devices/SM8550/options @@ -28,7 +28,7 @@ KERNEL_MAKE_EXTRACMD=" $(get_kernel_make_extracmd)" # Kernel cmdline - EXTRA_CMDLINE="quiet rootwait console=tty0 allow_mismatched_32bit_el0 fw_devlink.strict=1 pcie_ports=compat irqaffinity=0-2 cgroup.memory=nokmem,nosocket nosoftlockup usbcore.interrupt_interval_override=045e:028e:2" + EXTRA_CMDLINE="quiet rootwait console=tty0 allow_mismatched_32bit_el0 fw_devlink.strict=1 pcie_ports=compat irqaffinity=0-2 cgroup.memory=nokmem,nosocket nosoftlockup usbcore.interrupt_interval_override=045e:028e:2 ufshcd_core.uic_cmd_timeout=3000 mem_sleep_default=deep" # Bootloader to use (syslinux / u-boot) BOOTLOADER="qcom-abl" diff --git a/projects/ROCKNIX/packages/hardware/quirks/platforms/SM8550/030-suspend_mode b/projects/ROCKNIX/packages/hardware/quirks/platforms/SM8550/030-suspend_mode index 4f773ea5546..22981ff90b6 100755 --- a/projects/ROCKNIX/packages/hardware/quirks/platforms/SM8550/030-suspend_mode +++ b/projects/ROCKNIX/packages/hardware/quirks/platforms/SM8550/030-suspend_mode @@ -1,14 +1,31 @@ #!/bin/sh # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2023 JELOS (https://github.com/JustEnoughLinuxOS) +# Copyright (C) 2026 ROCKNIX (https://github.com/ROCKNIX) -### Sleep is currently broken, so we'll disable it. +. /etc/profile.d/001-functions -/usr/bin/suspendmode off +# Default SM8550 devices to mem suspend and keep systemd on that state. +mkdir -p /storage/.config/sleep.conf.d +cat </storage/.config/sleep.conf.d/zz-sm8550-deeponly.conf +[Sleep] +SuspendState= +SuspendState=mem +EOF -### Ignore power button presses for now, until we can finish up fixing sleep. cat <~/.config/logind.conf.d/login.conf [Login] -HandlePowerKey=ignore -HandleSuspendKey=ignore +HandlePowerKey=suspend +HandleSuspendKey=suspend +HandleLidSwitch=suspend +HandleLidSwitchExternalPower=suspend +HandleLidSwitchDocked=ignore EOF + +MYSLEEPMODE=$(get_setting system.suspendmode) +if [ -z "${MYSLEEPMODE}" ] || [ "${MYSLEEPMODE}" = "off" ] +then + /usr/bin/suspendmode mem +fi + +systemctl try-restart systemd-logind.service 2>/dev/null || true