From 27ceae9095978cee71e4d758559dc71cd8773413 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Fri, 31 Jul 2026 11:08:20 -0400 Subject: [PATCH 01/11] Port ROCKNIX deep-sleep kernel fixes --- Justfile | 8 +- kernel/PATCHES.md | 32 +- kernel/build.sh | 2 +- ...in-relink-completions-out-of-band-pm.patch | 423 ++++++++++++++++++ ...com-tsens-skip-sm8550-uplow-wake-irq.patch | 57 +++ ...qcom-balance-irq-on-host-reset-error.patch | 55 +++ ...m-resume-to-reinit-mcu-after-suspend.patch | 28 -- ...t-rsinput-suspend-resume-gamepad-mcu.patch | 64 +++ ...geni-mask-non-console-irq-on-suspend.patch | 69 +++ ...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 +++++++ kernel/patches/series | 12 +- 14 files changed, 1026 insertions(+), 36 deletions(-) create mode 100644 kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch create mode 100644 kernel/patches/0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch create mode 100644 kernel/patches/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch delete mode 100644 kernel/patches/0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch create mode 100644 kernel/patches/1004-input-rsinput-suspend-resume-gamepad-mcu.patch create mode 100644 kernel/patches/1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch create mode 100644 kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch create mode 100644 kernel/patches/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch create mode 100644 kernel/patches/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch create mode 100644 kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch diff --git a/Justfile b/Justfile index b1184a3..a22b50f 100644 --- a/Justfile +++ b/Justfile @@ -28,11 +28,13 @@ image pkg: (artifacts pkg) set -euo pipefail bash scripts/stage.sh {{pkg}} # mesa-android cross-builds on x86_64, so label its carrier for the target. - arch_flag=() if [ "{{pkg}}" = "mesa-android" ]; then - arch_flag=(--arch arm64) + podman build --arch arm64 -f oci/Containerfile -t "{{registry}}/{{pkg}}:latest" . + else + # Avoid expanding an empty array under `set -u`; macOS Bash 3.2 treats + # that as an unbound variable. + podman build -f oci/Containerfile -t "{{registry}}/{{pkg}}:latest" . fi - buildah build "${arch_flag[@]}" -f oci/Containerfile -t "{{registry}}/{{pkg}}:latest" . echo "==> {{registry}}/{{pkg}}:latest" # Build artifacts for every package diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index ce97f5a..7247a68 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -136,9 +136,6 @@ no equivalent submission was found, or a permanent URL to the upstream submissio source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/packages/linux/patches/mainline/0006-hid-playstation-expose-DualSense-Edge-Fn-and-back-paddles.patch upstream: https://lore.kernel.org/r/20260407044008.40222-1-awebster@gmail.com notes: Armada refreshed only the hunk context for Linux 7.1; DualSense Edge button behavior is unchanged. -- `patches/0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch` - source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/devices/SM8750/patches/linux/0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch - upstream: unknown - `patches/0504-Enable-64-bit-processes-to-use-compat-input-syscalls.patch` source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/devices/SM8250/patches/linux/0504-Enable-64-bit-processes-to-use-compat-input-syscalls.patch upstream: unknown @@ -168,6 +165,25 @@ no equivalent submission was found, or a permanent URL to the upstream submissio source: armada upstream: local notes: The imported bridge called the haptics driver's sleeping upload, playback, and erase callbacks under spin_lock_irq, which emitted "BUG: scheduling while atomic" on every rumble stop and intermittently hard-locked the Odin 3 (reproduced on hardware). The bridge now serializes with a mutex and is process-context only, RSInput defers its atomic playback callback to a work item, and the haptics suspend path cancels the pending stop and set-gain workers so they cannot fire into resume. ROCKNIX carries the identical bug as of 2026-08-04; worth upstreaming once soak-tested. +- `patches/1004-input-rsinput-suspend-resume-gamepad-mcu.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1004-input-rsinput-suspend-resume-gamepad-mcu.patch + upstream: unknown + notes: Supersedes the resume-only SM8750 patch previously carried as `0508`. +- `patches/1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch + upstream: unknown +- `patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch + upstream: unknown +- `patches/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch + upstream: unknown +- `patches/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch + upstream: unknown +- `patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch + upstream: unknown - `patches/1300-input-rsinput-axis-deadzone.patch` source: armada upstream: local @@ -229,6 +245,16 @@ no equivalent submission was found, or a permanent URL to the upstream submissio - `patches/0200-ASoC-wcd938x-add-DMIC-DAPM-inputs.patch` source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/devices/SM8550/patches/linux/0200-ASoC-wcd938x-add-DMIC-DAPM-inputs.patch upstream: unknown +- `patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch + upstream: unknown + notes: Armada carries ROCKNIX's out-of-band completion drain for system PM. +- `patches/0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch + upstream: unknown +- `patches/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch` + source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch + upstream: unknown - `patches/0500-ROCKNIX-set-boot-fanspeed.patch` source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/devices/SM8550/patches/linux/0500-ROCKNIX-set-boot-fanspeed.patch upstream: unknown diff --git a/kernel/build.sh b/kernel/build.sh index a05f31f..130cbef 100755 --- a/kernel/build.sh +++ b/kernel/build.sh @@ -1,4 +1,4 @@ -#!/usr/bin/bash +#!/usr/bin/env bash set -euxo pipefail diff --git a/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch b/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch new file mode 100644 index 0000000..02bb82a --- /dev/null +++ b/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch @@ -0,0 +1,423 @@ +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 + +[armada: rebase ufshcd_intr() onto Linux 7.0.11, before the +active_uic_cmd threaded-IRQ handoff change present in ROCKNIX's 7.1.2 base.] +--- +--- 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 +@@ -7207,6 +7317,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/kernel/patches/0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch b/kernel/patches/0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch new file mode 100644 index 0000000..3942457 --- /dev/null +++ b/kernel/patches/0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch @@ -0,0 +1,57 @@ +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 SM8550 uplow wake IRQs + +Native s2idle testing on SM8550 devices repeatedly resumes immediately with +pm_wakeup_irq pointing at the SM8550 TSENS uplow/passive threshold IRQ. +The IRQ wake state exposed under /sys/kernel/irq is read-only on the live +kernel, so runtime testing cannot disable this wake source. + +Keep normal TSENS interrupt handling active and keep critical TSENS wake +enabled. Only skip enable_irq_wake() for the ordinary "uplow" TSENS IRQs on +qcom,sm8550. Scoped to the SoC-level compatible (not a specific board) so it +covers every SM8550 board, not just the one it was first observed on. +--- + 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("qcom,sm8550")) ++ 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 SM8550\n", ++ irqname, irq); + } + + put_device(&pdev->dev); + return ret; + } +-- +2.50.0 diff --git a/kernel/patches/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch b/kernel/patches/0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch new file mode 100644 index 0000000..625deb1 --- /dev/null +++ b/kernel/patches/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/kernel/patches/0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch b/kernel/patches/0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch deleted file mode 100644 index 6a14afd..0000000 --- a/kernel/patches/0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch +++ /dev/null @@ -1,28 +0,0 @@ -diff --git a/drivers/input/joystick/rsinput.c b/drivers/input/joystick/rsinput.c -index 457cd4c43..ca43a19bd 100644 ---- a/drivers/input/joystick/rsinput.c -+++ b/drivers/input/joystick/rsinput.c -@@ -557,6 +557,15 @@ static void rsinput_remove(struct serdev_device *serdev) { - regulator_disable(drv->vdd); - } - -+static int rsinput_resume(struct device *dev) -+{ -+ struct rsinput_driver *drv = dev_get_drvdata(dev); -+ -+ return rsinput_init_commands(drv); -+} -+ -+static DEFINE_SIMPLE_DEV_PM_OPS(rsinput_pm_ops, NULL, rsinput_resume); -+ - static const struct of_device_id rsinput_of_match[] = { - { .compatible = "gamepad,rsinput" }, - { /* sentinel */ } -@@ -569,6 +578,7 @@ static struct serdev_device_driver rsinput_driver = { - .driver = { - .name = "rsinput", - .of_match_table = rsinput_of_match, -+ .pm = pm_sleep_ptr(&rsinput_pm_ops), - }, - }; - diff --git a/kernel/patches/1004-input-rsinput-suspend-resume-gamepad-mcu.patch b/kernel/patches/1004-input-rsinput-suspend-resume-gamepad-mcu.patch new file mode 100644 index 0000000..83a293a --- /dev/null +++ b/kernel/patches/1004-input-rsinput-suspend-resume-gamepad-mcu.patch @@ -0,0 +1,64 @@ +From: jaewun +Subject: [PATCH] input: rsinput: suspend and resume the gamepad MCU across system sleep + +The gamepad MCU streams input reports continuously over the UART. Across a +system-suspend teardown the MCU keeps sending, so the geni-serial RX interrupt +storms while the port is stopping, the primary handler returns IRQ_NONE, and the +kernel's spurious-IRQ detector permanently disables the UART IRQ -- the gamepad +is then dead after resume until the IRQ is re-requested. + +Add PM ops: on suspend quiesce the MCU (drop its enable rail and assert reset, +mirroring rsinput_remove) so the line goes silent before the UART suspends; on +resume re-power and re-initialise it. + +Signed-off-by: jaewun +--- +--- a/drivers/input/joystick/rsinput.c ++++ b/drivers/input/joystick/rsinput.c +@@ -612,6 +612,38 @@ + regulator_disable(drv->vdd); + } + ++static int rsinput_suspend(struct device *dev) ++{ ++ struct rsinput_driver *drv = dev_get_drvdata(dev); ++ ++ /* ++ * The MCU streams gamepad reports continuously over the UART. If it keeps ++ * sending across the system-suspend teardown, the geni-serial RX interrupt ++ * storms while the port is being stopped, the primary handler returns ++ * IRQ_NONE, and the kernel's spurious-IRQ detector permanently disables the ++ * UART IRQ -- leaving the gamepad dead after resume until the IRQ is ++ * re-requested. Quiesce the MCU (drop its enable rail and hold it in reset, ++ * mirroring rsinput_remove) so the line goes silent before the UART ++ * suspends; rsinput_resume() re-powers and re-initialises it. ++ */ ++ if (drv->enable_gpio) ++ gpiod_set_value_cansleep(drv->enable_gpio, 0); ++ ++ if (drv->reset_gpio) ++ gpiod_set_value_cansleep(drv->reset_gpio, 0); ++ ++ return 0; ++} ++ ++static int rsinput_resume(struct device *dev) ++{ ++ struct rsinput_driver *drv = dev_get_drvdata(dev); ++ ++ return rsinput_init_commands(drv); ++} ++ ++static DEFINE_SIMPLE_DEV_PM_OPS(rsinput_pm_ops, rsinput_suspend, rsinput_resume); ++ + static const struct of_device_id rsinput_of_match[] = { + { .compatible = "gamepad,rsinput" }, + { /* sentinel */ } +@@ -624,6 +656,7 @@ + .driver = { + .name = "rsinput", + .of_match_table = rsinput_of_match, ++ .pm = pm_sleep_ptr(&rsinput_pm_ops), + }, + }; + diff --git a/kernel/patches/1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch b/kernel/patches/1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch new file mode 100644 index 0000000..6738c87 --- /dev/null +++ b/kernel/patches/1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch @@ -0,0 +1,69 @@ +From: jaewun +Subject: [PATCH] tty: serial: qcom-geni: mask non-console IRQ across system suspend + +A peripheral that streams continuously over a non-console geni UART -- the AYN +gamepad MCU on 89c000.serial (irq 190) -- keeps transmitting across the +system-suspend teardown. uart_suspend_port() sets uport->suspended = 1 before +the line is quiet, and qcom_geni_serial_isr() then returns IRQ_NONE for every +subsequent interrupt WITHOUT clearing the GENI IRQ status. The level-triggered +line immediately re-asserts and storms; the kernel's spurious-interrupt detector +fires ("irq 190: nobody cared") and permanently disables the IRQ. That kills the +gamepad after resume, and the wedged geni state cascades into a hard suspend hang +on the following cycle ("first suspend works, the next one wedges"). + +The existing rsinput_suspend() MCU quiesce (1005) cannot prevent this on Thor: +the gamepad node has no reset-gpios, so only enable-gpios is toggled and the MCU +keeps its UART TX alive; the live trace confirms the storm still happens. + +Fix it at the controller: mask the IRQ at the GIC in qcom_geni_serial_suspend() +for non-console ports, before uart_suspend_port() runs. At that point +uport->suspended is still 0, so the ISR still services and clears the IRQ; +disable_irq() -> synchronize_irq() therefore drains the last in-flight handler +and returns, and the masked line delivers no further hardirqs, so the storm can +never reach the spurious detector. Balance it with enable_irq() in +qcom_geni_serial_resume(), after uart_resume_port() has re-initialised the port. + +The console UART (uart_console()) is deliberately left untouched. The non-console +geni UARTs here are not wakeup sources (no wakeup-source in DT, device_may_wakeup() +is false), so the enable_irq_wake() early-return in uart_suspend_port() is not +taken; the disable/enable pair is self-balanced regardless. + +Signed-off-by: jaewun +--- +diff --git a/drivers/tty/serial/qcom_geni_serial.c b/drivers/tty/serial/qcom_geni_serial.c +--- a/drivers/tty/serial/qcom_geni_serial.c ++++ b/drivers/tty/serial/qcom_geni_serial.c +@@ -1956,6 +1956,23 @@ + struct qcom_geni_private_data *private_data = uport->private_data; + + /* ++ * A peer that keeps streaming into a non-console geni UART across the ++ * suspend teardown (e.g. the gamepad MCU on irq 190) storms the RX IRQ ++ * once uart_suspend_port() sets uport->suspended: the ISR then returns ++ * IRQ_NONE without clearing the line, the level IRQ re-asserts, and the ++ * spurious-IRQ detector ("nobody cared") permanently disables it -- which ++ * kills the gamepad after resume and wedges the next suspend. Mask it ++ * here, while the ISR still services and acks the IRQ, so synchronize_irq() ++ * drains the last handler cleanly and the masked line delivers no further ++ * hardirqs. These geni UARTs are not wakeup sources, so the ++ * device_may_wakeup() early-return in uart_suspend_port() is not taken and ++ * this disable_irq() is balanced by enable_irq() in ++ * qcom_geni_serial_resume(). ++ */ ++ if (!uart_console(uport)) ++ disable_irq(uport->irq); ++ ++ /* + * This is done so we can hit the lowest possible state in suspend + * even with no_console_suspend + */ +@@ -1974,6 +1991,9 @@ + struct qcom_geni_private_data *private_data = uport->private_data; + + ret = uart_resume_port(private_data->drv, uport); ++ /* Balance the disable_irq() taken in qcom_geni_serial_suspend(). */ ++ if (!uart_console(uport)) ++ enable_irq(uport->irq); + if (uart_console(uport)) { + geni_icc_set_tag(&port->se, QCOM_ICC_TAG_ALWAYS); + geni_icc_set_bw(&port->se); diff --git a/kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch b/kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch new file mode 100644 index 0000000..912fb14 --- /dev/null +++ b/kernel/patches/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/kernel/patches/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch b/kernel/patches/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch new file mode 100644 index 0000000..a8c47fc --- /dev/null +++ b/kernel/patches/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/kernel/patches/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch b/kernel/patches/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch new file mode 100644 index 0000000..17733b2 --- /dev/null +++ b/kernel/patches/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/kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch b/kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch new file mode 100644 index 0000000..6074339 --- /dev/null +++ b/kernel/patches/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; diff --git a/kernel/patches/series b/kernel/patches/series index a470e09..9dc4f7e 100644 --- a/kernel/patches/series +++ b/kernel/patches/series @@ -46,7 +46,6 @@ 0069-input-misc-add-konkr-sysbtn-MCU-system-buttons.patch 0031_input--Add-driver-for-RSInput-Gamepad.patch 0006-hid-playstation-expose-DualSense-Edge-Fn-and-back-paddles.patch -0508-input-rsinput-add-pm-resume-to-reinit-mcu-after-suspend.patch 0504-Enable-64-bit-processes-to-use-compat-input-syscalls.patch 0506-usbcore-add-interrupt-interval-override.patch @@ -80,6 +79,17 @@ v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 0504-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch 0505-msm_gem-lock-before-put_iova_spaces.patch +# ============== SM8550 deep suspend (ROCKNIX PR #2954) ============== +0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch +0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch +0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch +1004-input-rsinput-suspend-resume-gamepad-mcu.patch +1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch +1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch +1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch +1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch +1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch + # ============== SM8550 DTSI prerequisites ============== 0001-pcie-update-sm8550-dtsi.patch 0101-v3_20260219_webgeek1234_arm64_qcom_sm8550_add_ddr_llcc_l3_cpu_bandwidth_scaling.patch From 2275305ddea13abb59f9e428299f8c84dfafc79a Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Sat, 1 Aug 2026 12:04:58 -0400 Subject: [PATCH 02/11] Recover UFS clock-gating failures --- kernel/PATCHES.md | 4 + ...ufs-recover-hibern8-enter-clk-gating.patch | 92 +++++++++++++++++++ kernel/patches/series | 1 + 3 files changed, 97 insertions(+) create mode 100644 kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index 7247a68..00cfd97 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -178,6 +178,10 @@ no equivalent submission was found, or a permanent URL to the upstream submissio - `patches/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch` source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch upstream: unknown +- `patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch` + source: armada + upstream: local + notes: Recovers a timed-out software Hibern8 enter inline, then disables runtime UFS clock gating for the remainder of the boot so storage remains usable. - `patches/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch` source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch upstream: unknown diff --git a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch new file mode 100644 index 0000000..d3fe92a --- /dev/null +++ b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch @@ -0,0 +1,92 @@ +From: Armada OS +Subject: [PATCH] scsi: ufs: recover clock-gating hibern8-enter failures inline + +ufshcd_gate_work() only restores the software clock-gating state to CLKS_ON +when DME_HIBER_ENTER times out. It does not repair the UFS link. This is +especially destructive while system PM is in progress: pm_op_in_progress +intentionally suppresses the generic fatal error handler, so the controller +is left operational in software but unable to complete device commands. +Subsequent filesystem I/O repeatedly times out and a following suspend can +block forever in the filesystem sync, after userspace has already blanked the +display. + +Recover a failed clock-gating hibern8 enter inline, matching the recovery +already used for clock-gating hibern8-exit failures by the SM8550 PM relink +patch. Keep clk_gating.is_suspended set around the UIC command and recovery +so the generic UIC error path cannot race a second error-handler reset when +the failure occurs outside system PM. Arm the non-MCQ relink poller while the +link is reset so completion does not depend on the threaded ONESHOT IRQ. + +After the first failure, pin runtime UFS clock gating off for the remainder of +the boot. This is the same active-request pin used by the clkgate_enable +sysfs control. System PM remains enabled and can still transition the UFS +device and link for suspend, while the failed idle Hibern8 path is not retried. + +Signed-off-by: Armada OS +--- +--- a/drivers/ufs/core/ufshcd.c ++++ b/drivers/ufs/core/ufshcd.c +@@ -2036,16 +2036,62 @@ static void ufshcd_gate_work(struct work_struct *work) + + /* put the link into hibern8 mode before turning off clocks */ + if (ufshcd_can_hibern8_during_gating(hba)) { ++ /* ++ * Suppress the generic fatal UIC error path while this worker owns ++ * recovery. System PM already does this through pm_op_in_progress, ++ * but an ordinary idle-gating failure needs the same exclusion. ++ */ ++ hba->clk_gating.is_suspended = true; + ret = ufshcd_uic_hibern8_enter(hba); + if (ret) { + hba->clk_gating.state = CLKS_ON; +- dev_err(hba->dev, "%s: hibern8 enter failed %d\n", ++ dev_err(hba->dev, ++ "%s: hibern8 enter failed %d, recovering link\n", + __func__, ret); + trace_ufshcd_clk_gating(hba, + hba->clk_gating.state); ++ ++ /* ++ * A non-MCQ relink cannot rely on this controller's threaded ++ * IRQ making forward progress across PM. Use the out-of-band ++ * completion poller installed by the SM8550 relink patch. ++ */ ++ 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); ++ } ++ ++ /* ++ * Do not retry a destructive idle-Hibern8 transition this boot. ++ * Pinning active_reqs is how clkgate_enable=0 keeps normal ++ * hold/release pairs balanced while clock gating is disabled. ++ */ ++ scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) { ++ if (hba->clk_gating.is_enabled) { ++ hba->clk_gating.active_reqs++; ++ hba->clk_gating.is_enabled = false; ++ } ++ } ++ hba->clk_gating.is_suspended = false; ++ ++ if (ret) ++ dev_err(hba->dev, ++ "%s: link recovery after hibern8 enter failed %d\n", ++ __func__, ret); ++ else ++ dev_warn(hba->dev, ++ "%s: link recovered; runtime clock gating disabled\n", ++ __func__); + return; + } + ufshcd_set_link_hibern8(hba); ++ hba->clk_gating.is_suspended = false; + } + + ufshcd_disable_irq(hba); diff --git a/kernel/patches/series b/kernel/patches/series index 9dc4f7e..6d3fba1 100644 --- a/kernel/patches/series +++ b/kernel/patches/series @@ -87,6 +87,7 @@ v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch 1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch 1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch +1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch 1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch 1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch From aff0cbc95d14b5e58f20162effe9f08aa5d2206c Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Sat, 1 Aug 2026 12:17:26 -0400 Subject: [PATCH 03/11] Add UFS gate-race failure diagnostics --- ...ufs-recover-hibern8-enter-clk-gating.patch | 80 ++++++++++++++++++- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch index d3fe92a..52fc0d6 100644 --- a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch +++ b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch @@ -7,8 +7,8 @@ especially destructive while system PM is in progress: pm_op_in_progress intentionally suppresses the generic fatal error handler, so the controller is left operational in software but unable to complete device commands. Subsequent filesystem I/O repeatedly times out and a following suspend can -block forever in the filesystem sync, after userspace has already blanked the -display. +block forever in the filesystem sync, after userspace has already blanked +the display. Recover a failed clock-gating hibern8 enter inline, matching the recovery already used for clock-gating hibern8-exit failures by the SM8550 PM relink @@ -22,14 +22,61 @@ the boot. This is the same active-request pin used by the clkgate_enable sysfs control. System PM remains enabled and can still transition the UFS device and link for suspend, while the failed idle Hibern8 path is not retried. +Capture the clock-gating and PM state when the worker is admitted, immediately +before DME_HIBER_ENTER, and after a failure. Emit the snapshots only on the +failure path. This makes it possible to distinguish a controller failure +from a race where system suspend requests clocks back on after the gate worker +has passed its initial eligibility check. + Signed-off-by: Armada OS --- --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c -@@ -2036,16 +2036,62 @@ static void ufshcd_gate_work(struct work_struct *work) +@@ -2056,6 +2056,10 @@ static void ufshcd_gate_work(struct work_struct *work) + { + struct ufs_hba *hba = container_of(work, struct ufs_hba, + clk_gating.gate_work.work); ++ enum clk_gating_state admitted_state; ++ int admitted_active_reqs; ++ bool admitted_pm; ++ bool admitted_system_suspend; + int ret; + + scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) { +@@ -2075,6 +2079,16 @@ static void ufshcd_gate_work(struct work_struct *work) + + if (hba->clk_gating.active_reqs) + return; ++ ++ /* ++ * Preserve the state that admitted this worker. A suspend request ++ * can race after this lock is dropped and change the gating state ++ * before the Hibern8 command is sent. ++ */ ++ admitted_state = hba->clk_gating.state; ++ admitted_active_reqs = hba->clk_gating.active_reqs; ++ admitted_pm = READ_ONCE(hba->pm_op_in_progress); ++ admitted_system_suspend = READ_ONCE(hba->system_suspending); + } + + scoped_guard(spinlock_irqsave, hba->host->host_lock) { +@@ -2085,16 +2099,99 @@ static void ufshcd_gate_work(struct work_struct *work) /* put the link into hibern8 mode before turning off clocks */ if (ufshcd_can_hibern8_during_gating(hba)) { ++ enum clk_gating_state h8_start_state; ++ int h8_start_active_reqs; ++ bool h8_start_pm; ++ bool h8_start_system_suspend; ++ ++ /* Snapshot any state transition since the worker was admitted. */ ++ scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) { ++ h8_start_state = hba->clk_gating.state; ++ h8_start_active_reqs = hba->clk_gating.active_reqs; ++ } ++ h8_start_pm = READ_ONCE(hba->pm_op_in_progress); ++ h8_start_system_suspend = READ_ONCE(hba->system_suspending); ++ + /* + * Suppress the generic fatal UIC error path while this worker owns + * recovery. System PM already does this through pm_op_in_progress, @@ -38,11 +85,36 @@ Signed-off-by: Armada OS + hba->clk_gating.is_suspended = true; ret = ufshcd_uic_hibern8_enter(hba); if (ret) { - hba->clk_gating.state = CLKS_ON; +- hba->clk_gating.state = CLKS_ON; - dev_err(hba->dev, "%s: hibern8 enter failed %d\n", ++ enum clk_gating_state failed_state; ++ int failed_active_reqs; ++ bool failed_enabled; ++ bool failed_suspended; ++ ++ scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) { ++ failed_state = hba->clk_gating.state; ++ failed_active_reqs = hba->clk_gating.active_reqs; ++ failed_enabled = hba->clk_gating.is_enabled; ++ failed_suspended = hba->clk_gating.is_suspended; ++ hba->clk_gating.state = CLKS_ON; ++ } + dev_err(hba->dev, + "%s: hibern8 enter failed %d, recovering link\n", __func__, ret); ++ dev_err(hba->dev, ++ "%s: gate race snapshot: admitted state=%d active=%d pm=%d system=%d; h8-start state=%d active=%d pm=%d system=%d; failed state=%d active=%d enabled=%d suspended=%d pm=%d system=%d outstanding=%#lx gate-work=%u ungate-work=%u\n", ++ __func__, admitted_state, admitted_active_reqs, ++ admitted_pm, admitted_system_suspend, ++ h8_start_state, h8_start_active_reqs, ++ h8_start_pm, h8_start_system_suspend, ++ failed_state, failed_active_reqs, ++ failed_enabled, failed_suspended, ++ READ_ONCE(hba->pm_op_in_progress), ++ READ_ONCE(hba->system_suspending), ++ READ_ONCE(hba->outstanding_reqs), ++ work_busy(&hba->clk_gating.gate_work.work), ++ work_busy(&hba->clk_gating.ungate_work)); trace_ufshcd_clk_gating(hba, hba->clk_gating.state); + From b097724ec20b3f20d31dd4cc291e0d5d9238c491 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Sat, 1 Aug 2026 15:59:36 -0400 Subject: [PATCH 04/11] Serialize UFS clock gating with system PM --- kernel/PATCHES.md | 4 + ...ufs-hold-clk-gating-across-system-pm.patch | 96 +++++++++++++++++++ kernel/patches/series | 1 + 3 files changed, 101 insertions(+) create mode 100644 kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index 00cfd97..ae7845e 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -185,6 +185,10 @@ no equivalent submission was found, or a permanent URL to the upstream submissio - `patches/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch` source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch upstream: unknown +- `patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch` + source: armada + upstream: local + notes: Holds runtime clock gating from system-PM prepare through resume complete so an asynchronous idle Hibern8 command cannot overlap device suspend or resume. - `patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch` source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch upstream: unknown diff --git a/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch b/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch new file mode 100644 index 0000000..d9a9000 --- /dev/null +++ b/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch @@ -0,0 +1,96 @@ +From: Armada OS +Subject: [PATCH] scsi: ufs: hold clock gating across system PM + +Hardware diagnostics captured a software clock-gating worker entering +DME_HIBER_ENTER with REQ_CLKS_OFF, no active requests, and no PM operation +in progress. While that command was still running, system suspend reached +UFS: pm_op_in_progress became set, ufshcd_hold() changed the state to +REQ_CLKS_ON, and the ungate worker became pending behind the running gate +worker. The UIC command then timed out while the PM path waited for that +worker. + +Serialize these paths at the existing system-PM prepare/complete boundary. +When the well-known LUN is not already runtime suspended in the exact +system suspend state, take a clock-gating hold after runtime resume in +ufshcd_suspend_prepare(). This cancels or drains any pending gate worker +while normal completion handling is available. Retain the hold across all +device suspend and resume callbacks, then release it from +ufshcd_resume_complete(). + +The normal UFS suspend path keeps its own balanced hold, so this extra pin +does not prevent explicit system power transitions. It only prevents the +asynchronous idle-gating Hibern8 path from starting inside the system-PM +window. Devices already runtime suspended in the desired system state +need no extra hold and remain untouched. + +Signed-off-by: Armada OS +--- +--- a/drivers/ufs/core/ufshcd.c ++++ b/drivers/ufs/core/ufshcd.c +@@ -11439,6 +11439,10 @@ void ufshcd_resume_complete(struct device *dev) + { + struct ufs_hba *hba = dev_get_drvdata(dev); + ++ if (hba->complete_clkgate_hold) { ++ hba->complete_clkgate_hold = false; ++ ufshcd_release(hba); ++ } + if (hba->complete_put) { + ufshcd_rpm_put(hba); + hba->complete_put = false; +@@ -11470,5 +11474,6 @@ int __ufshcd_suspend_prepare(struct device *dev, bool rpm_ok_for_spm) + { + struct ufs_hba *hba = dev_get_drvdata(dev); ++ bool rpm_ready_for_spm; + int ret; + + /* +@@ -11480,17 +11485,28 @@ int __ufshcd_suspend_prepare(struct device *dev, bool rpm_ok_for_spm) + if (hba->ufs_device_wlun) { + /* Prevent runtime suspend */ + ufshcd_rpm_get_noresume(hba); ++ rpm_ready_for_spm = rpm_ok_for_spm && ufshcd_rpm_ok_for_spm(hba); + /* + * Check if already runtime suspended in same state as system + * suspend would be. + */ +- if (!rpm_ok_for_spm || !ufshcd_rpm_ok_for_spm(hba)) { ++ if (!rpm_ready_for_spm) { + /* RPM state is not ok for SPM, so runtime resume */ + ret = ufshcd_rpm_resume(hba); + if (ret < 0 && ret != -EACCES) { + ufshcd_rpm_put(hba); + return ret; + } ++ ++ /* ++ * Serialize system PM against the asynchronous clock-gating ++ * worker. ufshcd_hold() cancels or drains a pending gate while ++ * normal IRQ completion is still available. Keeping this hold ++ * until .complete prevents a new idle Hibern8 command from racing ++ * any suspend or resume phase. ++ */ ++ ufshcd_hold(hba); ++ hba->complete_clkgate_hold = true; + } + hba->complete_put = true; + } +--- a/include/ufs/ufshcd.h ++++ b/include/ufs/ufshcd.h +@@ -929,6 +929,8 @@ struct ufs_hba { + * @nr_queues: number of Queues of different queue types + * @complete_put: whether or not to call ufshcd_rpm_put() from inside + * ufshcd_resume_complete() ++ * @complete_clkgate_hold: whether ufshcd_resume_complete() must release ++ * clock-gating hold acquired by ufshcd_suspend_prepare() + * @mcq_sup: is mcq supported by UFSHC + * @mcq_enabled: is mcq ready to accept requests + * @mcq_esi_enabled: is mcq ESI configured +@@ -1110,6 +1112,7 @@ struct ufs_hba { + unsigned int nr_hw_queues; + unsigned int nr_queues[HCTX_MAX_TYPES]; + bool complete_put; ++ bool complete_clkgate_hold; + bool scsi_host_added; + bool mcq_sup; + bool lsdb_sup; diff --git a/kernel/patches/series b/kernel/patches/series index 6d3fba1..399c0e3 100644 --- a/kernel/patches/series +++ b/kernel/patches/series @@ -89,6 +89,7 @@ v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch 1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch 1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch +1011-scsi-ufs-hold-clk-gating-across-system-pm.patch 1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch # ============== SM8550 DTSI prerequisites ============== From 117ca833f2dff1354c91cc639068885936d9776c Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Sat, 1 Aug 2026 16:07:29 -0400 Subject: [PATCH 05/11] Attribute Armada UFS fixes to gh123man --- .../1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch | 4 ++-- .../1011-scsi-ufs-hold-clk-gating-across-system-pm.patch | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch index 52fc0d6..5800692 100644 --- a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch +++ b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch @@ -1,4 +1,4 @@ -From: Armada OS +From: gh123man Subject: [PATCH] scsi: ufs: recover clock-gating hibern8-enter failures inline ufshcd_gate_work() only restores the software clock-gating state to CLKS_ON @@ -28,7 +28,7 @@ failure path. This makes it possible to distinguish a controller failure from a race where system suspend requests clocks back on after the gate worker has passed its initial eligibility check. -Signed-off-by: Armada OS +Signed-off-by: gh123man --- --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c diff --git a/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch b/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch index d9a9000..b54dc5d 100644 --- a/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch +++ b/kernel/patches/1011-scsi-ufs-hold-clk-gating-across-system-pm.patch @@ -1,4 +1,4 @@ -From: Armada OS +From: gh123man Subject: [PATCH] scsi: ufs: hold clock gating across system PM Hardware diagnostics captured a software clock-gating worker entering @@ -23,7 +23,7 @@ asynchronous idle-gating Hibern8 path from starting inside the system-PM window. Devices already runtime suspended in the desired system state need no extra hold and remain untouched. -Signed-off-by: Armada OS +Signed-off-by: gh123man --- --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c From 6da7bf7f0022363c9ba4f579fdf3bc6aef70a8a6 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Sat, 1 Aug 2026 18:10:42 -0400 Subject: [PATCH 06/11] Enable charger attach wake from deep sleep --- kernel/PATCHES.md | 8 ++ kernel/dts/qcs8550-retroidpocket-rp6.dts | 5 ++ ...506-irqchip-qcom-ipcc-propagate-wake.patch | 39 ++++++++++ ...sg-qcom-glink-smem-support-wake-irqs.patch | 77 +++++++++++++++++++ kernel/patches/series | 2 + 5 files changed, 131 insertions(+) create mode 100644 kernel/patches/0506-irqchip-qcom-ipcc-propagate-wake.patch create mode 100644 kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index ae7845e..53f9391 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -229,6 +229,14 @@ no equivalent submission was found, or a permanent URL to the upstream submissio source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/devices/SM8750/patches/linux/0504-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch upstream: unknown notes: Armada limits ROCKNIX's suspend IRQ change to `qcom,sm8750-ipcc`, preserving the original flags on the SM8250 devices that also use suspend-to-RAM. +- `patches/0506-irqchip-qcom-ipcc-propagate-wake.patch` + source: armada + upstream: local + notes: Propagates selected IPCC child wake configuration to the shared GIC parent. Validated on Retroid Pocket 6, where a charger attached during deep sleep wakes the AP through the selected ADSP GLINK edge. +- `patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch` + source: armada + upstream: local + notes: Replaces unconditional GLINK `IRQF_NO_SUSPEND` with opt-in device-tree wake IRQs, deferring GLINK client handling until orderly resume instead of running callbacks against suspended devices. - `patches/0505-msm_gem-lock-before-put_iova_spaces.patch` source: https://github.com/ROCKNIX/distribution/blob/bcf3b5bc574990b96543484575b06f912153a715/projects/ROCKNIX/devices/SM8250/patches/linux/0505-msm_gem-lock-before-put_iova_spaces.patch upstream: unknown diff --git a/kernel/dts/qcs8550-retroidpocket-rp6.dts b/kernel/dts/qcs8550-retroidpocket-rp6.dts index 9e646c9..3bb3d91 100644 --- a/kernel/dts/qcs8550-retroidpocket-rp6.dts +++ b/kernel/dts/qcs8550-retroidpocket-rp6.dts @@ -384,6 +384,11 @@ status = "okay"; }; +/* Wake cleanly so the ADSP charger service can process a new USB-C attach. */ +&remoteproc_adsp_glink { + wakeup-source; +}; + &spk_amp_l { firmware-name = "qcom/sm8550/ayn/odin2/aw883xx_acf.bin"; }; diff --git a/kernel/patches/0506-irqchip-qcom-ipcc-propagate-wake.patch b/kernel/patches/0506-irqchip-qcom-ipcc-propagate-wake.patch new file mode 100644 index 0000000..428608d --- /dev/null +++ b/kernel/patches/0506-irqchip-qcom-ipcc-propagate-wake.patch @@ -0,0 +1,39 @@ +From: gh123man +Subject: [PATCH] mailbox: qcom-ipcc: propagate child wake configuration + +IPCC child interrupts currently advertise IRQCHIP_SKIP_SET_WAKE, so a client +cannot arm the GIC summary interrupt as a wake source. This forces GLINK +transports to rely on IRQF_NO_SUSPEND and allows callbacks to run against +already-suspended devices. + +Propagate wake configuration from an IPCC child to the shared parent IRQ. +The interrupt core masks non-wakeup children during suspend, while the GIC +summary remains armed as long as at least one child needs it. + +Signed-off-by: gh123man +--- + drivers/mailbox/qcom-ipcc.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +--- a/drivers/mailbox/qcom-ipcc.c ++++ b/drivers/mailbox/qcom-ipcc.c +@@ -107,11 +107,18 @@ static void qcom_ipcc_unmask_irq(struct irq_data *irqd) + writel(hwirq, ipcc->base + IPCC_REG_RECV_SIGNAL_ENABLE); + } + ++static int qcom_ipcc_set_wake(struct irq_data *irqd, unsigned int on) ++{ ++ struct qcom_ipcc *ipcc = irq_data_get_irq_chip_data(irqd); ++ ++ return irq_set_irq_wake(ipcc->irq, on); ++} ++ + static struct irq_chip qcom_ipcc_irq_chip = { + .name = "ipcc", + .irq_mask = qcom_ipcc_mask_irq, + .irq_unmask = qcom_ipcc_unmask_irq, +- .flags = IRQCHIP_SKIP_SET_WAKE, ++ .irq_set_wake = qcom_ipcc_set_wake, + }; + + static int qcom_ipcc_domain_map(struct irq_domain *d, unsigned int irq, diff --git a/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch b/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch new file mode 100644 index 0000000..67411aa --- /dev/null +++ b/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch @@ -0,0 +1,77 @@ +From: gh123man +Subject: [PATCH] rpmsg: qcom: glink-smem: support dedicated wake edges + +GLINK SMEM currently requests every transport interrupt with +IRQF_NO_SUSPEND. On IPCC-backed systems this either allows arbitrary GLINK +client callbacks while their devices are suspended, or requires disabling +the shared IPCC summary interrupt and prevents selected transports from +waking the application processor. + +Make the transport interrupts ordinary suspend-managed interrupts. When a +GLINK edge is explicitly described as a wakeup source, register its existing +interrupt as the device wake IRQ instead. The IRQ core can then leave only +that edge armed, wake the system, and defer normal client handling until the +orderly resume path. + +Signed-off-by: gh123man +--- + drivers/rpmsg/qcom_glink_smem.c | 23 ++++++++++++++++++++++-- + 1 file changed, 22 insertions(+), 1 deletion(-) + +--- a/drivers/rpmsg/qcom_glink_smem.c ++++ b/drivers/rpmsg/qcom_glink_smem.c +@@ -7,7 +7,8 @@ + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -306,7 +307,7 @@ struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent, + + smem->irq = of_irq_get(smem->dev.of_node, 0); + ret = devm_request_irq(&smem->dev, smem->irq, qcom_glink_smem_intr, +- IRQF_NO_SUSPEND | IRQF_NO_AUTOEN, ++ IRQF_NO_AUTOEN, + "glink-smem", smem); + if (ret) { + dev_err(&smem->dev, "failed to request IRQ\n"); +@@ -348,8 +349,24 @@ struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent, + + enable_irq(smem->irq); + ++ if (of_property_read_bool(dev->of_node, "wakeup-source")) { ++ ret = device_init_wakeup(dev, true); ++ if (ret) ++ goto err_disable_irq; ++ ++ ret = dev_pm_set_wake_irq(dev, smem->irq); ++ if (ret) { ++ device_init_wakeup(dev, false); ++ goto err_disable_irq; ++ } ++ } ++ + return smem; + ++err_disable_irq: ++ disable_irq(smem->irq); ++ qcom_glink_native_remove(glink); ++ + err_free_mbox: + mbox_free_channel(smem->mbox_chan); + +@@ -365,6 +382,11 @@ void qcom_glink_smem_unregister(struct qcom_glink_smem *smem) + + disable_irq(smem->irq); + ++ if (device_can_wakeup(&smem->dev)) { ++ dev_pm_clear_wake_irq(&smem->dev); ++ device_init_wakeup(&smem->dev, false); ++ } ++ + qcom_glink_native_remove(glink); + + mbox_free_channel(smem->mbox_chan); diff --git a/kernel/patches/series b/kernel/patches/series index 399c0e3..a6507f3 100644 --- a/kernel/patches/series +++ b/kernel/patches/series @@ -77,6 +77,8 @@ 20260424_neil_armstrong_arm64_dts_qcom_sm8_456_50_add_missing_cx_power_domain_to_gcc.patch v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 0504-wakeup-qcom-ipcc-remove-IRQF-NO-SUSPEND.patch +0506-irqchip-qcom-ipcc-propagate-wake.patch +0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch 0505-msm_gem-lock-before-put_iova_spaces.patch # ============== SM8550 deep suspend (ROCKNIX PR #2954) ============== From a6122182e6ee3985daf1a3d7c349e2dc4d463a0c Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Sun, 2 Aug 2026 20:01:07 -0400 Subject: [PATCH 07/11] Expose PM wake reasons for charger policy --- kernel/PATCHES.md | 2 +- kernel/config/armada-kernel.config.overrides | 5 +++++ kernel/dts/qcs8550-retroidpocket-rp6.dts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index 53f9391..b2f37bf 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -232,7 +232,7 @@ no equivalent submission was found, or a permanent URL to the upstream submissio - `patches/0506-irqchip-qcom-ipcc-propagate-wake.patch` source: armada upstream: local - notes: Propagates selected IPCC child wake configuration to the shared GIC parent. Validated on Retroid Pocket 6, where a charger attached during deep sleep wakes the AP through the selected ADSP GLINK edge. + notes: Propagates selected IPCC child wake configuration to the shared GIC parent. Validated on Retroid Pocket 6; it mirrors Qualcomm Android's broad IPCC transport wake, with userspace filtering unrelated GLINK traffic before the graphical session is thawed. - `patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch` source: armada upstream: local diff --git a/kernel/config/armada-kernel.config.overrides b/kernel/config/armada-kernel.config.overrides index da86eac..b6cd03a 100644 --- a/kernel/config/armada-kernel.config.overrides +++ b/kernel/config/armada-kernel.config.overrides @@ -201,6 +201,11 @@ CONFIG_KPROBES=y CONFIG_KPROBE_EVENTS=y CONFIG_PERF_EVENTS=y CONFIG_BPF_EVENTS=y +# Exposes /sys/power/pm_wakeup_irq for the Armada suspend wake policy. These +# options add PM diagnostic controls; they do not enable a debug build or force +# verbose PM logging (pm_debug_messages and pm_print_times remain off). +CONFIG_PM_DEBUG=y +CONFIG_PM_SLEEP_DEBUG=y CONFIG_NTSYNC=y # Proton NT sync; off in arm64 defconfig CONFIG_LRU_GEN=y CONFIG_LRU_GEN_ENABLED=y # bare LRU_GEN only builds MGLRU, this enables it diff --git a/kernel/dts/qcs8550-retroidpocket-rp6.dts b/kernel/dts/qcs8550-retroidpocket-rp6.dts index 3bb3d91..e4aa6dd 100644 --- a/kernel/dts/qcs8550-retroidpocket-rp6.dts +++ b/kernel/dts/qcs8550-retroidpocket-rp6.dts @@ -384,7 +384,7 @@ status = "okay"; }; -/* Wake cleanly so the ADSP charger service can process a new USB-C attach. */ +/* The ADSP owns Type-C and charging state through the PMIC GLINK service. */ &remoteproc_adsp_glink { wakeup-source; }; From 4a28583737d8d557577937d729e29da138d35359 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Wed, 5 Aug 2026 16:27:54 -0400 Subject: [PATCH 08/11] kernel: port Thorch RP6 PCIe s2idle support --- kernel/PATCHES.md | 37 ++- kernel/dts/qcs8550-ayn-common.dtsi.patch | 43 ++- ...i-qcom-set-max-opp-before-dbi-resume.patch | 100 ++++++ ...-pci-host-common-add-d3cold-possible.patch | 144 +++++++++ .../patches/1018-pci-qcom-add-get-ltssm.patch | 124 ++++++++ .../1019-pci-qcom-power-down-phy.patch | 140 +++++++++ ...020-pci-dwc-use-common-d3cold-helper.patch | 120 ++++++++ .../1021-pci-qcom-add-d3cold-support.patch | 284 ++++++++++++++++++ ...qcom-skip-l23-ready-after-pme-sm8550.patch | 32 ++ ...-qcom-rpmh-add-suspend-state-support.patch | 251 ++++++++++++++++ ...ator-core-apply-mem-state-for-s2idle.patch | 32 ++ ...i-qcom-use-suspend-opp-for-non-s2ram.patch | 93 ++++++ ...ts-qcom-sm8550-mark-pcie-suspend-opp.patch | 114 +++++++ kernel/patches/series | 11 + 14 files changed, 1520 insertions(+), 5 deletions(-) create mode 100644 kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch create mode 100644 kernel/patches/1017-pci-host-common-add-d3cold-possible.patch create mode 100644 kernel/patches/1018-pci-qcom-add-get-ltssm.patch create mode 100644 kernel/patches/1019-pci-qcom-power-down-phy.patch create mode 100644 kernel/patches/1020-pci-dwc-use-common-d3cold-helper.patch create mode 100644 kernel/patches/1021-pci-qcom-add-d3cold-support.patch create mode 100644 kernel/patches/1023-pci-qcom-skip-l23-ready-after-pme-sm8550.patch create mode 100644 kernel/patches/1024-regulator-qcom-rpmh-add-suspend-state-support.patch create mode 100644 kernel/patches/1025-regulator-core-apply-mem-state-for-s2idle.patch create mode 100644 kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch create mode 100644 kernel/patches/1027-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index b2f37bf..914c8e4 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -192,6 +192,41 @@ no equivalent submission was found, or a permanent URL to the upstream submissio - `patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch` source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch upstream: unknown +- `patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch` + source: https://github.com/torvalds/linux/commit/5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 + upstream: https://github.com/torvalds/linux/commit/5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 +- `patches/1017-pci-host-common-add-d3cold-possible.patch` + source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0210-PCI-host-common-add-d3cold-eligibility-helper.patch + upstream: unknown + notes: Uses the hardware-validated endpoint-only eligibility semantics so an unbound host root port under `pcie_ports=compat` cannot block D3cold. +- `patches/1018-pci-qcom-add-get-ltssm.patch` + source: https://github.com/torvalds/linux/commit/131a93dbcb9546683384e31dc4057d4aaf38fa21 + upstream: https://github.com/torvalds/linux/commit/131a93dbcb9546683384e31dc4057d4aaf38fa21 +- `patches/1019-pci-qcom-power-down-phy.patch` + source: https://github.com/torvalds/linux/commit/8a847d3e9e5f1700beb5a0196e682f71837dfe5c + upstream: https://github.com/torvalds/linux/commit/8a847d3e9e5f1700beb5a0196e682f71837dfe5c +- `patches/1020-pci-dwc-use-common-d3cold-helper.patch` + source: https://github.com/torvalds/linux/commit/56378c03c1a80aeeab45f39b303cc92a3bb7716e + upstream: https://github.com/torvalds/linux/commit/56378c03c1a80aeeab45f39b303cc92a3bb7716e +- `patches/1021-pci-qcom-add-d3cold-support.patch` + source: https://github.com/torvalds/linux/commit/2cc0e7454c7891345f92e96b2f812b808be7fbdb + upstream: https://github.com/torvalds/linux/commit/2cc0e7454c7891345f92e96b2f812b808be7fbdb +- `patches/1023-pci-qcom-skip-l23-ready-after-pme-sm8550.patch` + source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0215-PCI-qcom-skip-l23-ready-after-pme-sm8550.patch + upstream: unknown +- `patches/1024-regulator-qcom-rpmh-add-suspend-state-support.patch` + source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0218-regulator-qcom-rpmh-add-suspend-state-support.patch + upstream: unknown +- `patches/1025-regulator-core-apply-mem-state-for-s2idle.patch` + source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0219-regulator-core-apply-mem-state-for-s2idle.patch + upstream: unknown +- `patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch` + source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0220-PCI-qcom-use-suspend-opp-for-non-s2ram.patch + upstream: unknown +- `patches/1027-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch` + source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0221-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch + upstream: unknown + notes: Retains the RP6-validated 5 MB/s DDR/LLCC wake contract during s2idle without changing any active-link OPP. - `patches/1300-input-rsinput-axis-deadzone.patch` source: armada upstream: local @@ -536,7 +571,7 @@ no equivalent submission was found, or a permanent URL to the upstream submissio notes: Armada applies this local patch after copying `dts/qcs8550-ayaneo-pocketds.dts`. - `dts/qcs8550-ayn-common.dtsi.patch` source: armada - notes: Armada removes the SDHCI capability mask and marks the shared RSInput node as connected to the PM8550B haptics device declared in the same common tree. This intentionally covers the AYN and Retroid products that inherit both nodes, including Pocket 6 and Nova. + notes: Armada removes the SDHCI capability mask, enables the shared haptics bridge, and includes Thorch's RP6-validated regulator sleep states and active-low WLAN WAKE# polarity. This covers the AYN and Retroid products that inherit the common tree, including Pocket 6 and Nova. - `dts/qcs8550-retroidpocket-rp6.dts.patch` source: armada notes: Armada switches Pocket 6 from ROCKNIX's Odin 2 fallback to audio firmware extracted from a Pocket 6 vendor image. diff --git a/kernel/dts/qcs8550-ayn-common.dtsi.patch b/kernel/dts/qcs8550-ayn-common.dtsi.patch index c7b5984..dfee8a4 100644 --- a/kernel/dts/qcs8550-ayn-common.dtsi.patch +++ b/kernel/dts/qcs8550-ayn-common.dtsi.patch @@ -1,17 +1,52 @@ From: Armada -Subject: [PATCH] arm64: dts: qcom: qcs8550-ayn: enable SDHCI capabilities and rumble +Subject: [PATCH] arm64: dts: qcom: qcs8550-ayn: add local hardware fixes + +Apply Armada's SDHCI and haptics corrections together with the SM8550 s2idle +power constraints validated by Thorch on the Retroid Pocket 6. --- a/arch/arm64/boot/dts/qcom/qcs8550-ayn-common.dtsi +++ b/arch/arm64/boot/dts/qcom/qcs8550-ayn-common.dtsi -@@ -1462,7 +1462,6 @@ - no-mmc; +@@ -596,6 +596,10 @@ + regulator-min-microvolt = <2720000>; + regulator-max-microvolt = <3960000>; + regulator-initial-mode = ; ++ ++ regulator-state-mem { ++ regulator-off-in-suspend; ++ }; + }; + + vreg_l2b_3p0: ldo2 { +@@ -700,6 +704,11 @@ + regulator-allow-set-load; + regulator-allowed-modes = ; ++ ++ regulator-state-mem { ++ regulator-on-in-suspend; ++ regulator-mode = ; ++ }; + }; + + vreg_l16b_2p8: ldo16 { +@@ -1098,7 +1107,7 @@ + }; + + &pcieport0 { +- wake-gpios = <&tlmm 96 GPIO_ACTIVE_HIGH>; ++ wake-gpios = <&tlmm 96 GPIO_ACTIVE_LOW>; + reset-gpios = <&tlmm 94 GPIO_ACTIVE_LOW>; + + wifi@0 { +@@ -1462,7 +1471,6 @@ + no-mmc; qcom,dll-config = <0x0007442c>; - sdhci-caps-mask = <0x3 0x0>; status = "okay"; }; -@@ -1700,6 +1699,7 @@ +@@ -1701,6 +1709,7 @@ gamepad: gamepad { compatible = "gamepad,rsinput"; diff --git a/kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch b/kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch new file mode 100644 index 0000000..17c9376 --- /dev/null +++ b/kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch @@ -0,0 +1,100 @@ +From 5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 Mon Sep 17 00:00:00 2001 +From: Qiang Yu +Date: Thu, 16 Apr 2026 21:16:25 -0700 +Subject: [PATCH] PCI: qcom: Set max OPP before DBI access during resume + +During resume, qcom_pcie_icc_opp_update() may access DBI registers before +the OPP votes are restored, triggering NoC errors. + +Set the PCIe controller to the maximum OPP first in resume_noirq(), then +proceed with link/DBI accesses. The OPP is later updated again based on +the actual link bandwidth requirements. + +Introduce a helper to reuse the max-OPP setup code and share it with +probe(). + +Fixes: 5b6272e0efd5 ("PCI: qcom: Add OPP support to scale performance") +Signed-off-by: Qiang Yu +[mani: commit log and error log rewording] +Signed-off-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20260416-setmaxopp-v1-1-6a74e2d945a0@oss.qualcomm.com +--- + drivers/pci/controller/dwc/pcie-qcom.c | 42 ++++++++++++++++---------- + 1 file changed, 26 insertions(+), 16 deletions(-) + +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index af6bf5cce65bee..f21f3806fc1f4b 100644 +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -1613,6 +1613,22 @@ static void qcom_pcie_icc_opp_update(struct qcom_pcie *pcie) + } + } + ++static int qcom_pcie_set_max_opp(struct device *dev) ++{ ++ unsigned long max_freq = ULONG_MAX; ++ struct dev_pm_opp *opp; ++ int ret; ++ ++ opp = dev_pm_opp_find_freq_floor(dev, &max_freq); ++ if (IS_ERR(opp)) ++ return PTR_ERR(opp); ++ ++ ret = dev_pm_opp_set_opp(dev, opp); ++ dev_pm_opp_put(opp); ++ ++ return ret; ++} ++ + static int qcom_pcie_link_transition_count(struct seq_file *s, void *data) + { + struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private); +@@ -1845,9 +1861,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) + struct qcom_pcie_perst *perst, *tmp_perst; + struct qcom_pcie_port *port, *tmp_port; + const struct qcom_pcie_cfg *pcie_cfg; +- unsigned long max_freq = ULONG_MAX; + struct device *dev = &pdev->dev; +- struct dev_pm_opp *opp; + struct qcom_pcie *pcie; + struct dw_pcie_rp *pp; + struct resource *res; +@@ -1951,21 +1965,9 @@ static int qcom_pcie_probe(struct platform_device *pdev) + * probe(), OPP will be updated using qcom_pcie_icc_opp_update(). + */ + if (!ret) { +- opp = dev_pm_opp_find_freq_floor(dev, &max_freq); +- if (IS_ERR(opp)) { +- ret = PTR_ERR(opp); +- dev_err_probe(pci->dev, ret, +- "Unable to find max freq OPP\n"); +- goto err_pm_runtime_put; +- } else { +- ret = dev_pm_opp_set_opp(dev, opp); +- } +- +- dev_pm_opp_put(opp); ++ ret = qcom_pcie_set_max_opp(dev); + if (ret) { +- dev_err_probe(pci->dev, ret, +- "Failed to set OPP for freq %lu\n", +- max_freq); ++ dev_err_probe(dev, ret, "Failed to set max OPP\n"); + goto err_pm_runtime_put; + } + +@@ -2100,6 +2102,14 @@ static int qcom_pcie_resume_noirq(struct device *dev) + return 0; + + if (pm_suspend_target_state != PM_SUSPEND_MEM) { ++ if (pcie->use_pm_opp) { ++ ret = qcom_pcie_set_max_opp(dev); ++ if (ret) { ++ dev_err(dev, "Failed to set max OPP: %d\n", ret); ++ return ret; ++ } ++ } ++ + ret = icc_enable(pcie->icc_cpu); + if (ret) { + dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret); diff --git a/kernel/patches/1017-pci-host-common-add-d3cold-possible.patch b/kernel/patches/1017-pci-host-common-add-d3cold-possible.patch new file mode 100644 index 0000000..78006f6 --- /dev/null +++ b/kernel/patches/1017-pci-host-common-add-d3cold-possible.patch @@ -0,0 +1,144 @@ +From: Krishna Chaitanya Chundru +Date: Wed, 29 Apr 2026 12:12:23 +0530 +Subject: [PATCH] PCI: host-common: Add helper to determine host bridge D3cold eligibility + +Add a common helper, pci_host_common_d3cold_possible(), to determine +whether PCIe devices under host bridge can safely transition to D3cold. + +This helper is intended to be used by PCI host controller drivers to +decide whether they may safely put the host bridge into D3cold based on +the power state and wakeup capabilities of downstream endpoints. + +The helper walks all devices on the all bridge buses and only allows +the devices to enter D3cold if all PCIe endpoints are already in +PCI_D3hot. This ensures that we do not power off the host bridge while +any active endpoint still requires the link to remain powered. + +For devices that may wake the system, the helper additionally requires +that the device supports PME wake from D3cold (via WAKE#). Devices that +do not have wakeup enabled are not restricted by this check and do not +block the devices under host bridge from entering D3cold. + +Devices without a bound driver and with PCI not enabled via sysfs are +treated as inactive and therefore do not prevent the devices under host +bridge from entering D3cold. This allows controllers to power down more +aggressively when there are no actively managed endpoints. + +Some devices (e.g. M.2 without auxiliary power) lose PME detection when +main power is removed. Even if such devices advertise PME-from-D3cold +capability, entering D3cold may break wakeup. So, return PME-from-D3cold +capability via an output parameter so PCIe controller drivers can apply +platform-specific handling to preserve wakeup functionality. + +Signed-off-by: Krishna Chaitanya Chundru + +[Thorch backport: mainline "PCI: qcom: Add D3cold support" v5 series, patch 1/5. + Needed so the Qualcomm PCIe host can use the D3cold suspend path on Linux 7.1.2. + Ref: https://lore.kernel.org/all/20260429-d3cold-v5-1-89e9735b9df6@oss.qualcomm.com/ ] +--- + drivers/pci/controller/pci-host-common.c | 71 ++++++++++++++++++++++++++++++++ + drivers/pci/controller/pci-host-common.h | 2 + + 2 files changed, 73 insertions(+) + +diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c +index d6258c1cffe5..09432d69175c 100644 +--- a/drivers/pci/controller/pci-host-common.c ++++ b/drivers/pci/controller/pci-host-common.c +@@ -17,6 +17,9 @@ + + #include "pci-host-common.h" + ++#define PCI_HOST_D3COLD_ALLOWED BIT(0) ++#define PCI_HOST_PME_D3COLD_CAPABLE BIT(1) ++ + static void gen_pci_unmap_cfg(void *ptr) + { + pci_ecam_free((struct pci_config_window *)ptr); +@@ -106,5 +109,73 @@ void pci_host_common_remove(struct platform_device *pdev) + } + EXPORT_SYMBOL_GPL(pci_host_common_remove); + ++static int __pci_host_common_d3cold_possible(struct pci_dev *pdev, void *userdata) ++{ ++ u32 *flags = userdata; ++ int type; ++ ++ /* Ignore conventional PCI devices */ ++ if (!pci_is_pcie(pdev)) ++ return 0; ++ ++ type = pci_pcie_type(pdev); ++ if (type != PCI_EXP_TYPE_ENDPOINT && ++ type != PCI_EXP_TYPE_LEG_END && ++ type != PCI_EXP_TYPE_RC_END) ++ return 0; ++ ++ if (!pdev->dev.driver && !pci_is_enabled(pdev)) ++ return 0; ++ ++ if (pdev->current_state != PCI_D3hot) ++ goto exit; ++ ++ if (device_may_wakeup(&pdev->dev)) { ++ if (!pci_pme_capable(pdev, PCI_D3cold)) ++ goto exit; ++ else ++ *flags |= PCI_HOST_PME_D3COLD_CAPABLE; ++ } ++ ++ return 0; ++ ++exit: ++ *flags &= ~PCI_HOST_D3COLD_ALLOWED; ++ ++ return -EOPNOTSUPP; ++} ++ ++/** ++ * pci_host_common_d3cold_possible - Determine whether the host bridge can transition the ++ * devices into D3Cold. ++ * ++ * @bridge: PCI host bridge to check ++ * @pme_capable: Pointer to update if there is any device which is capable of generating ++ * PME from D3cold. ++ * ++ * Walk downstream PCIe endpoint devices and determine whether the host bridge ++ * is permitted to transition the devices into D3cold. ++ * ++ * Devices under host bridge can enter D3cold only if all active PCIe endpoints are in ++ * PCI_D3hot and any wakeup-enabled endpoint is capable of generating PME from D3cold. ++ * Inactive endpoints are ignored. ++ * ++ * The @pme_capable output allows PCIe controller drivers to apply ++ * platform-specific handling to preserve wakeup functionality. ++ * ++ * Return: %true if the host bridge may enter D3cold, otherwise %false. ++ */ ++bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, bool *pme_capable) ++{ ++ u32 flags = PCI_HOST_D3COLD_ALLOWED; ++ ++ pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &flags); ++ ++ *pme_capable = !!(flags & PCI_HOST_PME_D3COLD_CAPABLE); ++ ++ return !!(flags & PCI_HOST_D3COLD_ALLOWED); ++} ++EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible); ++ + MODULE_DESCRIPTION("Common library for PCI host controller drivers"); + MODULE_LICENSE("GPL v2"); +diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h +index b5075d4bd7eb..7eb5599b9ce4 100644 +--- a/drivers/pci/controller/pci-host-common.h ++++ b/drivers/pci/controller/pci-host-common.h +@@ -20,4 +20,6 @@ void pci_host_common_remove(struct platform_device *pdev); + + struct pci_config_window *pci_host_common_ecam_create(struct device *dev, + struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops); ++ ++bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, bool *pme_capable); + #endif + +-- +2.34.1 diff --git a/kernel/patches/1018-pci-qcom-add-get-ltssm.patch b/kernel/patches/1018-pci-qcom-add-get-ltssm.patch new file mode 100644 index 0000000..76ce3c6 --- /dev/null +++ b/kernel/patches/1018-pci-qcom-add-get-ltssm.patch @@ -0,0 +1,124 @@ +From 131a93dbcb9546683384e31dc4057d4aaf38fa21 Mon Sep 17 00:00:00 2001 +From: Krishna Chaitanya Chundru +Date: Wed, 29 Apr 2026 12:12:24 +0530 +Subject: [PATCH] PCI: qcom: Add .get_ltssm() callback to query LTSSM status + +For older SoCs like SC7280, reading DBI LTSSM register after sending +PME_Turn_Off message causes NOC error. + +To avoid unsafe DBI accesses, introduce qcom_pcie_get_ltssm() to retrieve +the LTSSM state without DBI. For newer platforms, read the LTSSM state from +the PARF_LTSSM register; for older platforms continue to retrieve it from +ELBI_SYS_STTS. + +This helper is used in place of direct DBI-based link state checks in the +D3cold path after sending PME_Turn_Off message, ensuring the LTSSM state +can be queried safely even after DBI access is no longer valid. + +Signed-off-by: Krishna Chaitanya Chundru +[mani: commit log and fixed get_ltssm() check] +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20260429-d3cold-v5-2-89e9735b9df6@oss.qualcomm.com +--- + drivers/pci/controller/dwc/pcie-qcom.c | 31 ++++++++++++++++++++++++++ + 1 file changed, 31 insertions(+) + +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index f21f3806fc1f4b..881746efe3b6ec 100644 +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -71,6 +71,7 @@ + + /* ELBI registers */ + #define ELBI_SYS_CTRL 0x04 ++#define ELBI_SYS_STTS 0x08 + + /* DBI registers */ + #define AXI_MSTR_RESP_COMP_CTRL0 0x818 +@@ -131,6 +132,7 @@ + + /* PARF_LTSSM register fields */ + #define LTSSM_EN BIT(8) ++#define PARF_LTSSM_STATE_MASK GENMASK(5, 0) + + /* PARF_NO_SNOOP_OVERRIDE register fields */ + #define WR_NO_SNOOP_OVERRIDE_EN BIT(1) +@@ -145,6 +147,9 @@ + /* ELBI_SYS_CTRL register fields */ + #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) + ++/* ELBI_SYS_STTS register fields */ ++#define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12) ++ + /* AXI_MSTR_RESP_COMP_CTRL0 register fields */ + #define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_2K 0x4 + #define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_4K 0x5 +@@ -245,6 +250,7 @@ struct qcom_pcie_ops { + void (*deinit)(struct qcom_pcie *pcie); + void (*ltssm_enable)(struct qcom_pcie *pcie); + int (*config_sid)(struct qcom_pcie *pcie); ++ enum dw_pcie_ltssm (*get_ltssm)(struct qcom_pcie *pcie); + }; + + /** +@@ -428,6 +434,15 @@ static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie) + writel(val, pci->elbi_base + ELBI_SYS_CTRL); + } + ++static enum dw_pcie_ltssm qcom_pcie_2_1_0_get_ltssm(struct qcom_pcie *pcie) ++{ ++ struct dw_pcie *pci = pcie->pci; ++ u32 val; ++ ++ val = readl(pci->elbi_base + ELBI_SYS_STTS); ++ return (enum dw_pcie_ltssm)FIELD_GET(ELBI_SYS_STTS_LTSSM_STATE_MASK, val); ++} ++ + static int qcom_pcie_get_resources_2_1_0(struct qcom_pcie *pcie) + { + struct qcom_pcie_resources_2_1_0 *res = &pcie->res.v2_1_0; +@@ -1260,6 +1275,19 @@ static bool qcom_pcie_link_up(struct dw_pcie *pci) + return val & PCI_EXP_LNKSTA_DLLLA; + } + ++static enum dw_pcie_ltssm qcom_pcie_get_ltssm(struct dw_pcie *pci) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pci); ++ u32 val; ++ ++ if (pcie->cfg->ops->get_ltssm) ++ return pcie->cfg->ops->get_ltssm(pcie); ++ ++ val = readl(pcie->parf + PARF_LTSSM); ++ ++ return (enum dw_pcie_ltssm)FIELD_GET(PARF_LTSSM_STATE_MASK, val); ++} ++ + static void qcom_pcie_phy_power_off(struct qcom_pcie *pcie) + { + struct qcom_pcie_port *port; +@@ -1385,6 +1413,7 @@ static const struct qcom_pcie_ops ops_2_1_0 = { + .post_init = qcom_pcie_post_init_2_1_0, + .deinit = qcom_pcie_deinit_2_1_0, + .ltssm_enable = qcom_pcie_2_1_0_ltssm_enable, ++ .get_ltssm = qcom_pcie_2_1_0_get_ltssm, + }; + + /* Qcom IP rev.: 1.0.0 Synopsys IP rev.: 4.11a */ +@@ -1394,6 +1423,7 @@ static const struct qcom_pcie_ops ops_1_0_0 = { + .post_init = qcom_pcie_post_init_1_0_0, + .deinit = qcom_pcie_deinit_1_0_0, + .ltssm_enable = qcom_pcie_2_1_0_ltssm_enable, ++ .get_ltssm = qcom_pcie_2_1_0_get_ltssm, + }; + + /* Qcom IP rev.: 2.3.2 Synopsys IP rev.: 4.21a */ +@@ -1512,6 +1542,7 @@ static const struct qcom_pcie_cfg cfg_fw_managed = { + static const struct dw_pcie_ops dw_pcie_ops = { + .link_up = qcom_pcie_link_up, + .start_link = qcom_pcie_start_link, ++ .get_ltssm = qcom_pcie_get_ltssm, + }; + + static int qcom_pcie_icc_init(struct qcom_pcie *pcie) diff --git a/kernel/patches/1019-pci-qcom-power-down-phy.patch b/kernel/patches/1019-pci-qcom-power-down-phy.patch new file mode 100644 index 0000000..fb041fc --- /dev/null +++ b/kernel/patches/1019-pci-qcom-power-down-phy.patch @@ -0,0 +1,140 @@ +From 8a847d3e9e5f1700beb5a0196e682f71837dfe5c Mon Sep 17 00:00:00 2001 +From: Krishna Chaitanya Chundru +Date: Wed, 29 Apr 2026 12:12:25 +0530 +Subject: [PATCH] PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling + rails/clocks + +Some Qcom PCIe controller variants bring the PHY out of test power-down +(PHY_TEST_PWR_DOWN) during init. When the link is later transitioned to +D3cold and the driver disables PCIe clocks and/or regulators without +explicitly re-asserting PHY_TEST_PWR_DOWN, the PHY can remain partially +powered, leading to avoidable power leakage. + +Update the init-path comments to reflect that PARF_PHY_CTRL is used to +power the PHY on. Also, for controller revisions that enable PHY power in +init (2.3.2, 2.3.3, 2.4.0, 2.7.0 and 2.9.0), explicitly power the PHY down +via PARF_PHY_CTRL in the deinit path before disabling clocks or regulators. + +This ensures the PHY is put into a defined low-power state prior to +removing its supplies, preventing leakage when entering D3cold. + +Signed-off-by: Krishna Chaitanya Chundru +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20260429-d3cold-v5-3-89e9735b9df6@oss.qualcomm.com +--- + drivers/pci/controller/dwc/pcie-qcom.c | 38 ++++++++++++++++++++++++-- + 1 file changed, 35 insertions(+), 3 deletions(-) + +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index 881746efe3b6ec..179d26038da840 100644 +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -532,7 +532,7 @@ static int qcom_pcie_post_init_2_1_0(struct qcom_pcie *pcie) + u32 val; + int ret; + +- /* enable PCIe clocks and resets */ ++ /* Force PHY out of lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val &= ~PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); +@@ -699,6 +699,12 @@ static int qcom_pcie_get_resources_2_3_2(struct qcom_pcie *pcie) + static void qcom_pcie_deinit_2_3_2(struct qcom_pcie *pcie) + { + struct qcom_pcie_resources_2_3_2 *res = &pcie->res.v2_3_2; ++ u32 val; ++ ++ /* Force PHY to lowest power state*/ ++ val = readl(pcie->parf + PARF_PHY_CTRL); ++ val |= PHY_TEST_PWR_DOWN; ++ writel(val, pcie->parf + PARF_PHY_CTRL); + + clk_bulk_disable_unprepare(res->num_clks, res->clks); + regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies); +@@ -731,7 +737,7 @@ static int qcom_pcie_post_init_2_3_2(struct qcom_pcie *pcie) + { + u32 val; + +- /* enable PCIe clocks and resets */ ++ /* Force PHY out of lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val &= ~PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); +@@ -795,6 +801,12 @@ static int qcom_pcie_get_resources_2_4_0(struct qcom_pcie *pcie) + static void qcom_pcie_deinit_2_4_0(struct qcom_pcie *pcie) + { + struct qcom_pcie_resources_2_4_0 *res = &pcie->res.v2_4_0; ++ u32 val; ++ ++ /* Force PHY to lowest power state*/ ++ val = readl(pcie->parf + PARF_PHY_CTRL); ++ val |= PHY_TEST_PWR_DOWN; ++ writel(val, pcie->parf + PARF_PHY_CTRL); + + reset_control_bulk_assert(res->num_resets, res->resets); + clk_bulk_disable_unprepare(res->num_clks, res->clks); +@@ -863,6 +875,12 @@ static int qcom_pcie_get_resources_2_3_3(struct qcom_pcie *pcie) + static void qcom_pcie_deinit_2_3_3(struct qcom_pcie *pcie) + { + struct qcom_pcie_resources_2_3_3 *res = &pcie->res.v2_3_3; ++ u32 val; ++ ++ /* Force PHY to lowest power state */ ++ val = readl(pcie->parf + PARF_PHY_CTRL); ++ val |= PHY_TEST_PWR_DOWN; ++ writel(val, pcie->parf + PARF_PHY_CTRL); + + clk_bulk_disable_unprepare(res->num_clks, res->clks); + } +@@ -918,6 +936,7 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) + u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + u32 val; + ++ /* Force PHY out of lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val &= ~PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); +@@ -1013,7 +1032,7 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie) + /* configure PCIe to RC mode */ + writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE); + +- /* enable PCIe clocks and resets */ ++ /* Force PHY out of lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val &= ~PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); +@@ -1084,6 +1103,12 @@ static void qcom_pcie_host_post_init_2_7_0(struct qcom_pcie *pcie) + static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie) + { + struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0; ++ u32 val; ++ ++ /* Force PHY to lowest power state */ ++ val = readl(pcie->parf + PARF_PHY_CTRL); ++ val |= PHY_TEST_PWR_DOWN; ++ writel(val, pcie->parf + PARF_PHY_CTRL); + + clk_bulk_disable_unprepare(res->num_clks, res->clks); + +@@ -1188,6 +1213,12 @@ static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie) + static void qcom_pcie_deinit_2_9_0(struct qcom_pcie *pcie) + { + struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0; ++ u32 val; ++ ++ /* Force PHY to lowest power state */ ++ val = readl(pcie->parf + PARF_PHY_CTRL); ++ val |= PHY_TEST_PWR_DOWN; ++ writel(val, pcie->parf + PARF_PHY_CTRL); + + clk_bulk_disable_unprepare(res->num_clks, res->clks); + } +@@ -1228,6 +1259,7 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) + u32 val; + int i; + ++ /* Force PHY out of lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val &= ~PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); diff --git a/kernel/patches/1020-pci-dwc-use-common-d3cold-helper.patch b/kernel/patches/1020-pci-dwc-use-common-d3cold-helper.patch new file mode 100644 index 0000000..6254d07 --- /dev/null +++ b/kernel/patches/1020-pci-dwc-use-common-d3cold-helper.patch @@ -0,0 +1,120 @@ +From 56378c03c1a80aeeab45f39b303cc92a3bb7716e Mon Sep 17 00:00:00 2001 +From: Krishna Chaitanya Chundru +Date: Wed, 29 Apr 2026 12:12:26 +0530 +Subject: [PATCH] PCI: dwc: Use common D3cold eligibility helper in suspend + path + +Previously, the driver skipped putting the link into L2 and device state in +D3cold whenever L1 ASPM was enabled, since some devices (e.g. NVMe) expect +low resume latency and may not tolerate deeper power states. However, such +devices typically remain in D0 and are already covered by the new helper's +requirement that all endpoints be in D3hot before the devices under host +bridge may enter D3cold. + +Replace the local L1/L1SS-based check in dw_pcie_suspend_noirq() with the +shared pci_host_common_d3cold_possible() helper to decide whether the +devices under host bridge can safely transition to D3cold. + +In addition, propagate PME-from-D3cold capability information from the +helper and record it in skip_pwrctrl_off. Some devices (e.g. M.2 cards +without auxiliary power) cannot send PME when the main power is removed, +even if they advertise PME-from-D3cold support. This allows controller +power-off to be skipped when required to preserve wakeup functionality. + +While at it, update the 'dw_pcie::suspended' flag in dw_pcie_resume_noirq() +only after the PCIe link resumes successfully, to avoid marking the +controller as active when link resume fails. + +Signed-off-by: Krishna Chaitanya Chundru +[mani: commit log and added TODO to query Vaux] +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20260429-d3cold-v5-4-89e9735b9df6@oss.qualcomm.com +--- + .../pci/controller/dwc/pcie-designware-host.c | 23 ++++++++++++------- + drivers/pci/controller/dwc/pcie-designware.h | 1 + + 2 files changed, 16 insertions(+), 8 deletions(-) + +diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c +index c9517a34883686..cffb34f6f3a9ec 100644 +--- a/drivers/pci/controller/dwc/pcie-designware-host.c ++++ b/drivers/pci/controller/dwc/pcie-designware-host.c +@@ -16,9 +16,11 @@ + #include + #include + #include ++#include + #include + #include + ++#include "../pci-host-common.h" + #include "../../pci.h" + #include "pcie-designware.h" + +@@ -1218,18 +1220,14 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci) + + int dw_pcie_suspend_noirq(struct dw_pcie *pci) + { +- u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); ++ bool pme_capable = false; + int ret = 0; + u32 val; + + if (!dw_pcie_link_up(pci)) + goto stop_link; + +- /* +- * If L1SS is supported, then do not put the link into L2 as some +- * devices such as NVMe expect low resume latency. +- */ +- if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1) ++ if (!pci_host_common_d3cold_possible(pci->pp.bridge, &pme_capable)) + return 0; + + if (pci->pp.ops->pme_turn_off) { +@@ -1273,6 +1271,15 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) + udelay(1); + + stop_link: ++ /* ++ * TODO: "pme_capable" means some downstream device is wakeup- ++ * enabled and is capable of generating PME from D3cold, which ++ * requires auxiliary power. Instead of always skipping power off ++ * if PME is supported from D3cold, query the pwrctrl core and skip ++ * power off only if device supports PME from D3cold and Vaux is ++ * not supported. ++ */ ++ pci->pp.skip_pwrctrl_off = pme_capable; + dw_pcie_stop_link(pci); + if (pci->pp.ops->deinit) + pci->pp.ops->deinit(&pci->pp); +@@ -1290,8 +1297,6 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci) + if (!pci->suspended) + return 0; + +- pci->suspended = false; +- + if (pci->pp.ops->init) { + ret = pci->pp.ops->init(&pci->pp); + if (ret) { +@@ -1313,6 +1318,8 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci) + if (pci->pp.ops->post_init) + pci->pp.ops->post_init(&pci->pp); + ++ pci->suspended = false; ++ + return 0; + + err_stop_link: +diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h +index 3e69ef60165b0e..e759c5c7257e52 100644 +--- a/drivers/pci/controller/dwc/pcie-designware.h ++++ b/drivers/pci/controller/dwc/pcie-designware.h +@@ -450,6 +450,7 @@ struct dw_pcie_rp { + bool ecam_enabled; + bool native_ecam; + bool skip_l23_ready; ++ bool skip_pwrctrl_off; + }; + + struct dw_pcie_ep_ops { diff --git a/kernel/patches/1021-pci-qcom-add-d3cold-support.patch b/kernel/patches/1021-pci-qcom-add-d3cold-support.patch new file mode 100644 index 0000000..ee77c3b --- /dev/null +++ b/kernel/patches/1021-pci-qcom-add-d3cold-support.patch @@ -0,0 +1,284 @@ +From 2cc0e7454c7891345f92e96b2f812b808be7fbdb Mon Sep 17 00:00:00 2001 +From: Krishna Chaitanya Chundru +Date: Wed, 29 Apr 2026 12:12:27 +0530 +Subject: [PATCH] PCI: qcom: Add D3cold support + +Add support for transitioning PCIe endpoints under host bridge into D3cold +by integrating with the DWC core suspend/resume helpers. + +Implement PME_Turn_Off message generation via ELBI_SYS_CTRL and hook it +into the DWC host operations so the controller follows the standard +PME_Turn_Off based power-down sequence before entering D3cold. + +When the device is suspended into D3cold, fully tear down interconnect +bandwidth and OPP votes. If D3cold is not entered, retain existing +behavior by keeping the required interconnect and OPP votes. + +Use dw_pcie::skip_pwrctrl_off to avoid powering off devices during suspend +to preserve wakeup capability of the devices and also not to power on the +devices in the init path. + +Finally, drop the qcom_pcie::suspended flag and rely on the existing +dw_pcie::suspended state, which now drives both the power-management flow +and the interconnect/OPP handling. + +Signed-off-by: Krishna Chaitanya Chundru +[mani: commit log] +Signed-off-by: Manivannan Sadhasivam +Signed-off-by: Bjorn Helgaas +Link: https://patch.msgid.link/20260429-d3cold-v5-5-89e9735b9df6@oss.qualcomm.com +--- + drivers/pci/controller/dwc/pcie-qcom.c | 163 ++++++++++++++++--------- + 1 file changed, 103 insertions(+), 60 deletions(-) + +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index 179d26038da840..bc5a31efc0d071 100644 +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -146,6 +146,7 @@ + + /* ELBI_SYS_CTRL register fields */ + #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) ++#define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4) + + /* ELBI_SYS_STTS register fields */ + #define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12) +@@ -288,7 +289,6 @@ struct qcom_pcie { + const struct qcom_pcie_cfg *cfg; + struct dentry *debugfs; + struct list_head ports; +- bool suspended; + bool use_pm_opp; + }; + +@@ -1364,13 +1364,17 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) + if (ret) + goto err_deinit; + +- ret = pci_pwrctrl_create_devices(pci->dev); +- if (ret) +- goto err_disable_phy; ++ if (!pci->suspended) { ++ ret = pci_pwrctrl_create_devices(pci->dev); ++ if (ret) ++ goto err_disable_phy; ++ } + +- ret = pci_pwrctrl_power_on_devices(pci->dev); +- if (ret) +- goto err_pwrctrl_destroy; ++ if (!pp->skip_pwrctrl_off) { ++ ret = pci_pwrctrl_power_on_devices(pci->dev); ++ if (ret) ++ goto err_pwrctrl_destroy; ++ } + + if (pcie->cfg->ops->post_init) { + ret = pcie->cfg->ops->post_init(pcie); +@@ -1395,9 +1399,10 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) + err_assert_reset: + qcom_pcie_perst_assert(pcie); + err_pwrctrl_power_off: +- pci_pwrctrl_power_off_devices(pci->dev); ++ if (!pp->skip_pwrctrl_off) ++ pci_pwrctrl_power_off_devices(pci->dev); + err_pwrctrl_destroy: +- if (ret != -EPROBE_DEFER) ++ if (ret != -EPROBE_DEFER && !pci->suspended) + pci_pwrctrl_destroy_devices(pci->dev); + err_disable_phy: + qcom_pcie_phy_power_off(pcie); +@@ -1414,11 +1419,14 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp) + + qcom_pcie_perst_assert(pcie); + +- /* +- * No need to destroy pwrctrl devices as this function only gets called +- * during system suspend as of now. +- */ +- pci_pwrctrl_power_off_devices(pci->dev); ++ if (!pci->pp.skip_pwrctrl_off) { ++ /* ++ * No need to destroy pwrctrl devices as this function only ++ * gets called during system suspend as of now. ++ */ ++ pci_pwrctrl_power_off_devices(pci->dev); ++ } ++ + qcom_pcie_phy_power_off(pcie); + pcie->cfg->ops->deinit(pcie); + } +@@ -1432,10 +1440,18 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp) + pcie->cfg->ops->host_post_init(pcie); + } + ++static void qcom_pcie_host_pme_turn_off(struct dw_pcie_rp *pp) ++{ ++ struct dw_pcie *pci = to_dw_pcie_from_pp(pp); ++ ++ writel(ELBI_SYS_CTRL_PME_TURNOFF_MSG, pci->elbi_base + ELBI_SYS_CTRL); ++} ++ + static const struct dw_pcie_host_ops qcom_pcie_dw_ops = { + .init = qcom_pcie_host_init, + .deinit = qcom_pcie_host_deinit, + .post_init = qcom_pcie_host_post_init, ++ .pme_turn_off = qcom_pcie_host_pme_turn_off, + }; + + /* Qcom IP rev.: 2.1.0 Synopsys IP rev.: 4.01a */ +@@ -2104,53 +2120,51 @@ static int qcom_pcie_suspend_noirq(struct device *dev) + if (!pcie) + return 0; + +- /* +- * Set minimum bandwidth required to keep data path functional during +- * suspend. +- */ +- if (pcie->icc_mem) { +- ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1)); +- if (ret) { +- dev_err(dev, +- "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n", +- ret); +- return ret; +- } +- } ++ ret = dw_pcie_suspend_noirq(pcie->pci); ++ if (ret) ++ return ret; + +- /* +- * Turn OFF the resources only for controllers without active PCIe +- * devices. For controllers with active devices, the resources are kept +- * ON and the link is expected to be in L0/L1 (sub)states. +- * +- * Turning OFF the resources for controllers with active PCIe devices +- * will trigger access violation during the end of the suspend cycle, +- * as kernel tries to access the PCIe devices config space for masking +- * MSIs. +- * +- * Also, it is not desirable to put the link into L2/L3 state as that +- * implies VDD supply will be removed and the devices may go into +- * powerdown state. This will affect the lifetime of the storage devices +- * like NVMe. +- */ +- if (!dw_pcie_link_up(pcie->pci)) { +- qcom_pcie_host_deinit(&pcie->pci->pp); +- pcie->suspended = true; +- } ++ if (pcie->pci->suspended) { ++ ret = icc_disable(pcie->icc_mem); ++ if (ret) ++ dev_err(dev, "Failed to disable PCIe-MEM interconnect path: %d\n", ret); + +- /* +- * Only disable CPU-PCIe interconnect path if the suspend is non-S2RAM. +- * Because on some platforms, DBI access can happen very late during the +- * S2RAM and a non-active CPU-PCIe interconnect path may lead to NoC +- * error. +- */ +- if (pm_suspend_target_state != PM_SUSPEND_MEM) { + ret = icc_disable(pcie->icc_cpu); + if (ret) + dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret); + + if (pcie->use_pm_opp) + dev_pm_opp_set_opp(pcie->pci->dev, NULL); ++ } else { ++ /* ++ * Set minimum bandwidth required to keep data path ++ * functional during suspend. ++ */ ++ if (pcie->icc_mem) { ++ ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1)); ++ if (ret) { ++ dev_err(dev, ++ "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n", ++ ret); ++ return ret; ++ } ++ } ++ ++ /* ++ * Only disable CPU-PCIe interconnect path if the suspend ++ * is non-S2RAM. On some platforms, DBI access can happen ++ * very late during S2RAM and a non-active CPU-PCIe ++ * interconnect path may lead to NoC error. ++ */ ++ if (pm_suspend_target_state != PM_SUSPEND_MEM) { ++ ret = icc_disable(pcie->icc_cpu); ++ if (ret) ++ dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ++ ret); ++ ++ if (pcie->use_pm_opp) ++ dev_pm_opp_set_opp(pcie->pci->dev, NULL); ++ } + } + return ret; + } +@@ -2164,7 +2178,7 @@ static int qcom_pcie_resume_noirq(struct device *dev) + if (!pcie) + return 0; + +- if (pm_suspend_target_state != PM_SUSPEND_MEM) { ++ if (pcie->pci->suspended) { + if (pcie->use_pm_opp) { + ret = qcom_pcie_set_max_opp(dev); + if (ret) { +@@ -2178,19 +2192,48 @@ static int qcom_pcie_resume_noirq(struct device *dev) + dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret); + return ret; + } +- } + +- if (pcie->suspended) { +- ret = qcom_pcie_host_init(&pcie->pci->pp); +- if (ret) +- return ret; ++ ret = icc_enable(pcie->icc_mem); ++ if (ret) { ++ dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", ret); ++ goto disable_icc_cpu; ++ } ++ ++ /* ++ * Ignore -ENODEV & -EIO here since it is expected when no ++ * endpoint is connected to the PCIe link. ++ */ ++ ret = dw_pcie_resume_noirq(pcie->pci); ++ if (ret && ret != -ENODEV && ret != -EIO) ++ goto disable_icc_mem; ++ } else { ++ if (pm_suspend_target_state != PM_SUSPEND_MEM) { ++ if (pcie->use_pm_opp) { ++ ret = qcom_pcie_set_max_opp(dev); ++ if (ret) { ++ dev_err(dev, "Failed to set max OPP: %d\n", ret); ++ return ret; ++ } ++ } + +- pcie->suspended = false; ++ ret = icc_enable(pcie->icc_cpu); ++ if (ret) { ++ dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ++ ret); ++ return ret; ++ } ++ } + } + + qcom_pcie_icc_opp_update(pcie); + + return 0; ++disable_icc_mem: ++ icc_disable(pcie->icc_mem); ++disable_icc_cpu: ++ icc_disable(pcie->icc_cpu); ++ ++ return ret; + } + + static const struct of_device_id qcom_pcie_match[] = { diff --git a/kernel/patches/1023-pci-qcom-skip-l23-ready-after-pme-sm8550.patch b/kernel/patches/1023-pci-qcom-skip-l23-ready-after-pme-sm8550.patch new file mode 100644 index 0000000..f7c2657 --- /dev/null +++ b/kernel/patches/1023-pci-qcom-skip-l23-ready-after-pme-sm8550.patch @@ -0,0 +1,32 @@ +From: Luke Johnson +Subject: [PATCH] PCI: qcom: skip L2/L3-ready LTSSM poll after PME_TurnOff (SM8550) + +On SM8550 the L2/L3-ready poll read_poll_timeout(dw_pcie_get_ltssm) in +dw_pcie_suspend_noirq() accesses a controller LTSSM register after the +PME_Turn_Off broadcast, which the SoC cannot service at that point (the +read wedges/faults), so system suspend never resumes. Confirmed needed: +with the get_ltssm helper corrected to read PARF_LTSSM, suspend still +crashes at the L2-wait; only skipping the poll lets it proceed. +Set pp->skip_l23_ready so the DWC core waits a fixed delay for L2/L3 +entry instead of polling, matching i.MX6's handling. Companion to the +d3cold backport + the get_ltssm helper fix. +--- +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -2062,6 +2062,16 @@ + + pp->ops = &qcom_pcie_dw_ops; + ++ /* ++ * SM8550: the L2/L3-ready poll in dw_pcie_suspend_noirq() reads a ++ * controller LTSSM register after the PME_Turn_Off broadcast, which ++ * is unreadable on this SoC (ELBI via a NULL get_ltssm, or PARF_LTSSM ++ * once get_ltssm is corrected) and wedges/faults system suspend with ++ * no resume. Skip that poll (fixed mdelay for L2 entry instead), as ++ * other DWC platforms (e.g. i.MX6) do for the same limitation. ++ */ ++ pp->skip_l23_ready = true; ++ + ret = qcom_pcie_parse_ports(pcie); + if (ret) { + if (ret != -ENODEV) { diff --git a/kernel/patches/1024-regulator-qcom-rpmh-add-suspend-state-support.patch b/kernel/patches/1024-regulator-qcom-rpmh-add-suspend-state-support.patch new file mode 100644 index 0000000..97e01e9 --- /dev/null +++ b/kernel/patches/1024-regulator-qcom-rpmh-add-suspend-state-support.patch @@ -0,0 +1,251 @@ +From: Luke Johnson +Date: Thu, 2 Jul 2026 00:00:00 +1200 +Subject: [PATCH] regulator: qcom-rpmh: add suspend-state (SLEEP/WAKE TCS) support + +The mainline driver only ever votes in RPMH_ACTIVE_ONLY_STATE, so every +rail that is enabled when the system suspends stays at full power through +the platform sleep state. Downstream (qcom,set=<3>) mirrors votes into +the SLEEP set and additionally forces specific rails off in sleep. + +Implement regulator-state-mem support: + + * set_suspend_enable/disable place an enable vote in the SLEEP set and + the current active-set contribution in the WAKE set, so the AOP wake + set replay restores exactly the pre-suspend state before the kernel + resumes. rpmh's is_req_valid() prunes equal sleep/wake pairs, so a + rail with no APPS enable vote degenerates to a guaranteed no-op and + other DRVs' votes can never be overridden. + + * set_suspend_mode does the same for the VRM mode register (LDO LPM in + suspend with HPM restored on wake). + + * Every active-set request is now also mirrored into the SLEEP cache. + rpmh_flush() runs on runtime cluster idle too, not only at system + suspend; without the mirror, an enable that arrives after resume + would only update the wake side and leave a stale sleep=off vote that + cuts the rail on every runtime RSC deep-idle. Equal pairs are pruned, + so the mirror is invisible in the TCSes until a suspend vote is armed. + + * ops->resume re-aligns the SLEEP set with the active set so the + system-suspend configuration does not persist into post-resume + runtime idle (e.g. a codec rail left in LPM under WLAN load). + +Only regulators whose DT node carries regulator-state-mem with +regulator-on/off-in-suspend are affected (the regulator core calls the +suspend ops, and ops->resume, only for those). The companion regulator +core patch maps s2idle (PM_SUSPEND_TO_IDLE) to the same state-mem +constraints, so the ops fire for both suspend modes on this platform. +Active-set votes are never modified. +--- + drivers/regulator/qcom-rpmh-regulator.c | 165 +++++++++++++++++++++++- + 1 file changed, 163 insertions(+), 2 deletions(-) + +--- a/drivers/regulator/qcom-rpmh-regulator.c ++++ b/drivers/regulator/qcom-rpmh-regulator.c +@@ -204,8 +204,21 @@ + else + ret = rpmh_write_async(vreg->dev, RPMH_ACTIVE_ONLY_STATE, cmd, + 1); ++ if (ret < 0) ++ return ret; + +- return ret; ++ /* ++ * Mirror every active-set request into the SLEEP set cache. rpmh ++ * prunes cache entries whose sleep and wake values are equal (an ++ * ACTIVE_ONLY write also updates the cached wake value), so this ++ * changes nothing in the sleep/wake TCSes by itself. It matters ++ * once a suspend-state vote (set_suspend_*) has been placed for a ++ * register: any later active-set change then re-aligns the sleep ++ * vote instead of leaving a stale suspend value that rpmh_flush() ++ * would re-apply on every runtime RSC deep-idle entry. This is ++ * cache-only; no request is sent to the hardware here. ++ */ ++ return rpmh_write(vreg->dev, RPMH_SLEEP_STATE, cmd, 1); + } + + static int _rpmh_regulator_vrm_set_voltage_sel(struct regulator_dev *rdev, +@@ -342,6 +355,141 @@ + } + + /** ++ * rpmh_regulator_send_sleep_wake_request() - cache a SLEEP set vote together ++ * with the matching WAKE set restore value for one register of ++ * an RPMh regulator resource ++ * @vreg: Pointer to the RPMh regulator ++ * @reg_offset: VRM/XOB register offset within the RPMh resource ++ * @sleep_val: Value to apply while the RSC is in its sleep state ++ * @wake_val: Value the wake set replay restores before the CPUs resume ++ * ++ * Both votes are only cached here; rpmh_flush() writes them to the sleep ++ * and wake TCSes when the last CPU powers down. rpmh's is_req_valid() ++ * prunes cache entries whose sleep and wake values are equal, so sending ++ * an identical pair is a guaranteed no-op for this resource. The active ++ * set is never modified by this function. ++ * ++ * Return: 0 on success, or a negative error number on failure ++ */ ++static int rpmh_regulator_send_sleep_wake_request(struct rpmh_vreg *vreg, ++ u32 reg_offset, u32 sleep_val, u32 wake_val) ++{ ++ struct tcs_cmd cmd = { ++ .addr = vreg->addr + reg_offset, ++ .data = sleep_val, ++ }; ++ int ret; ++ ++ ret = rpmh_write(vreg->dev, RPMH_SLEEP_STATE, &cmd, 1); ++ if (ret < 0) ++ return ret; ++ ++ cmd.data = wake_val; ++ ++ return rpmh_write(vreg->dev, RPMH_WAKE_ONLY_STATE, &cmd, 1); ++} ++ ++static int rpmh_regulator_set_suspend_enable(struct regulator_dev *rdev) ++{ ++ struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); ++ ++ return rpmh_regulator_send_sleep_wake_request(vreg, ++ RPMH_REGULATOR_REG_ENABLE, 1, vreg->enabled == 1); ++} ++ ++static int rpmh_regulator_set_suspend_disable(struct regulator_dev *rdev) ++{ ++ struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); ++ ++ /* ++ * WAKE restores the current active-set contribution: 1 only if an ++ * APPS enable vote is in place right now. If no enable request has ++ * ever been sent (enabled == -EINVAL) the pair is 0/0, which rpmh ++ * prunes - the vote of another DRV (AOP, TZ, ...) keeping such a ++ * rail on can never be overridden from here. ++ */ ++ return rpmh_regulator_send_sleep_wake_request(vreg, ++ RPMH_REGULATOR_REG_ENABLE, 0, vreg->enabled == 1); ++} ++ ++/* ++ * Return the PMIC mode value currently requested in the active set, or a ++ * negative error number if no mode request has been sent. ++ */ ++static int rpmh_regulator_vrm_active_pmic_mode(struct rpmh_vreg *vreg) ++{ ++ if (vreg->bypassed) ++ return PMIC4_BOB_MODE_PASS; ++ ++ if (vreg->mode == REGULATOR_MODE_INVALID) ++ return -EINVAL; ++ ++ return vreg->hw_data->pmic_mode_map[vreg->mode]; ++} ++ ++static int rpmh_regulator_vrm_set_suspend_mode(struct regulator_dev *rdev, ++ unsigned int mode) ++{ ++ struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); ++ int sleep_mode, wake_mode; ++ ++ if (mode > REGULATOR_MODE_STANDBY) ++ return -EINVAL; ++ ++ sleep_mode = vreg->hw_data->pmic_mode_map[mode]; ++ if (sleep_mode < 0) ++ return sleep_mode; ++ ++ /* ++ * WAKE must restore the pre-suspend active-set mode. If no mode ++ * request has ever been sent, reuse sleep_mode so that the equal ++ * pair is pruned instead of inventing a wake vote that was not ++ * present before suspend. ++ */ ++ wake_mode = rpmh_regulator_vrm_active_pmic_mode(vreg); ++ if (wake_mode < 0) ++ wake_mode = sleep_mode; ++ ++ /* Never override bypass (pass-through) mode in the sleep set. */ ++ if (vreg->bypassed) ++ sleep_mode = wake_mode; ++ ++ return rpmh_regulator_send_sleep_wake_request(vreg, ++ RPMH_REGULATOR_REG_VRM_MODE, sleep_mode, wake_mode); ++} ++ ++static int rpmh_regulator_resume(struct regulator_dev *rdev) ++{ ++ struct rpmh_vreg *vreg = rdev_get_drvdata(rdev); ++ int mode; ++ int ret; ++ ++ /* ++ * Re-align the SLEEP set with the active set. The rpmh sleep/wake ++ * cache persists after resume, and rpmh_flush() also runs when the ++ * last CPU powers down for runtime cpuidle - without this undo the ++ * system-suspend configuration would be re-applied on every runtime ++ * RSC sleep. Writing SLEEP equal to the current active value makes ++ * is_req_valid() prune the entry at the next flush. ++ */ ++ ret = rpmh_regulator_send_sleep_wake_request(vreg, ++ RPMH_REGULATOR_REG_ENABLE, ++ vreg->enabled == 1, vreg->enabled == 1); ++ if (ret < 0) ++ return ret; ++ ++ if (vreg->hw_data->regulator_type != VRM) ++ return 0; ++ ++ mode = rpmh_regulator_vrm_active_pmic_mode(vreg); ++ if (mode < 0) ++ return 0; ++ ++ return rpmh_regulator_send_sleep_wake_request(vreg, ++ RPMH_REGULATOR_REG_VRM_MODE, mode, mode); ++} ++ ++/** + * rpmh_regulator_vrm_get_optimum_mode() - get the mode based on the load + * @rdev: Regulator device pointer for the rpmh-regulator + * @input_uV: Input voltage +@@ -399,6 +547,10 @@ + .list_voltage = regulator_list_voltage_linear_range, + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, ++ .set_suspend_enable = rpmh_regulator_set_suspend_enable, ++ .set_suspend_disable = rpmh_regulator_set_suspend_disable, ++ .set_suspend_mode = rpmh_regulator_vrm_set_suspend_mode, ++ .resume = rpmh_regulator_resume, + }; + + static const struct regulator_ops rpmh_regulator_vrm_drms_ops = { +@@ -411,6 +563,10 @@ + .set_mode = rpmh_regulator_vrm_set_mode, + .get_mode = rpmh_regulator_vrm_get_mode, + .get_optimum_mode = rpmh_regulator_vrm_get_optimum_mode, ++ .set_suspend_enable = rpmh_regulator_set_suspend_enable, ++ .set_suspend_disable = rpmh_regulator_set_suspend_disable, ++ .set_suspend_mode = rpmh_regulator_vrm_set_suspend_mode, ++ .resume = rpmh_regulator_resume, + }; + + static const struct regulator_ops rpmh_regulator_vrm_bypass_ops = { +@@ -424,12 +580,19 @@ + .get_mode = rpmh_regulator_vrm_get_mode, + .set_bypass = rpmh_regulator_vrm_set_bypass, + .get_bypass = rpmh_regulator_vrm_get_bypass, ++ .set_suspend_enable = rpmh_regulator_set_suspend_enable, ++ .set_suspend_disable = rpmh_regulator_set_suspend_disable, ++ .set_suspend_mode = rpmh_regulator_vrm_set_suspend_mode, ++ .resume = rpmh_regulator_resume, + }; + + static const struct regulator_ops rpmh_regulator_xob_ops = { + .enable = rpmh_regulator_enable, + .disable = rpmh_regulator_disable, + .is_enabled = rpmh_regulator_is_enabled, ++ .set_suspend_enable = rpmh_regulator_set_suspend_enable, ++ .set_suspend_disable = rpmh_regulator_set_suspend_disable, ++ .resume = rpmh_regulator_resume, + }; + + /** diff --git a/kernel/patches/1025-regulator-core-apply-mem-state-for-s2idle.patch b/kernel/patches/1025-regulator-core-apply-mem-state-for-s2idle.patch new file mode 100644 index 0000000..5564da1 --- /dev/null +++ b/kernel/patches/1025-regulator-core-apply-mem-state-for-s2idle.patch @@ -0,0 +1,32 @@ +From: Luke Johnson +Subject: [PATCH] regulator: core: apply regulator-state-mem for s2idle too + +On SM8550 the default suspend mode is s2idle, which converges on the same +RPMh RSC sleep/wake handoff as deep suspend. Map PM_SUSPEND_TO_IDLE to the +regulator-state-mem constraints so suspend sleep votes apply on the default +path (upstream has no TO_IDLE devicetree binding). +--- +--- a/drivers/regulator/core.c ++++ b/drivers/regulator/core.c +@@ -465,7 +465,7 @@ + /* return 0 if the state is valid */ + static int regulator_check_states(suspend_state_t state) + { +- return (state > PM_SUSPEND_MAX || state == PM_SUSPEND_TO_IDLE); ++ return (state > PM_SUSPEND_MAX); + } + + /* Make sure we select a voltage that suits the needs of all +@@ -570,6 +570,12 @@ + switch (state) { + case PM_SUSPEND_STANDBY: + return &rdev->constraints->state_standby; ++ /* ++ * SM8550 s2idle reaches the same RSC sleep/wake handoff as deep, ++ * so apply the regulator-state-mem configuration there too (upstream ++ * has no PM_SUSPEND_TO_IDLE devicetree binding). ++ */ ++ case PM_SUSPEND_TO_IDLE: + case PM_SUSPEND_MEM: + return &rdev->constraints->state_mem; + case PM_SUSPEND_MAX: diff --git a/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch b/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch new file mode 100644 index 0000000..9f038d3 --- /dev/null +++ b/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch @@ -0,0 +1,93 @@ +From: Luke Johnson +Date: Fri, 3 Jul 2026 00:00:00 +1200 +Subject: [PATCH] PCI: qcom: retain the suspend OPP during s2idle + +On SM8550/QCS8550, disabling the PCIe OPP before s2idle removes the +OPP-derived MC0/SH0 (DDR/LLCC) sleep bandwidth votes. When APSS enters +cluster_sleep_1 (PSCI 0x4100c344), AOP-owned sleep is reached and RTC +wake fires, but firmware does not return to Linux. Reproduced on AYN +Thor and Retroid Pocket 6 with different AOP builds. cluster_sleep_0 +wakes guard-free; CX floors, CX_AO, cx_mol, DDR MOL and fixed DDR +frequency do not guard it. Setting the suspend (floor) OPP instead of +dropping to NULL preserves the MC0/SH0 sleep bandwidth and fixes +resume. Falls back to the previous behaviour (NULL) when no +opp-suspend is marked. + +On OPP-scaled platforms (use_pm_opp) the probe path skips ICC +initialisation entirely (icc_mem/icc_cpu stay NULL), so the OPP's +opp-peak-kBps values are the only source of the memory-path (MC0/DDR +and SH0/LLCC BCM) bandwidth votes, including their RPMh sleep-set +contribution. qcom_pcie_suspend_noirq() drops the OPP to NULL at both +the d3cold branch and the non-S2RAM else branch, zeroing that sleep +contract. + +Add qcom_pcie_set_suspend_opp(), which programs the OPP marked with the +generic "opp-suspend" devicetree property (looked up via +dev_pm_opp_get_suspend_opp_freq()) as the suspend floor, and call it +from both former dev_pm_opp_set_opp(dev, NULL) sites. Platforms that +do not mark a suspend OPP keep the current behaviour unchanged. + +The helper is used in both paths that previously dropped the OPP: D3cold +and the non-S2RAM fallback. PM_SUSPEND_MEM keeps its existing behavior. + +The resume path needs no change: qcom_pcie_resume_noirq() already +re-derives the link OPP from the retrained link speed/width and sets it +via qcom_pcie_icc_opp_update(). +--- +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -2119,6 +2119,36 @@ + return ret; + } + ++/* ++ * On platforms where the PCIe controller scales via OPP (use_pm_opp), ++ * the OPP's opp-peak-kBps values carry the only memory-path (DDR/LLCC) ++ * interconnect votes, including their RPMh sleep-set contribution. ++ * Instead of unconditionally dropping the OPP on suspend, honour an OPP ++ * marked with the generic "opp-suspend" property as the suspend floor so ++ * the sleep-set bandwidth votes survive the platform sleep state. Fall ++ * back to dropping the OPP entirely when no suspend OPP is marked. ++ */ ++static int qcom_pcie_set_suspend_opp(struct qcom_pcie *pcie) ++{ ++ struct device *dev = pcie->pci->dev; ++ struct dev_pm_opp *opp; ++ unsigned long freq; ++ int ret; ++ ++ freq = dev_pm_opp_get_suspend_opp_freq(dev); ++ if (!freq) ++ return dev_pm_opp_set_opp(dev, NULL); ++ ++ opp = dev_pm_opp_find_freq_exact(dev, freq, true); ++ if (IS_ERR(opp)) ++ return PTR_ERR(opp); ++ ++ ret = dev_pm_opp_set_opp(dev, opp); ++ dev_pm_opp_put(opp); ++ ++ return ret; ++} ++ + static int qcom_pcie_suspend_noirq(struct device *dev) + { + struct qcom_pcie *pcie; +@@ -2142,7 +2172,7 @@ + dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret); + + if (pcie->use_pm_opp) +- dev_pm_opp_set_opp(pcie->pci->dev, NULL); ++ qcom_pcie_set_suspend_opp(pcie); + } else { + /* + * Set minimum bandwidth required to keep data path functional during +@@ -2171,7 +2201,7 @@ + ret); + + if (pcie->use_pm_opp) +- dev_pm_opp_set_opp(pcie->pci->dev, NULL); ++ qcom_pcie_set_suspend_opp(pcie); + } + } + return ret; diff --git a/kernel/patches/1027-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch b/kernel/patches/1027-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch new file mode 100644 index 0000000..ff6839d --- /dev/null +++ b/kernel/patches/1027-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch @@ -0,0 +1,114 @@ +From: Luke Johnson +Date: Fri, 3 Jul 2026 00:00:00 +1200 +Subject: [PATCH] arm64: dts: qcom: sm8550: add PCIe suspend-only DDR/LLCC floor OPP + +On SM8550/QCS8550 the PCIe controllers scale via OPP (use_pm_opp), so +the probe path skips ICC init (icc_mem/icc_cpu stay NULL) and the OPP +table's opp-peak-kBps values are the only source of the MC0 (DDR) and +SH0 (LLCC) memory-path bandwidth votes, including their RPMh sleep-set +contribution. Paired with the pcie-qcom change that selects the +generic suspend OPP instead of dropping the OPP to NULL on non-S2RAM +suspend, this retains a nonzero DDR/LLCC sleep floor across s2idle: +zeroing those sleep-set votes removes the memory-path contract the AOP +needs to bring the SoC back out of cluster_sleep_1 (PSCI 0x4100c344), +and the firmware never returns on resume. + +Add a dedicated suspend-only OPP (opp-1-1) to both PCIe OPP tables +rather than marking one of the active OPPs opp-suspend. This matters: +qcom_pcie_icc_opp_update() selects the active OPP by an exact +(freq = link_speed_mbps * width * 1000, level = link_speed) key, so +marking (and lowering) the real 2.5 GT/s x1 OPP would also cap a live +Gen1 x1 / downtrained link to the tiny suspend bandwidth. opp-hz = 1 +is a synthetic key that is deliberately not a real speed*width product, +so the active-link exact match never selects it; the active OPP table +is left completely unchanged. dev_pm_opp_get_suspend_opp_freq() still +returns it (it carries opp-suspend) so it is used only at suspend. + +opp-hz = 1 is safe as a suspend key: the PCIe link is in d3cold at +suspend so link bandwidth is moot, and opp-hz has no functional clock +effect here -- the only clock bound to the OPP table (gcc_pcie_N_aux) +is a branch off a single-entry (19.2 MHz) RCG, so clk_set_rate() rounds +any requested rate to 19.2 MHz and cannot fail. That last point also +guarantees the scaling-down suspend path does not error out before the +opp-peak-kBps bandwidth vote is applied. + +opp-peak-kBps = 5000 (5 MB/s) is the DDR/LLCC wake-contract floor; it is +~500x below the 2.5 GT/s x1 active OPP's 250 MB/s and lets DDR settle to +its lowest tier during suspend. + +Measured on Retroid Pocket 6 (charge-counter, RTC-timed s2idle, back to +back at ~3.7-4.0 V): V-OPP baseline (no suspend floor OPP) 151 mA; ++ USB runtime-PM shed 107 mA; + this 5 MB/s suspend floor ~54 mA +(~210 mW) -- less than half the prior ~113 mA baseline. Reliability: +13/13 s2idle suspend/resume cycles woke cleanly (12x 90 s spaced + 1x +20 min), suspend_stats success incrementing, boot_id unchanged, zero +resets. The earlier 250 MB/s wake floor was also reproduced on AYN Thor; +the 5 MB/s floor remains board-validation-sensitive because it deliberately +minimizes wake margin. + +pcie0 is the WiFi link (ath12k, trains Gen2 x2 -> opp-5000000); pcie1 is +active on the AYANEO Pocket family (Renesas uPD720201 USB3 host). + +Depends on the pcie-qcom "use suspend OPP instead of dropping OPP" +change (0220); without it dev_pm_opp_set_opp(dev, NULL) still runs and +this OPP is never selected at suspend. +--- + arch/arm64/boot/dts/qcom/sm8550.dtsi | 40 ++++++++++++++++++++++++++++ + 1 file changed, 40 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/sm8550.dtsi ++++ b/arch/arm64/boot/dts/qcom/sm8550.dtsi +@@ -2035,6 +2035,26 @@ + opp-level = <1>; + }; + ++ /* ++ * Suspend-only DDR/LLCC bandwidth floor. On use_pm_opp ++ * PCIe (ICC handled via OPP) the OPP's opp-peak-kBps is ++ * the only source of the MC0(DDR)/SH0(LLCC) sleep-set ++ * votes; pcie-qcom votes this opp-suspend OPP (not NULL) ++ * at suspend so a nonzero floor survives cluster_sleep_1. ++ * opp-hz=1 is a synthetic key (not a real speed*width ++ * product) so qcom_pcie_icc_opp_update()'s exact ++ * (freq,level) match never selects it for an active link; ++ * the link is d3cold at suspend so opp-hz has no clock ++ * effect (the aux RCG rounds any rate to 19.2 MHz). ++ */ ++ opp-1-1 { ++ opp-hz = /bits/ 64 <1>; ++ required-opps = <&rpmhpd_opp_low_svs>; ++ opp-peak-kBps = <5000 1>; ++ opp-level = <1>; ++ opp-suspend; ++ }; ++ + /* 2.5 GT/s x2 */ + opp-5000000-1 { + opp-hz = /bits/ 64 <5000000>; +@@ -2215,6 +2235,26 @@ + opp-level = <1>; + }; + ++ /* ++ * Suspend-only DDR/LLCC bandwidth floor. On use_pm_opp ++ * PCIe (ICC handled via OPP) the OPP's opp-peak-kBps is ++ * the only source of the MC0(DDR)/SH0(LLCC) sleep-set ++ * votes; pcie-qcom votes this opp-suspend OPP (not NULL) ++ * at suspend so a nonzero floor survives cluster_sleep_1. ++ * opp-hz=1 is a synthetic key (not a real speed*width ++ * product) so qcom_pcie_icc_opp_update()'s exact ++ * (freq,level) match never selects it for an active link; ++ * the link is d3cold at suspend so opp-hz has no clock ++ * effect (the aux RCG rounds any rate to 19.2 MHz). ++ */ ++ opp-1-1 { ++ opp-hz = /bits/ 64 <1>; ++ required-opps = <&rpmhpd_opp_low_svs>; ++ opp-peak-kBps = <5000 1>; ++ opp-level = <1>; ++ opp-suspend; ++ }; ++ + /* 2.5 GT/s x2 */ + opp-5000000-1 { + opp-hz = /bits/ 64 <5000000>; diff --git a/kernel/patches/series b/kernel/patches/series index a6507f3..6c0a70f 100644 --- a/kernel/patches/series +++ b/kernel/patches/series @@ -93,6 +93,17 @@ v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch 1011-scsi-ufs-hold-clk-gating-across-system-pm.patch 1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch +1016-pci-qcom-set-max-opp-before-dbi-resume.patch +1017-pci-host-common-add-d3cold-possible.patch +1018-pci-qcom-add-get-ltssm.patch +1019-pci-qcom-power-down-phy.patch +1020-pci-dwc-use-common-d3cold-helper.patch +1021-pci-qcom-add-d3cold-support.patch +1023-pci-qcom-skip-l23-ready-after-pme-sm8550.patch +1024-regulator-qcom-rpmh-add-suspend-state-support.patch +1025-regulator-core-apply-mem-state-for-s2idle.patch +1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch +1027-arm64-dts-qcom-sm8550-mark-pcie-suspend-opp.patch # ============== SM8550 DTSI prerequisites ============== 0001-pcie-update-sm8550-dtsi.patch From 3e48f6da53d0bdd4bc41aee3bfde74ca5fe368b4 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Wed, 5 Aug 2026 17:23:02 -0400 Subject: [PATCH 09/11] kernel: refresh deep-sleep patches for 7.1.5 --- kernel/PATCHES.md | 3 - ...in-relink-completions-out-of-band-pm.patch | 17 ++- ...sg-qcom-glink-smem-support-wake-irqs.patch | 7 +- ...ptics-fix-rumble-bridge-atomic-sleep.patch | 28 ++--- ...agate-hibern8-exit-failure-clk-scale.patch | 8 +- ...isable-rx-linecfg-after-link-startup.patch | 3 +- ...i-qcom-set-max-opp-before-dbi-resume.patch | 100 ------------------ ...i-qcom-use-suspend-opp-for-non-s2ram.patch | 5 +- kernel/patches/series | 3 +- 9 files changed, 30 insertions(+), 144 deletions(-) delete mode 100644 kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch diff --git a/kernel/PATCHES.md b/kernel/PATCHES.md index 914c8e4..fdb0f92 100644 --- a/kernel/PATCHES.md +++ b/kernel/PATCHES.md @@ -192,9 +192,6 @@ no equivalent submission was found, or a permanent URL to the upstream submissio - `patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch` source: https://github.com/ROCKNIX/distribution/blob/aa7d8320421bd44ec5b46b2d852b544fec237c54/projects/ROCKNIX/devices/SM8550/patches/linux/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch upstream: unknown -- `patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch` - source: https://github.com/torvalds/linux/commit/5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 - upstream: https://github.com/torvalds/linux/commit/5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 - `patches/1017-pci-host-common-add-d3cold-possible.patch` source: https://github.com/thorch-os/thorch/blob/45dfb981aeed5e3758b85a5b8e933d994da96dec/packages/linux-thorch/patches/0210-PCI-host-common-add-d3cold-eligibility-helper.patch upstream: unknown diff --git a/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch b/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch index 02bb82a..c8bd4a7 100644 --- a/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch +++ b/kernel/patches/0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch @@ -28,8 +28,8 @@ ungate window and run link recovery directly. Signed-off-by: jaewun -[armada: rebase ufshcd_intr() onto Linux 7.0.11, before the -active_uic_cmd threaded-IRQ handoff change present in ROCKNIX's 7.1.2 base.] +[armada: rebase ufshcd_intr() onto Linux 7.1.5's active_uic_cmd +threaded-IRQ handoff.] --- --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c @@ -323,7 +323,7 @@ active_uic_cmd threaded-IRQ handoff change present in ROCKNIX's 7.1.2 base.] /** * ufshcd_intr - Main interrupt service routine * @irq: irq number -@@ -7207,6 +7317,18 @@ +@@ -7468,13 +7578,25 @@ struct ufs_hba *hba = __hba; u32 intr_status, enabled_intr_status; @@ -339,9 +339,16 @@ active_uic_cmd threaded-IRQ handoff change present in ROCKNIX's 7.1.2 base.] + return IRQ_HANDLED; + } + - /* Move interrupt handling to thread when MCQ & ESI are not enabled */ - if (!hba->mcq_enabled || !hba->mcq_esi_enabled) + /* + * Handle interrupt in thread if MCQ or ESI is disabled, + * and no active UIC command. + */ + if ((!hba->mcq_enabled || !hba->mcq_esi_enabled) && + !hba->active_uic_cmd) return IRQ_WAKE_THREAD; + + intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS); + enabled_intr_status = intr_status & ufshcd_readl(hba, REG_INTERRUPT_ENABLE); @@ -9729,6 +9957,7 @@ static void ufshcd_hba_exit(struct ufs_hba *hba) diff --git a/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch b/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch index 67411aa..ddef2ac 100644 --- a/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch +++ b/kernel/patches/0507-rpmsg-qcom-glink-smem-support-wake-irqs.patch @@ -20,15 +20,10 @@ Signed-off-by: gh123man --- a/drivers/rpmsg/qcom_glink_smem.c +++ b/drivers/rpmsg/qcom_glink_smem.c -@@ -7,7 +7,8 @@ - #include - #include - #include +@@ -10,2 +10,3 @@ #include +#include #include - #include - #include @@ -306,7 +307,7 @@ struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent, smem->irq = of_irq_get(smem->dev.of_node, 0); diff --git a/kernel/patches/1003-input-haptics-fix-rumble-bridge-atomic-sleep.patch b/kernel/patches/1003-input-haptics-fix-rumble-bridge-atomic-sleep.patch index 9eb8fb1..eace9c1 100644 --- a/kernel/patches/1003-input-haptics-fix-rumble-bridge-atomic-sleep.patch +++ b/kernel/patches/1003-input-haptics-fix-rumble-bridge-atomic-sleep.patch @@ -219,30 +219,14 @@ post-wake rumble. if (drv->enable_gpio) gpiod_set_value_cansleep(drv->enable_gpio, 0); -@@ -620,6 +648,17 @@ - regulator_disable(drv->vdd); - } +@@ -625,6 +653,10 @@ static int rsinput_suspend(struct device *dev) + { + struct rsinput_driver *drv = dev_get_drvdata(dev); -+static int rsinput_suspend(struct device *dev) -+{ -+ struct rsinput_driver *drv = dev_get_drvdata(dev); -+ + /* a pending rumble start must not fire into or after haptics suspend */ + if (drv->rumble_work.func) + cancel_work_sync(&drv->rumble_work); + -+ return 0; -+} -+ - static int rsinput_resume(struct device *dev) - { - struct rsinput_driver *drv = dev_get_drvdata(dev); -@@ -627,7 +666,7 @@ - return rsinput_init_commands(drv); - } - --static DEFINE_SIMPLE_DEV_PM_OPS(rsinput_pm_ops, NULL, rsinput_resume); -+static DEFINE_SIMPLE_DEV_PM_OPS(rsinput_pm_ops, rsinput_suspend, rsinput_resume); - - static const struct of_device_id rsinput_of_match[] = { - { .compatible = "gamepad,rsinput" }, + /* + * The MCU streams gamepad reports continuously over the UART. If it keeps + * sending across the system-suspend teardown, the geni-serial RX interrupt diff --git a/kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch b/kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch index 912fb14..dfd27a2 100644 --- a/kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch +++ b/kernel/patches/1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch @@ -21,14 +21,16 @@ 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 @@ +@@ -1983,10 +1983,10 @@ } - + ufs_qcom_icc_update_bw(host); - ufshcd_uic_hibern8_exit(hba); + err = ufshcd_uic_hibern8_exit(hba); } - + - return 0; + return err; } + + static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host) diff --git a/kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch b/kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch index 6074339..c87dfec 100644 --- a/kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch +++ b/kernel/patches/1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch @@ -146,7 +146,7 @@ Signed-off-by: jaewun 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, +@@ -739,6 +746,12 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, err = ufshcd_disable_host_tx_lcc(hba); break; @@ -158,3 +158,4 @@ Signed-off-by: jaewun + break; default: break; + } diff --git a/kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch b/kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch deleted file mode 100644 index 17c9376..0000000 --- a/kernel/patches/1016-pci-qcom-set-max-opp-before-dbi-resume.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 Mon Sep 17 00:00:00 2001 -From: Qiang Yu -Date: Thu, 16 Apr 2026 21:16:25 -0700 -Subject: [PATCH] PCI: qcom: Set max OPP before DBI access during resume - -During resume, qcom_pcie_icc_opp_update() may access DBI registers before -the OPP votes are restored, triggering NoC errors. - -Set the PCIe controller to the maximum OPP first in resume_noirq(), then -proceed with link/DBI accesses. The OPP is later updated again based on -the actual link bandwidth requirements. - -Introduce a helper to reuse the max-OPP setup code and share it with -probe(). - -Fixes: 5b6272e0efd5 ("PCI: qcom: Add OPP support to scale performance") -Signed-off-by: Qiang Yu -[mani: commit log and error log rewording] -Signed-off-by: Manivannan Sadhasivam -Link: https://patch.msgid.link/20260416-setmaxopp-v1-1-6a74e2d945a0@oss.qualcomm.com ---- - drivers/pci/controller/dwc/pcie-qcom.c | 42 ++++++++++++++++---------- - 1 file changed, 26 insertions(+), 16 deletions(-) - -diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c -index af6bf5cce65bee..f21f3806fc1f4b 100644 ---- a/drivers/pci/controller/dwc/pcie-qcom.c -+++ b/drivers/pci/controller/dwc/pcie-qcom.c -@@ -1613,6 +1613,22 @@ static void qcom_pcie_icc_opp_update(struct qcom_pcie *pcie) - } - } - -+static int qcom_pcie_set_max_opp(struct device *dev) -+{ -+ unsigned long max_freq = ULONG_MAX; -+ struct dev_pm_opp *opp; -+ int ret; -+ -+ opp = dev_pm_opp_find_freq_floor(dev, &max_freq); -+ if (IS_ERR(opp)) -+ return PTR_ERR(opp); -+ -+ ret = dev_pm_opp_set_opp(dev, opp); -+ dev_pm_opp_put(opp); -+ -+ return ret; -+} -+ - static int qcom_pcie_link_transition_count(struct seq_file *s, void *data) - { - struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private); -@@ -1845,9 +1861,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) - struct qcom_pcie_perst *perst, *tmp_perst; - struct qcom_pcie_port *port, *tmp_port; - const struct qcom_pcie_cfg *pcie_cfg; -- unsigned long max_freq = ULONG_MAX; - struct device *dev = &pdev->dev; -- struct dev_pm_opp *opp; - struct qcom_pcie *pcie; - struct dw_pcie_rp *pp; - struct resource *res; -@@ -1951,21 +1965,9 @@ static int qcom_pcie_probe(struct platform_device *pdev) - * probe(), OPP will be updated using qcom_pcie_icc_opp_update(). - */ - if (!ret) { -- opp = dev_pm_opp_find_freq_floor(dev, &max_freq); -- if (IS_ERR(opp)) { -- ret = PTR_ERR(opp); -- dev_err_probe(pci->dev, ret, -- "Unable to find max freq OPP\n"); -- goto err_pm_runtime_put; -- } else { -- ret = dev_pm_opp_set_opp(dev, opp); -- } -- -- dev_pm_opp_put(opp); -+ ret = qcom_pcie_set_max_opp(dev); - if (ret) { -- dev_err_probe(pci->dev, ret, -- "Failed to set OPP for freq %lu\n", -- max_freq); -+ dev_err_probe(dev, ret, "Failed to set max OPP\n"); - goto err_pm_runtime_put; - } - -@@ -2100,6 +2102,14 @@ static int qcom_pcie_resume_noirq(struct device *dev) - return 0; - - if (pm_suspend_target_state != PM_SUSPEND_MEM) { -+ if (pcie->use_pm_opp) { -+ ret = qcom_pcie_set_max_opp(dev); -+ if (ret) { -+ dev_err(dev, "Failed to set max OPP: %d\n", ret); -+ return ret; -+ } -+ } -+ - ret = icc_enable(pcie->icc_cpu); - if (ret) { - dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret); diff --git a/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch b/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch index 9f038d3..8d38612 100644 --- a/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch +++ b/kernel/patches/1026-pci-qcom-use-suspend-opp-for-non-s2ram.patch @@ -73,7 +73,7 @@ via qcom_pcie_icc_opp_update(). static int qcom_pcie_suspend_noirq(struct device *dev) { struct qcom_pcie *pcie; -@@ -2142,7 +2172,7 @@ +@@ -2151,8 +2181,8 @@ dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret); if (pcie->use_pm_opp) @@ -81,7 +81,8 @@ via qcom_pcie_icc_opp_update(). + qcom_pcie_set_suspend_opp(pcie); } else { /* - * Set minimum bandwidth required to keep data path functional during + * Set minimum bandwidth required to keep data path + * functional during suspend. @@ -2171,7 +2201,7 @@ ret); diff --git a/kernel/patches/series b/kernel/patches/series index 6c0a70f..1718f86 100644 --- a/kernel/patches/series +++ b/kernel/patches/series @@ -46,6 +46,7 @@ 0069-input-misc-add-konkr-sysbtn-MCU-system-buttons.patch 0031_input--Add-driver-for-RSInput-Gamepad.patch 0006-hid-playstation-expose-DualSense-Edge-Fn-and-back-paddles.patch +1004-input-rsinput-suspend-resume-gamepad-mcu.patch 0504-Enable-64-bit-processes-to-use-compat-input-syscalls.patch 0506-usbcore-add-interrupt-interval-override.patch @@ -85,7 +86,6 @@ v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 0201-scsi-ufs-drain-relink-completions-out-of-band-pm.patch 0203-thermal-qcom-tsens-skip-sm8550-uplow-wake-irq.patch 0207-scsi-ufs-qcom-balance-irq-on-host-reset-error.patch -1004-input-rsinput-suspend-resume-gamepad-mcu.patch 1006-tty-serial-qcom-geni-mask-non-console-irq-on-suspend.patch 1007-scsi-ufs-qcom-propagate-hibern8-exit-failure-clk-scale.patch 1008-scsi-ufs-qcom-auto-hibern8-clk-gating-collision.patch @@ -93,7 +93,6 @@ v9_20260729_qualcomm_crypto_qce_runtime_pm_interconnect.patch 1010-scsi-ufs-qcom-keep-mphy-powered-on-hibern8-park.patch 1011-scsi-ufs-hold-clk-gating-across-system-pm.patch 1015-ufs-qcom-disable-rx-linecfg-after-link-startup.patch -1016-pci-qcom-set-max-opp-before-dbi-resume.patch 1017-pci-host-common-add-d3cold-possible.patch 1018-pci-qcom-add-get-ltssm.patch 1019-pci-qcom-power-down-phy.patch From 7fb2833b7f48819a1db6d7b0855b98d2e84c8736 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Wed, 5 Aug 2026 17:25:49 -0400 Subject: [PATCH 10/11] kernel: refresh RP6 firmware DTS overlay --- kernel/dts/qcs8550-retroidpocket-rp6.dts.patch | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/kernel/dts/qcs8550-retroidpocket-rp6.dts.patch b/kernel/dts/qcs8550-retroidpocket-rp6.dts.patch index 8abc8dd..dda5434 100644 --- a/kernel/dts/qcs8550-retroidpocket-rp6.dts.patch +++ b/kernel/dts/qcs8550-retroidpocket-rp6.dts.patch @@ -3,9 +3,7 @@ Subject: [PATCH] arm64: dts: qcom: rp6: use device-specific audio firmware --- a/arch/arm64/boot/dts/qcom/qcs8550-retroidpocket-rp6.dts +++ b/arch/arm64/boot/dts/qcom/qcs8550-retroidpocket-rp6.dts -@@ -379,15 +379,15 @@ - }; - +@@ -381,5 +381,5 @@ &remoteproc_adsp { - firmware-name = "qcom/sm8550/ayn/odin2/adsp.mbn", - "qcom/sm8550/ayn/odin2/adsp_dtb.mbn"; @@ -13,7 +11,7 @@ Subject: [PATCH] arm64: dts: qcom: rp6: use device-specific audio firmware + "qcom/sm8550/retroidpocket/rp6/adsp_dtb.mbn"; status = "okay"; }; - +@@ -392,7 +392,7 @@ &spk_amp_l { - firmware-name = "qcom/sm8550/ayn/odin2/aw883xx_acf.bin"; + firmware-name = "qcom/sm8550/retroidpocket/rp6/aw883xx_acf.bin"; From 071d09c1fb4e1e57f183c250dcb3ec8f9618ed04 Mon Sep 17 00:00:00 2001 From: Brian Floersch Date: Wed, 5 Aug 2026 18:10:53 -0400 Subject: [PATCH 11/11] kernel: fix UFS diagnostics for 7.1 --- ...i-ufs-recover-hibern8-enter-clk-gating.patch | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch index 5800692..468284e 100644 --- a/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch +++ b/kernel/patches/1009-scsi-ufs-recover-hibern8-enter-clk-gating.patch @@ -32,18 +32,17 @@ Signed-off-by: gh123man --- --- a/drivers/ufs/core/ufshcd.c +++ b/drivers/ufs/core/ufshcd.c -@@ -2056,6 +2056,10 @@ static void ufshcd_gate_work(struct work_struct *work) +@@ -2056,6 +2056,9 @@ static void ufshcd_gate_work(struct work_struct *work) { struct ufs_hba *hba = container_of(work, struct ufs_hba, clk_gating.gate_work.work); + enum clk_gating_state admitted_state; + int admitted_active_reqs; + bool admitted_pm; -+ bool admitted_system_suspend; int ret; scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) { -@@ -2075,6 +2079,16 @@ static void ufshcd_gate_work(struct work_struct *work) +@@ -2075,6 +2078,15 @@ static void ufshcd_gate_work(struct work_struct *work) if (hba->clk_gating.active_reqs) return; @@ -56,18 +55,16 @@ Signed-off-by: gh123man + admitted_state = hba->clk_gating.state; + admitted_active_reqs = hba->clk_gating.active_reqs; + admitted_pm = READ_ONCE(hba->pm_op_in_progress); -+ admitted_system_suspend = READ_ONCE(hba->system_suspending); } scoped_guard(spinlock_irqsave, hba->host->host_lock) { -@@ -2085,16 +2099,99 @@ static void ufshcd_gate_work(struct work_struct *work) +@@ -2085,16 +2097,96 @@ static void ufshcd_gate_work(struct work_struct *work) /* put the link into hibern8 mode before turning off clocks */ if (ufshcd_can_hibern8_during_gating(hba)) { + enum clk_gating_state h8_start_state; + int h8_start_active_reqs; + bool h8_start_pm; -+ bool h8_start_system_suspend; + + /* Snapshot any state transition since the worker was admitted. */ + scoped_guard(spinlock_irqsave, &hba->clk_gating.lock) { @@ -75,7 +72,6 @@ Signed-off-by: gh123man + h8_start_active_reqs = hba->clk_gating.active_reqs; + } + h8_start_pm = READ_ONCE(hba->pm_op_in_progress); -+ h8_start_system_suspend = READ_ONCE(hba->system_suspending); + + /* + * Suppress the generic fatal UIC error path while this worker owns @@ -103,15 +99,14 @@ Signed-off-by: gh123man + "%s: hibern8 enter failed %d, recovering link\n", __func__, ret); + dev_err(hba->dev, -+ "%s: gate race snapshot: admitted state=%d active=%d pm=%d system=%d; h8-start state=%d active=%d pm=%d system=%d; failed state=%d active=%d enabled=%d suspended=%d pm=%d system=%d outstanding=%#lx gate-work=%u ungate-work=%u\n", ++ "%s: gate race snapshot: admitted state=%d active=%d pm=%d; h8-start state=%d active=%d pm=%d; failed state=%d active=%d enabled=%d suspended=%d pm=%d outstanding=%#lx gate-work=%u ungate-work=%u\n", + __func__, admitted_state, admitted_active_reqs, -+ admitted_pm, admitted_system_suspend, ++ admitted_pm, + h8_start_state, h8_start_active_reqs, -+ h8_start_pm, h8_start_system_suspend, ++ h8_start_pm, + failed_state, failed_active_reqs, + failed_enabled, failed_suspended, + READ_ONCE(hba->pm_op_in_progress), -+ READ_ONCE(hba->system_suspending), + READ_ONCE(hba->outstanding_reqs), + work_busy(&hba->clk_gating.gate_work.work), + work_busy(&hba->clk_gating.ungate_work));